some audio things
[carveJwlIkooP6JGAAIwe30JlM.git] / blender_export.py
1 #
2 # =============================================================================
3 #
4 # Copyright . . . -----, ,----- ,---. .---.
5 # 2021-2023 |\ /| | / | | | | /|
6 # | \ / | +-- / +----- +---' | / |
7 # | \ / | | / | | \ | / |
8 # | \/ | | / | | \ | / |
9 # ' ' '--' [] '----- '----- ' ' '---' SOFTWARE
10 #
11 # =============================================================================
12 #
13 # Python exporter for Blender, compiles .mdl format for Skate Rift.
14 #
15 # Its really slow, sorry, I don't know how to speed it up.
16 # Also not sure why you need to put # before {} in code blocks, there is errors
17 # otherwise
18 #
19
20 import bpy, math, gpu, os
21 import cProfile
22 from ctypes import *
23 from mathutils import *
24 from gpu_extras.batch import batch_for_shader
25
26 bl_info = {
27 "name":"Skate Rift model compiler",
28 "author": "Harry Godden (hgn)",
29 "version": (0,2),
30 "blender":(3,1,0),
31 "location":"Export",
32 "descriptin":"",
33 "warning":"",
34 "wiki_url":"",
35 "category":"Import/Export",
36 }
37
38 class mdl_vert(Structure): # 48 bytes. Quite large. Could compress
39 #{ # the normals and uvs to i16s. Not an
40 _pack_ = 1 # real issue, yet.
41 _fields_ = [("co",c_float*3),
42 ("norm",c_float*3),
43 ("uv",c_float*2),
44 ("colour",c_uint8*4),
45 ("weights",c_uint16*4),
46 ("groups",c_uint8*4)]
47 #}
48
49 class mdl_submesh(Structure):
50 #{
51 _pack_ = 1
52 _fields_ = [("indice_start",c_uint32),
53 ("indice_count",c_uint32),
54 ("vertex_start",c_uint32),
55 ("vertex_count",c_uint32),
56 ("bbx",(c_float*3)*2),
57 ("material_id",c_uint32)] # index into the material array
58 #}
59
60 class mdl_texture(Structure):
61 #{
62 _pack_ = 1
63 _fields_ = [("pstr_name",c_uint32),
64 ("pack_offset",c_uint32),
65 ("pack_length",c_uint32)]
66 #}
67
68 class mdl_material(Structure):
69 #{
70 _pack_ = 1
71 _fields_ = [("pstr_name",c_uint32),
72 ("shader",c_uint32),
73 ("flags",c_uint32),
74 ("surface_prop",c_uint32),
75 ("colour",c_float*4),
76 ("colour1",c_float*4),
77 ("tex_diffuse",c_uint32),
78 ("tex_decal",c_uint32),
79 ("tex_normal",c_uint32)]
80 #}
81
82 class mdl_node(Structure):
83 #{
84 _pack_ = 1
85 _fields_ = [("co",c_float*3),
86 ( "q",c_float*4),
87 ( "s",c_float*3),
88 ("sub_uid",c_uint32), # dont use
89 ("submesh_start",c_uint32),
90 ("submesh_count",c_uint32),
91 ("classtype",c_uint32),
92 ("offset",c_uint32),
93 ("parent",c_uint32),
94 ("pstr_name",c_uint32)]
95 #}
96
97 class mdl_header(Structure):
98 #{
99 _pack_ = 1
100 _fields_ = [("identifier",c_uint32),
101 ("version",c_uint32),
102 ("file_length",c_uint32),
103 ("pad0",c_uint32),
104
105 ("node_count",c_uint32),
106 ("node_offset",c_uint32),
107
108 ("submesh_count",c_uint32),
109 ("submesh_offset",c_uint32),
110
111 ("material_count",c_uint32),
112 ("material_offset",c_uint32),
113
114 ("texture_count",c_uint32),
115 ("texture_offset",c_uint32),
116
117 ("anim_count",c_uint32),
118 ("anim_offset",c_uint32),
119
120 ("entdata_size",c_uint32),
121 ("entdata_offset",c_uint32),
122
123 ("strings_size",c_uint32),
124 ("strings_offset",c_uint32),
125
126 ("keyframe_count",c_uint32),
127 ("keyframe_offset",c_uint32),
128
129 ("vertex_count",c_uint32),
130 ("vertex_offset",c_uint32),
131
132 ("indice_count",c_uint32),
133 ("indice_offset",c_uint32),
134
135 ("pack_size",c_uint32),
136 ("pack_offset",c_uint32)]
137 #}
138
139 class mdl_animation(Structure):
140 #{
141 _pack_ = 1
142 _fields_ = [("pstr_name",c_uint32),
143 ("length",c_uint32),
144 ("rate",c_float),
145 ("offset",c_uint32)]
146 #}
147
148 class mdl_keyframe(Structure):
149 #{
150 _pack_ = 1
151 _fields_ = [("co",c_float*3),
152 ("q",c_float*4),
153 ("s",c_float*3)]
154 #}
155
156 # ---------------------------------------------------------------------------- #
157 # #
158 # Entity definitions #
159 # #
160 # ---------------------------------------------------------------------------- #
161 #
162 # ctypes _fields_ defines the data which is filled in by:
163 # def encode_obj( _, node, node_def ):
164 #
165 # gizmos get drawn into the viewport via:
166 # @staticmethod
167 # def draw_scene_helpers( obj ):
168 #
169 # editor enterface, simiraliy:
170 # @staticmethod
171 # def editor_interface( layout, obj ):
172 #
173
174 # Classtype 1
175 #
176 # Purpose: A rift. must target another gate, the target gate can not have more
177 # than one target nodes of its own.
178 #
179 class classtype_gate(Structure):
180 #{
181 _pack_ = 1
182 _fields_ = [("target",c_uint32),
183 ("dims",c_float*3)]
184
185 def encode_obj(_, node,node_def):
186 #{
187 node.classtype = 1
188
189 obj = node_def['obj']
190
191 if obj.cv_data.target != None:
192 _.target = obj.cv_data.target.cv_data.uid
193
194 if obj.type == 'MESH':
195 #{
196 _.dims[0] = obj.data.cv_data.v0[0]
197 _.dims[1] = obj.data.cv_data.v0[1]
198 _.dims[2] = obj.data.cv_data.v0[2]
199 #}
200 else:
201 #{
202 _.dims[0] = obj.cv_data.v0[0]
203 _.dims[1] = obj.cv_data.v0[1]
204 _.dims[2] = obj.cv_data.v0[2]
205 #}
206 #}
207
208 @staticmethod
209 def draw_scene_helpers( obj ):
210 #{
211 global cv_view_verts, cv_view_colours
212
213 if obj.type == 'MESH':
214 dims = obj.data.cv_data.v0
215 else:
216 dims = obj.cv_data.v0
217
218 vs = [None]*9
219 c = Vector((0,0,dims[2]))
220
221 vs[0] = obj.matrix_world @ Vector((-dims[0],0.0,-dims[1]+dims[2]))
222 vs[1] = obj.matrix_world @ Vector((-dims[0],0.0, dims[1]+dims[2]))
223 vs[2] = obj.matrix_world @ Vector(( dims[0],0.0, dims[1]+dims[2]))
224 vs[3] = obj.matrix_world @ Vector(( dims[0],0.0,-dims[1]+dims[2]))
225 vs[4] = obj.matrix_world @ (c+Vector((-1,0,-2)))
226 vs[5] = obj.matrix_world @ (c+Vector((-1,0, 2)))
227 vs[6] = obj.matrix_world @ (c+Vector(( 1,0, 2)))
228 vs[7] = obj.matrix_world @ (c+Vector((-1,0, 0)))
229 vs[8] = obj.matrix_world @ (c+Vector(( 1,0, 0)))
230
231 indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(7,8)]
232
233 for l in indices:
234 #{
235 v0 = vs[l[0]]
236 v1 = vs[l[1]]
237 cv_view_verts += [(v0[0],v0[1],v0[2])]
238 cv_view_verts += [(v1[0],v1[1],v1[2])]
239 cv_view_colours += [(1,1,0,1),(1,1,0,1)]
240 #}
241
242 sw = (0.4,0.4,0.4,0.2)
243 if obj.cv_data.target != None:
244 cv_draw_arrow( obj.location, obj.cv_data.target.location, sw )
245 #}
246
247 @staticmethod
248 def editor_interface( layout, obj ):
249 #{
250 layout.prop( obj.cv_data, "target" )
251
252 mesh = obj.data
253 layout.label( text=F"(i) Data is stored in {mesh.name}" )
254 layout.prop( mesh.cv_data, "v0", text="Gate dimensions" )
255 #}
256 #}
257
258 class classtype_nonlocal_gate(classtype_gate):
259 #{
260 def encode_obj(_,node,node_def):
261 #{
262 node.classtype = 300
263
264 obj = node_def['obj']
265 _.target = encoder_process_pstr( node_def['obj'].cv_data.strp )
266
267 if obj.type == 'MESH':
268 #{
269 _.dims[0] = obj.data.cv_data.v0[0]
270 _.dims[1] = obj.data.cv_data.v0[1]
271 _.dims[2] = obj.data.cv_data.v0[2]
272 #}
273 else:
274 #{
275 _.dims[0] = obj.cv_data.v0[0]
276 _.dims[1] = obj.cv_data.v0[1]
277 _.dims[2] = obj.cv_data.v0[2]
278 #}
279 #}
280
281 @staticmethod
282 def editor_interface( layout, obj ):
283 #{
284 layout.prop( obj.cv_data, "strp", text="Nonlocal ID" )
285
286 mesh = obj.data
287 layout.label( text=F"(i) Data is stored in {mesh.name}" )
288 layout.prop( mesh.cv_data, "v0", text="Gate dimensions" )
289 #}
290 #}
291
292 # Classtype 3
293 #
294 # Purpose: player can reset here, its a safe place
295 # spawns can share the same name, the closest one will be picked
296 #
297 # when the world loads it will pick the one named 'start' first.
298 #
299 class classtype_spawn(Structure):
300 #{
301 _pack_ = 1
302 _fields_ = [("pstr_alias",c_uint32)]
303
304 def encode_obj(_, node,node_def):
305 #{
306 node.classtype = 3
307 _.pstr_alias = encoder_process_pstr( node_def['obj'].cv_data.strp )
308 #}
309
310 @staticmethod
311 def draw_scene_helpers( obj ):
312 #{
313 global cv_view_verts, cv_view_colours
314
315 vs = [None]*4
316 vs[0] = obj.matrix_world @ Vector((0,0,0))
317 vs[1] = obj.matrix_world @ Vector((0,2,0))
318 vs[2] = obj.matrix_world @ Vector((0.5,1,0))
319 vs[3] = obj.matrix_world @ Vector((-0.5,1,0))
320 indices = [(0,1),(1,2),(1,3)]
321
322 for l in indices:
323 #{
324 v0 = vs[l[0]]
325 v1 = vs[l[1]]
326
327 cv_view_verts += [(v0[0],v0[1],v0[2])]
328 cv_view_verts += [(v1[0],v1[1],v1[2])]
329 cv_view_colours += [(0,1,1,1),(0,1,1,1)]
330 #}
331
332 cv_draw_sphere( obj.location, 20.0, [0.1,0,0.9,0.4] )
333 #}
334
335 @staticmethod
336 def editor_interface( layout, obj ):
337 #{
338 layout.prop( obj.cv_data, "strp", text="Alias" )
339 #}
340 #}
341
342 # Classtype 4
343 #
344 # Purpose: Tells the game to draw water HERE, at this entity.
345 #
346 class classtype_water(Structure):
347 #{
348 _pack_ = 1
349 _fields_ = [("temp",c_uint32)]
350
351 def encode_obj(_, node,node_def):
352 #{
353 node.classtype = 4
354 # no data, spooky
355 #}
356 #}
357
358 # Classtype 8
359 #
360 # Purpose: Defines a route node and links to up to two more nodes
361 #
362 class classtype_route_node(Structure):
363 #{
364 _pack_ = 1
365 _fields_ = [("target",c_uint32),
366 ("target1",c_uint32)]
367
368 def encode_obj(_, node,node_def):
369 #{
370 node.classtype = 8
371 obj = node_def['obj']
372
373 if obj.cv_data.target != None:
374 _.target = obj.cv_data.target.cv_data.uid
375 if obj.cv_data.target1 != None:
376 _.target1 = obj.cv_data.target1.cv_data.uid
377 #}
378
379 @staticmethod
380 def draw_scene_helpers( obj ):
381 #{
382 global cv_view_verts, cv_view_colours
383
384 sw = Vector((0.4,0.4,0.4,0.2))
385 sw2 = Vector((1.5,0.2,0.2,0.0))
386 if obj.cv_data.target != None:
387 cv_draw_bpath( obj, obj.cv_data.target, sw, sw )
388 if obj.cv_data.target1 != None:
389 cv_draw_bpath( obj, obj.cv_data.target1, sw, sw )
390
391 cv_draw_bhandle( obj, 1.0, (0.8,0.8,0.8,1.0) )
392 cv_draw_bhandle( obj, -1.0, (0.4,0.4,0.4,1.0) )
393
394 p1 = obj.location+ \
395 obj.matrix_world.to_quaternion() @ Vector((0,0,-6+1.5))
396 cv_draw_arrow( obj.location, p1, sw )
397 #}
398
399 @staticmethod
400 def editor_interface( layout, obj ):
401 #{
402 layout.prop( obj.cv_data, "target", text="Left" )
403 layout.prop( obj.cv_data, "target1", text="Right" )
404 #}
405 #}
406
407 # Classtype 9
408 #
409 # Purpose: Defines a route, its 'starting' point, and the colour to use for it
410 #
411 class classtype_route(Structure):
412 #{
413 _pack_ = 1
414 _fields_ = [("id_start",c_uint32),
415 ("pstr_name",c_uint32),
416 ("colour",c_float*3)]
417
418 def encode_obj(_, node,node_def):
419 #{
420 node.classtype = 9
421 obj = node_def['obj']
422
423 _.colour[0] = obj.cv_data.colour[0]
424 _.colour[1] = obj.cv_data.colour[1]
425 _.colour[2] = obj.cv_data.colour[2]
426 _.pstr_name = encoder_process_pstr( obj.cv_data.strp )
427
428 if obj.cv_data.target != None:
429 _.id_start = obj.cv_data.target.cv_data.uid
430 #}
431
432 @staticmethod
433 def draw_scene_helpers( obj ):
434 #{
435 global cv_view_verts, cv_view_colours, cv_view_course_i
436
437 if obj.cv_data.target:
438 cv_draw_arrow( obj.location, obj.cv_data.target.location, [1,1,1,1] )
439
440 # Tries to simulate how we do it in the game
441 #
442 stack = [None]*64
443 stack_i = [0]*64
444 stack[0] = obj.cv_data.target
445 si = 1
446 loop_complete = False
447
448 while si > 0:
449 #{
450 if stack_i[si-1] == 2:
451 #{
452 si -= 1
453 continue
454
455 if si == 0: # Loop failed to complete
456 break
457 #}
458
459 node = stack[si-1]
460
461 targets = [None,None]
462 targets[0] = node.cv_data.target
463
464 if node.cv_data.classtype == 'classtype_route_node':
465 #{
466 targets[1] = node.cv_data.target1
467 #}
468
469 nextnode = targets[stack_i[si-1]]
470 stack_i[si-1] += 1
471
472 if nextnode != None: # branch
473 #{
474 if nextnode == stack[0]: # Loop completed
475 #{
476 loop_complete = True
477 break
478 #}
479
480 valid=True
481 for sj in range(si):
482 #{
483 if stack[sj] == nextnode: # invalidated path
484 #{
485 valid=False
486 break
487 #}
488 #}
489
490 if valid:
491 #{
492 stack_i[si] = 0
493 stack[si] = nextnode
494 si += 1
495 continue
496 #}
497 #}
498 #}
499
500 if loop_complete:
501 #{
502 cc = Vector((obj.cv_data.colour[0],\
503 obj.cv_data.colour[1],\
504 obj.cv_data.colour[2],\
505 1.0))
506
507 for sj in range(si):
508 #{
509 sk = (sj+1)%si
510
511 if stack[sj].cv_data.classtype == 'classtype_gate' and \
512 stack[sk].cv_data.classtype == 'classtype_gate':
513 #{
514 dist = (stack[sj].location-stack[sk].location).magnitude
515 cv_draw_sbpath( stack[sj], stack[sk], cc*0.4, cc, dist, dist )
516 #}
517 else:
518 cv_draw_bpath( stack[sj], stack[sk], cc, cc )
519 #}
520
521 cv_view_course_i += 1
522 #}
523 #}
524
525 @staticmethod
526 def editor_interface( layout, obj ):
527 #{
528 layout.prop( obj.cv_data, "target", text="'Start' from" )
529 layout.prop( obj.cv_data, "colour" )
530 layout.prop( obj.cv_data, "strp", text="Name" )
531 #}
532 #}
533
534 # Classtype 12
535 #
536 # Purpose: links an mesh node to a type 11
537 #
538 class classtype_skin(Structure):
539 #{
540 _pack_ = 1
541 _fields_ = [("skeleton",c_uint32)]
542
543 def encode_obj(_, node,node_def):
544 #{
545 node.classtype = 12
546
547 armature_def = node_def['linked_armature']
548 _.skeleton = armature_def['obj'].cv_data.uid
549 #}
550 #}
551
552 # Classtype 11
553 #
554 # Purpose: defines the allocation requirements for a skeleton
555 #
556 class classtype_skeleton(Structure):
557 #{
558 _pack_ = 1
559 _fields_ = [("channels",c_uint32),
560 ("ik_count",c_uint32),
561 ("collider_count",c_uint32),
562 ("anim_start",c_uint32),
563 ("anim_count",c_uint32)]
564
565 def encode_obj(_, node,node_def):
566 #{
567 node.classtype = 11
568
569 _.channels = len( node_def['bones'] )
570 _.ik_count = node_def['ik_count']
571 _.collider_count = node_def['collider_count']
572 _.anim_start = node_def['anim_start']
573 _.anim_count = node_def['anim_count']
574 #}
575 #}
576
577
578 # Classtype 10
579 #
580 # Purpose: intrinsic bone type, stores collision information and limits too
581 #
582 class classtype_bone(Structure):
583 #{
584 _pack_ = 1
585 _fields_ = [("flags",c_uint32),
586 ("ik_target",c_uint32),
587 ("ik_pole",c_uint32),
588 ("hitbox",(c_float*3)*2),
589 ("conevx",c_float*3),
590 ("conevy",c_float*3),
591 ("coneva",c_float*3),
592 ("conet",c_float)]
593
594 def encode_obj(_, node,node_def):
595 #{
596 node.classtype = 10
597
598 armature_def = node_def['linked_armature']
599 obj = node_def['bone']
600
601 _.flags = node_def['deform']
602
603 if 'ik_target' in node_def:
604 #{
605 _.flags |= 0x2
606 _.ik_target = armature_def['bones'].index( node_def['ik_target'] )
607 _.ik_pole = armature_def['bones'].index( node_def['ik_pole'] )
608 #}
609
610 # For ragdolls
611 #
612 if obj.cv_data.collider != 'collider_none':
613 #{
614 if obj.cv_data.collider == 'collider_box':
615 _.flags |= 0x4
616 else:
617 _.flags |= 0x8
618
619 _.hitbox[0][0] = obj.cv_data.v0[0]
620 _.hitbox[0][1] = obj.cv_data.v0[2]
621 _.hitbox[0][2] = -obj.cv_data.v1[1]
622 _.hitbox[1][0] = obj.cv_data.v1[0]
623 _.hitbox[1][1] = obj.cv_data.v1[2]
624 _.hitbox[1][2] = -obj.cv_data.v0[1]
625 #}
626
627 if obj.cv_data.con0:
628 #{
629 _.flags |= 0x100
630 _.conevx[0] = obj.cv_data.conevx[0]
631 _.conevx[1] = obj.cv_data.conevx[2]
632 _.conevx[2] = -obj.cv_data.conevx[1]
633 _.conevy[0] = obj.cv_data.conevy[0]
634 _.conevy[1] = obj.cv_data.conevy[2]
635 _.conevy[2] = -obj.cv_data.conevy[1]
636 _.coneva[0] = obj.cv_data.coneva[0]
637 _.coneva[1] = obj.cv_data.coneva[2]
638 _.coneva[2] = -obj.cv_data.coneva[1]
639 _.conet = obj.cv_data.conet
640 #}
641 #}
642 #}
643
644 # Classtype 100
645 #
646 # Purpose: sends a signal to another entity
647 #
648 class classtype_trigger(Structure):
649 #{
650 _pack_ = 1
651 _fields_ = [("target",c_uint32)]
652
653 def encode_obj(_, node,node_def ):
654 #{
655 node.classtype = 100
656 if node_def['obj'].cv_data.target:
657 _.target = node_def['obj'].cv_data.target.cv_data.uid
658 #}
659
660 @staticmethod
661 def draw_scene_helpers( obj ):
662 #{
663 global cv_view_verts, cv_view_colours
664 cv_draw_ucube( obj.matrix_world, [0,1,0,1] )
665
666 if obj.cv_data.target:
667 cv_draw_arrow( obj.location, obj.cv_data.target.location, [1,1,1,1] )
668 #}
669
670 @staticmethod
671 def editor_interface( layout, obj ):
672 #{
673 layout.prop( obj.cv_data, "target", text="Triggers" )
674 #}
675 #}
676
677 # Classtype 101
678 #
679 # Purpose: Gives the player an achievement.
680 # No cheating! You shouldn't use this entity anyway, since only ME can
681 # add achievements to the steam ;)
682 #
683 class classtype_logic_achievement(Structure):
684 #{
685 _pack_ = 1
686 _fields_ = [("pstr_name",c_uint32)]
687
688 def encode_obj(_, node,node_def ):
689 #{
690 node.classtype = 101
691 _.pstr_name = encoder_process_pstr( node_def['obj'].cv_data.strp )
692 #}
693
694 @staticmethod
695 def editor_interface( layout, obj ):
696 #{
697 layout.prop( obj.cv_data, "strp", text="Achievement ID" )
698 #}
699 #}
700
701 # Classtype 102
702 #
703 # Purpose: sends a signal to another entity
704 #
705 class classtype_logic_relay(Structure):
706 #{
707 _pack_ = 1
708 _fields_ = [("targets",c_uint32*4)]
709
710 def encode_obj(_, node,node_def ):
711 #{
712 node.classtype = 102
713 obj = node_def['obj']
714 if obj.cv_data.target:
715 _.targets[0] = obj.cv_data.target.cv_data.uid
716 if obj.cv_data.target1:
717 _.targets[1] = obj.cv_data.target1.cv_data.uid
718 if obj.cv_data.target2:
719 _.targets[2] = obj.cv_data.target2.cv_data.uid
720 if obj.cv_data.target3:
721 _.targets[3] = obj.cv_data.target3.cv_data.uid
722 #}
723
724 @staticmethod
725 def draw_scene_helpers( obj ):
726 #{
727 global cv_view_verts, cv_view_colours
728
729 if obj.cv_data.target:
730 cv_draw_arrow( obj.location, obj.cv_data.target.location, [1,1,1,1] )
731 if obj.cv_data.target1:
732 cv_draw_arrow( obj.location, obj.cv_data.target1.location, [1,1,1,1] )
733 if obj.cv_data.target2:
734 cv_draw_arrow( obj.location, obj.cv_data.target2.location, [1,1,1,1] )
735 if obj.cv_data.target3:
736 cv_draw_arrow( obj.location, obj.cv_data.target3.location, [1,1,1,1] )
737 #}
738
739 @staticmethod
740 def editor_interface( layout, obj ):
741 #{
742 layout.prop( obj.cv_data, "target", text="Triggers" )
743 layout.prop( obj.cv_data, "target1", text="Triggers" )
744 layout.prop( obj.cv_data, "target2", text="Triggers" )
745 layout.prop( obj.cv_data, "target3", text="Triggers" )
746 #}
747 #}
748
749 # Classtype 14
750 #
751 # Purpose: Plays some audio (44100hz .ogg vorbis only)
752 # NOTE: There is a 32mb limit on the audio buffer, world audio is
753 # decompressed and stored in signed 16 bit integers (2 bytes)
754 # per sample.
755 #
756 # volume: not used if has 3D flag
757 # flags:
758 # AUDIO_FLAG_LOOP 0x1
759 # AUDIO_FLAG_ONESHOT 0x2 (DONT USE THIS, it breaks semaphores)
760 # AUDIO_FLAG_SPACIAL_3D 0x4 (Probably what you want)
761 # AUDIO_FLAG_AUTO_START 0x8 (Play when the world starts)
762 # ......
763 # the rest are just internal flags, only use the above 3.
764 #
765 class classtype_audio(Structure):
766 #{
767 _pack_ = 1
768 _fields_ = [("pstr_file",c_uint32),
769 ("flags",c_uint32),
770 ("volume",c_float)]
771
772 def encode_obj(_, node,node_def ):
773 #{
774 node.classtype = 14
775
776 obj = node_def['obj']
777
778 _.pstr_file = encoder_process_pstr( obj.cv_data.strp )
779
780 flags = 0x00
781 if obj.cv_data.bp0: flags |= 0x1
782 if obj.cv_data.bp1: flags |= 0x4
783 if obj.cv_data.bp2: flags |= 0x8
784
785 if obj.cv_data.audio_format == 'stereo':
786 flags |= 0x200
787 if obj.cv_data.audio_format == 'remain compressed':
788 flags |= 0x400
789
790 _.flags = flags
791 _.volume = obj.cv_data.fltp
792 #}
793
794 @staticmethod
795 def editor_interface( layout, obj ):
796 #{
797 layout.prop( obj.cv_data, "strp", text = "File (.ogg)" )
798
799 layout.prop( obj.cv_data, "bp0", text = "Looping" )
800 layout.prop( obj.cv_data, "bp1", text = "3D Audio" )
801 layout.prop( obj.cv_data, "bp2", text = "Auto Start" )
802 layout.prop( obj.cv_data, "audio_format" )
803
804 layout.prop( obj.cv_data, "fltp", text = "Volume (0-1)" )
805 #}
806
807 @staticmethod
808 def draw_scene_helpers( obj ):
809 #{
810 global cv_view_verts, cv_view_colours
811
812 cv_draw_sphere( obj.location, obj.scale[0], [1,1,0,1] )
813 #}
814 #}
815
816 # Classtype 200
817 #
818 # Purpose: world light
819 #
820 class classtype_world_light( Structure ):
821 #{
822 _pack_ = 1
823 _fields_ = [("type",c_uint32),
824 ("colour",c_float*4),
825 ("angle",c_float),
826 ("range",c_float)]
827
828 def encode_obj(_, node, node_def):
829 #{
830 node.classtype = 200
831
832 obj = node_def['obj']
833 data = obj.data
834 _.colour[0] = data.color[0]
835 _.colour[1] = data.color[1]
836 _.colour[2] = data.color[2]
837 _.colour[3] = data.energy
838 _.range = data.cutoff_distance # this has to be manually set
839 # TODO: At some point, automate a min
840 # threshold value
841
842 if obj.data.type == 'POINT':
843 #{
844 _.type = 0
845 _.angle = 0.0
846 #}
847 elif obj.data.type == 'SPOT':
848 #{
849 _.type = 1
850 _.angle = data.spot_size*0.5
851 #}
852
853 if data.cv_data.bp0:
854 _.type += 2
855 #}
856
857 @staticmethod
858 def editor_interface( layout, obj ):
859 #{
860 pass
861 #}
862 #}
863
864 # Classtype 201
865 #
866 # Purpose: lighting settings for world
867 #
868 class classtype_lighting_info(Structure):
869 #{
870 _pack_ = 1
871 _fields_ = [("colours",(c_float*3)*3),
872 ("directions",(c_float*2)*3),
873 ("states",c_uint32*3),
874 ("shadow_spread",c_float),
875 ("shadow_length",c_float),
876 ("ambient",c_float*3)]
877
878 def encode_obj(_, node, node_def):
879 #{
880 node.classtype = 201
881
882 # TODO
883 #}
884
885 @staticmethod
886 def editor_interface( layout, obj ):
887 #{
888 pass
889 #}
890 #}
891
892 class classtype_spawn_link(Structure):
893 #{
894 _pack_ = 1
895 _fields_ = [("connections",c_uint32*4)]
896
897 def encode_obj(_, node,node_def ):
898 #{
899 node.classtype = 0
900 #}
901
902 @staticmethod
903 def editor_interface( layout, obj ):
904 #{
905 pass
906 #}
907
908 @staticmethod
909 def draw_scene_helpers( obj ):
910 #{
911 global cv_view_verts, cv_view_colours
912
913 count = 0
914
915 for obj1 in bpy.context.collection.objects:
916 #{
917 if (obj1.cv_data.classtype != 'classtype_spawn_link') and \
918 (obj1.cv_data.classtype != 'classtype_spawn') :
919 continue
920
921 if (obj1.location - obj.location).length < 40.0:
922 #{
923 cv_draw_line( obj.location, obj1.location, [1,1,1,1] )
924 count +=1
925 #}
926
927 if count == 4:
928 break
929 #}
930
931 cv_draw_sphere( obj.location, 20.0, [0.5,0,0.2,0.4] )
932 #}
933 #}
934
935 # ---------------------------------------------------------------------------- #
936 # #
937 # Compiler section #
938 # #
939 # ---------------------------------------------------------------------------- #
940
941 # Current encoder state
942 #
943 g_encoder = None
944
945 # Reset encoder
946 #
947 def encoder_init( collection ):
948 #{
949 global g_encoder
950
951 g_encoder = \
952 {
953 # The actual file header
954 #
955 'header': mdl_header(),
956
957 # Options
958 #
959 'pack_textures': collection.cv_data.pack_textures,
960
961 # Compiled data chunks (each can be read optionally by the client)
962 #
963 'data':
964 {
965 #1---------------------------------
966 'node': [], # Metadata 'chunk'
967 'submesh': [],
968 'material': [],
969 'texture': [],
970 'anim': [],
971 'entdata': bytearray(), # variable width
972 'strings': bytearray(), # .
973 #2---------------------------------
974 'keyframe': [], # Animations
975 #3---------------------------------
976 'vertex': [], # Mesh data
977 'indice': [],
978 #4---------------------------------
979 'pack': bytearray() # Other generic packed data
980 },
981
982 # All objects of the model in their final heirachy
983 #
984 "uid_count": 1,
985 "scene_graph":{},
986 "graph_lookup":{},
987
988 # Allows us to reuse definitions
989 #
990 'string_cache':{},
991 'mesh_cache': {},
992 'material_cache': {},
993 'texture_cache': {}
994 }
995
996 g_encoder['header'].identifier = 0xABCD0000
997 g_encoder['header'].version = 1
998
999 # Add fake NoneID material and texture
1000 #
1001 none_material = mdl_material()
1002 none_material.pstr_name = encoder_process_pstr( "" )
1003 none_material.texture_id = 0
1004
1005 none_texture = mdl_texture()
1006 none_texture.pstr_name = encoder_process_pstr( "" )
1007 none_texture.pack_offset = 0
1008 none_texture.pack_length = 0
1009
1010 g_encoder['data']['material'] += [none_material]
1011 g_encoder['data']['texture'] += [none_texture]
1012
1013 g_encoder['data']['pack'].extend( b'datapack\0\0\0\0\0\0\0\0' )
1014
1015 # Add root node
1016 #
1017 root = mdl_node()
1018 root.co[0] = 0
1019 root.co[1] = 0
1020 root.co[2] = 0
1021 root.q[0] = 0
1022 root.q[1] = 0
1023 root.q[2] = 0
1024 root.q[3] = 1
1025 root.s[0] = 1
1026 root.s[1] = 1
1027 root.s[2] = 1
1028 root.pstr_name = encoder_process_pstr('')
1029 root.submesh_start = 0
1030 root.submesh_count = 0
1031 root.offset = 0
1032 root.classtype = 0
1033 root.parent = 0xffffffff
1034
1035 g_encoder['data']['node'] += [root]
1036 #}
1037
1038
1039 # fill with 0x00 until a multiple of align. Returns how many bytes it added
1040 #
1041 def bytearray_align_to( buffer, align, offset=0 ):
1042 #{
1043 count = 0
1044
1045 while ((len(buffer)+offset) % align) != 0:
1046 #{
1047 buffer.extend( b'\0' )
1048 count += 1
1049 #}
1050
1051 return count
1052 #}
1053
1054 # Add a string to the string buffer except if it already exists there then we
1055 # just return its ID.
1056 #
1057 def encoder_process_pstr( s ):
1058 #{
1059 global g_encoder
1060
1061 cache = g_encoder['string_cache']
1062
1063 if s in cache:
1064 return cache[s]
1065
1066 cache[s] = len( g_encoder['data']['strings'] )
1067
1068 buffer = g_encoder['data']['strings']
1069 buffer.extend( s.encode('utf-8') )
1070 buffer.extend( b'\0' )
1071
1072 bytearray_align_to( buffer, 4 )
1073 return cache[s]
1074 #}
1075
1076 def get_texture_resource_name( img ):
1077 #{
1078 return os.path.splitext( img.name )[0]
1079 #}
1080
1081 # Pack a texture
1082 #
1083 def encoder_process_texture( img ):
1084 #{
1085 global g_encoder
1086
1087 if img == None:
1088 return 0
1089
1090 cache = g_encoder['texture_cache']
1091 buffer = g_encoder['data']['texture']
1092 pack = g_encoder['data']['pack']
1093
1094 name = get_texture_resource_name( img )
1095
1096 if name in cache:
1097 return cache[name]
1098
1099 cache[name] = len( buffer )
1100
1101 tex = mdl_texture()
1102 tex.pstr_name = encoder_process_pstr( name )
1103
1104 if g_encoder['pack_textures']:
1105 #{
1106 tex.pack_offset = len( pack )
1107 pack.extend( qoi_encode( img ) )
1108 tex.pack_length = len( pack ) - tex.pack_offset
1109 #}
1110 else:
1111 tex.pack_offset = 0
1112
1113 buffer += [ tex ]
1114 return cache[name]
1115 #}
1116
1117 def material_tex_image(v):
1118 #{
1119 return {
1120 "Image Texture":
1121 {
1122 "image": F"{v}"
1123 }
1124 }
1125 #}
1126
1127 cxr_graph_mapping = \
1128 {
1129 # Default shader setup
1130 "Principled BSDF":
1131 {
1132 "Base Color":
1133 {
1134 "Image Texture":
1135 {
1136 "image": "tex_diffuse"
1137 },
1138 "Mix":
1139 {
1140 "A": material_tex_image("tex_diffuse"),
1141 "B": material_tex_image("tex_decal")
1142 },
1143 },
1144 "Normal":
1145 {
1146 "Normal Map":
1147 {
1148 "Color": material_tex_image("tex_normal")
1149 }
1150 }
1151 }
1152 }
1153
1154 # https://harrygodden.com/git/?p=convexer.git;a=blob;f=__init__.py;#l1164
1155 #
1156 def material_info(mat):
1157 #{
1158 info = {}
1159
1160 # Using the cv_graph_mapping as a reference, go through the shader
1161 # graph and gather all $props from it.
1162 #
1163 def _graph_read( node_def, node=None, depth=0 ):
1164 #{
1165 nonlocal mat
1166 nonlocal info
1167
1168 # Find rootnodes
1169 #
1170 if node == None:
1171 #{
1172 _graph_read.extracted = []
1173
1174 for node_idname in node_def:
1175 #{
1176 for n in mat.node_tree.nodes:
1177 #{
1178 if n.name == node_idname:
1179 #{
1180 node_def = node_def[node_idname]
1181 node = n
1182 break
1183 #}
1184 #}
1185 #}
1186 #}
1187
1188 for link in node_def:
1189 #{
1190 link_def = node_def[link]
1191
1192 if isinstance( link_def, dict ):
1193 #{
1194 node_link = None
1195 for x in node.inputs:
1196 #{
1197 if isinstance( x, bpy.types.NodeSocketColor ):
1198 #{
1199 if link == x.name:
1200 #{
1201 node_link = x
1202 break
1203 #}
1204 #}
1205 #}
1206
1207 if node_link and node_link.is_linked:
1208 #{
1209 # look for definitions for the connected node type
1210 #
1211 from_node = node_link.links[0].from_node
1212
1213 node_name = from_node.name.split('.')[0]
1214 if node_name in link_def:
1215 #{
1216 from_node_def = link_def[ node_name ]
1217
1218 _graph_read( from_node_def, from_node, depth+1 )
1219 #}
1220
1221 # No definition! :(
1222 # TODO: Make a warning for this?
1223 #}
1224 else:
1225 #{
1226 if "default" in link_def:
1227 #{
1228 prop = link_def['default']
1229 info[prop] = node_link.default_value
1230 #}
1231 #}
1232 #}
1233 else:
1234 #{
1235 prop = link_def
1236 info[prop] = getattr( node, link )
1237 #}
1238 #}
1239 #}
1240
1241 _graph_read( cxr_graph_mapping )
1242 return info
1243 #}
1244
1245 # Add a material to the material buffer. Returns 0 (None ID) if invalid
1246 #
1247 def encoder_process_material( mat ):
1248 #{
1249 global g_encoder
1250
1251 if mat == None:
1252 return 0
1253
1254 cache = g_encoder['material_cache']
1255 buffer = g_encoder['data']['material']
1256
1257 if mat.name in cache:
1258 return cache[mat.name]
1259
1260 cache[mat.name] = len( buffer )
1261
1262 dest = mdl_material()
1263 dest.pstr_name = encoder_process_pstr( mat.name )
1264
1265 flags = 0x00
1266 if mat.cv_data.collision:
1267 flags |= 0x2
1268 if mat.cv_data.skate_surface: flags |= 0x1
1269 if mat.cv_data.grind_surface: flags |= (0x8|0x1)
1270
1271 if mat.cv_data.grow_grass: flags |= 0x4
1272 dest.flags = flags
1273
1274 if mat.cv_data.surface_prop == 'concrete': dest.surface_prop = 0
1275 if mat.cv_data.surface_prop == 'wood': dest.surface_prop = 1
1276 if mat.cv_data.surface_prop == 'grass': dest.surface_prop = 2
1277 if mat.cv_data.surface_prop == 'tiles': dest.surface_prop = 3
1278
1279 if mat.cv_data.shader == 'standard': dest.shader = 0
1280 if mat.cv_data.shader == 'standard_cutout': dest.shader = 1
1281 if mat.cv_data.shader == 'terrain_blend':
1282 #{
1283 dest.shader = 2
1284
1285 dest.colour[0] = pow( mat.cv_data.sand_colour[0], 1.0/2.2 )
1286 dest.colour[1] = pow( mat.cv_data.sand_colour[1], 1.0/2.2 )
1287 dest.colour[2] = pow( mat.cv_data.sand_colour[2], 1.0/2.2 )
1288 dest.colour[3] = 1.0
1289
1290 dest.colour1[0] = mat.cv_data.blend_offset[0]
1291 dest.colour1[1] = mat.cv_data.blend_offset[1]
1292 #}
1293
1294 if mat.cv_data.shader == 'vertex_blend':
1295 #{
1296 dest.shader = 3
1297
1298 dest.colour1[0] = mat.cv_data.blend_offset[0]
1299 dest.colour1[1] = mat.cv_data.blend_offset[1]
1300 #}
1301
1302 if mat.cv_data.shader == 'water':
1303 #{
1304 dest.shader = 4
1305
1306 dest.colour[0] = pow( mat.cv_data.shore_colour[0], 1.0/2.2 )
1307 dest.colour[1] = pow( mat.cv_data.shore_colour[1], 1.0/2.2 )
1308 dest.colour[2] = pow( mat.cv_data.shore_colour[2], 1.0/2.2 )
1309 dest.colour[3] = 1.0
1310 dest.colour1[0] = pow( mat.cv_data.ocean_colour[0], 1.0/2.2 )
1311 dest.colour1[1] = pow( mat.cv_data.ocean_colour[1], 1.0/2.2 )
1312 dest.colour1[2] = pow( mat.cv_data.ocean_colour[2], 1.0/2.2 )
1313 dest.colour1[3] = 1.0
1314 #}
1315
1316 inf = material_info( mat )
1317
1318 if mat.cv_data.shader == 'standard' or \
1319 mat.cv_data.shader == 'standard_cutout' or \
1320 mat.cv_data.shader == 'terrain_blend' or \
1321 mat.cv_data.shader == 'vertex_blend':
1322 #{
1323 if 'tex_diffuse' in inf:
1324 dest.tex_diffuse = encoder_process_texture(inf['tex_diffuse'])
1325 #}
1326
1327 buffer += [dest]
1328 return cache[mat.name]
1329 #}
1330
1331 # Create a tree structure containing all the objects in the collection
1332 #
1333 def encoder_build_scene_graph( collection ):
1334 #{
1335 global g_encoder
1336
1337 print( " creating scene graph" )
1338
1339 # initialize root
1340 #
1341 graph = g_encoder['scene_graph']
1342 graph_lookup = g_encoder['graph_lookup']
1343 graph["obj"] = None
1344 graph["depth"] = 0
1345 graph["children"] = []
1346 graph["uid"] = 0
1347 graph["parent"] = None
1348
1349 def _new_uid():
1350 #{
1351 global g_encoder
1352 uid = g_encoder['uid_count']
1353 g_encoder['uid_count'] += 1
1354 return uid
1355 #}
1356
1357 for obj in collection.all_objects:
1358 #{
1359 #if obj.parent: continue
1360
1361 def _extend( p, n, d ):
1362 #{
1363 nonlocal collection
1364
1365 uid = _new_uid()
1366 tree = {}
1367 tree["uid"] = uid
1368 tree["children"] = []
1369 tree["depth"] = d
1370 tree["obj"] = n
1371 tree["parent"] = p
1372 n.cv_data.uid = uid
1373
1374 # Descend into amature
1375 #
1376 if n.type == 'ARMATURE':
1377 #{
1378 tree["bones"] = [None] # None is the root transform
1379 tree["ik_count"] = 0
1380 tree["collider_count"] = 0
1381 tree["compile_animation"] = collection.cv_data.animations
1382
1383 # Here also collects some information about constraints, ik and
1384 # counts colliders for the armature.
1385 #
1386 def _extendb( p, n, d ):
1387 #{
1388 nonlocal tree
1389
1390 btree = {}
1391 btree["bone"] = n
1392 btree["linked_armature"] = tree
1393 btree["uid"] = _new_uid()
1394 btree["children"] = []
1395 btree["depth"] = d
1396 btree["parent"] = p
1397 tree["bones"] += [n.name]
1398
1399 for c in n.children:
1400 #{
1401 _extendb( btree, c, d+1 )
1402 #}
1403
1404 for c in tree['obj'].pose.bones[n.name].constraints:
1405 #{
1406 if c.type == 'IK':
1407 #{
1408 btree["ik_target"] = c.subtarget
1409 btree["ik_pole"] = c.pole_subtarget
1410 tree["ik_count"] += 1
1411 #}
1412 #}
1413
1414 if n.cv_data.collider != 'collider_none':
1415 tree['collider_count'] += 1
1416
1417 btree['deform'] = n.use_deform
1418 p['children'] += [btree]
1419 #}
1420
1421 for b in n.data.bones:
1422 if not b.parent:
1423 _extendb( tree, b, d+1 )
1424 #}
1425
1426 # Recurse into children of this object
1427 #
1428 for obj1 in n.children:
1429 #{
1430 for c1 in obj1.users_collection:
1431 #{
1432 if c1 == collection:
1433 #{
1434 _extend( tree, obj1, d+1 )
1435 break
1436 #}
1437 #}
1438 #}
1439
1440 p["children"] += [tree]
1441 graph_lookup[n] = tree
1442
1443 #}
1444
1445 _extend( graph, obj, 1 )
1446
1447 #}
1448 #}
1449
1450
1451 # Kind of a useless thing i made but it looks cool and adds complexity!!1
1452 #
1453 def encoder_graph_iterator( root ):
1454 #{
1455 for c in root['children']:
1456 #{
1457 yield c
1458 yield from encoder_graph_iterator(c)
1459 #}
1460 #}
1461
1462
1463 # Push a vertex into the model file, or return a cached index (c_uint32)
1464 #
1465 def encoder_vertex_push( vertex_reference, co,norm,uv,colour,groups,weights ):
1466 #{
1467 global g_encoder
1468 buffer = g_encoder['data']['vertex']
1469
1470 TOLERENCE = 4
1471 m = float(10**TOLERENCE)
1472
1473 # Would be nice to know if this can be done faster than it currently runs,
1474 # its quite slow.
1475 #
1476 key = (int(co[0]*m+0.5),
1477 int(co[1]*m+0.5),
1478 int(co[2]*m+0.5),
1479 int(norm[0]*m+0.5),
1480 int(norm[1]*m+0.5),
1481 int(norm[2]*m+0.5),
1482 int(uv[0]*m+0.5),
1483 int(uv[1]*m+0.5),
1484 colour[0], # these guys are already quantized
1485 colour[1], # .
1486 colour[2], # .
1487 colour[3], # .
1488 weights[0], # v
1489 weights[1],
1490 weights[2],
1491 weights[3],
1492 groups[0],
1493 groups[1],
1494 groups[2],
1495 groups[3])
1496
1497 if key in vertex_reference:
1498 return vertex_reference[key]
1499 else:
1500 #{
1501 index = c_uint32( len(vertex_reference) )
1502 vertex_reference[key] = index
1503
1504 v = mdl_vert()
1505 v.co[0] = co[0]
1506 v.co[1] = co[2]
1507 v.co[2] = -co[1]
1508 v.norm[0] = norm[0]
1509 v.norm[1] = norm[2]
1510 v.norm[2] = -norm[1]
1511 v.uv[0] = uv[0]
1512 v.uv[1] = uv[1]
1513 v.colour[0] = colour[0]
1514 v.colour[1] = colour[1]
1515 v.colour[2] = colour[2]
1516 v.colour[3] = colour[3]
1517 v.weights[0] = weights[0]
1518 v.weights[1] = weights[1]
1519 v.weights[2] = weights[2]
1520 v.weights[3] = weights[3]
1521 v.groups[0] = groups[0]
1522 v.groups[1] = groups[1]
1523 v.groups[2] = groups[2]
1524 v.groups[3] = groups[3]
1525
1526 buffer += [v]
1527 return index
1528 #}
1529 #}
1530
1531
1532 # Compile a mesh (or use one from the cache) onto node, based on node_def
1533 # No return value
1534 #
1535 def encoder_compile_mesh( node, node_def ):
1536 #{
1537 global g_encoder
1538
1539 graph = g_encoder['scene_graph']
1540 graph_lookup = g_encoder['graph_lookup']
1541 mesh_cache = g_encoder['mesh_cache']
1542 obj = node_def['obj']
1543 armature_def = None
1544 can_use_cache = True
1545
1546 # Check for modifiers that typically change the data per-instance
1547 # there is no well defined rule for the choices here, its just what i've
1548 # needed while producing the game.
1549 #
1550 # It may be possible to detect these cases automatically.
1551 #
1552 for mod in obj.modifiers:
1553 #{
1554 if mod.type == 'DATA_TRANSFER' or mod.type == 'SHRINKWRAP' or \
1555 mod.type == 'BOOLEAN' or mod.type == 'CURVE' or \
1556 mod.type == 'ARRAY':
1557 #{
1558 can_use_cache = False
1559 #}
1560
1561 if mod.type == 'ARMATURE':
1562 armature_def = graph_lookup[mod.object]
1563
1564 # Check the cache first
1565 #
1566 if can_use_cache and (obj.data.name in mesh_cache):
1567 #{
1568 ref = mesh_cache[obj.data.name]
1569 node.submesh_start = ref.submesh_start
1570 node.submesh_count = ref.submesh_count
1571 return
1572 #}
1573
1574 # Compile a whole new mesh
1575 #
1576 node.submesh_start = len( g_encoder['data']['submesh'] )
1577 node.submesh_count = 0
1578
1579 dgraph = bpy.context.evaluated_depsgraph_get()
1580 data = obj.evaluated_get(dgraph).data
1581 data.calc_loop_triangles()
1582 data.calc_normals_split()
1583
1584 # Mesh is split into submeshes based on their material
1585 #
1586 mat_list = data.materials if len(data.materials) > 0 else [None]
1587 for material_id, mat in enumerate(mat_list):
1588 #{
1589 mref = {}
1590
1591 sm = mdl_submesh()
1592 sm.indice_start = len( g_encoder['data']['indice'] )
1593 sm.vertex_start = len( g_encoder['data']['vertex'] )
1594 sm.vertex_count = 0
1595 sm.indice_count = 0
1596 sm.material_id = encoder_process_material( mat )
1597
1598 for i in range(3):
1599 #{
1600 sm.bbx[0][i] = 999999
1601 sm.bbx[1][i] = -999999
1602 #}
1603
1604 # Keep a reference to very very very similar vertices
1605 #
1606 vertex_reference = {}
1607
1608 # Write the vertex / indice data
1609 #
1610 for tri_index, tri in enumerate(data.loop_triangles):
1611 #{
1612 if tri.material_index != material_id:
1613 continue
1614
1615 for j in range(3):
1616 #{
1617 vert = data.vertices[tri.vertices[j]]
1618 li = tri.loops[j]
1619 vi = data.loops[li].vertex_index
1620
1621 # Gather vertex information
1622 #
1623 co = vert.co
1624 norm = data.loops[li].normal
1625 uv = (0,0)
1626 colour = (255,255,255,255)
1627 groups = [0,0,0,0]
1628 weights = [0,0,0,0]
1629
1630 # Uvs
1631 #
1632 if data.uv_layers:
1633 uv = data.uv_layers.active.data[li].uv
1634
1635 # Vertex Colours
1636 #
1637 if data.vertex_colors:
1638 #{
1639 colour = data.vertex_colors.active.data[li].color
1640 colour = (int(colour[0]*255.0),\
1641 int(colour[1]*255.0),\
1642 int(colour[2]*255.0),\
1643 int(colour[3]*255.0))
1644 #}
1645
1646 # Weight groups: truncates to the 3 with the most influence. The
1647 # fourth bone ID is never used by the shader so it is
1648 # always 0
1649 #
1650 if armature_def:
1651 #{
1652 src_groups = [_ for _ in data.vertices[vi].groups \
1653 if obj.vertex_groups[_.group].name in \
1654 armature_def['bones']]
1655
1656 weight_groups = sorted( src_groups, key = \
1657 lambda a: a.weight, reverse=True )
1658 tot = 0.0
1659 for ml in range(3):
1660 #{
1661 if len(weight_groups) > ml:
1662 #{
1663 g = weight_groups[ml]
1664 name = obj.vertex_groups[g.group].name
1665 weight = g.weight
1666
1667 weights[ml] = weight
1668 groups[ml] = armature_def['bones'].index(name)
1669 tot += weight
1670 #}
1671 #}
1672
1673 if len(weight_groups) > 0:
1674 #{
1675 inv_norm = (1.0/tot) * 65535.0
1676 for ml in range(3):
1677 #{
1678 weights[ml] = int( weights[ml] * inv_norm )
1679 weights[ml] = min( weights[ml], 65535 )
1680 weights[ml] = max( weights[ml], 0 )
1681 #}
1682 #}
1683 #}
1684 else:
1685 #{
1686 li1 = tri.loops[(j+1)%3]
1687 vi1 = data.loops[li1].vertex_index
1688 e0 = data.edges[ data.loops[li].edge_index ]
1689
1690 if e0.use_freestyle_mark and \
1691 ((e0.vertices[0] == vi and e0.vertices[1] == vi1) or \
1692 (e0.vertices[0] == vi1 and e0.vertices[1] == vi)):
1693 #{
1694 weights[0] = 1
1695 #}
1696 #}
1697
1698 # Add vertex and expand bound box
1699 #
1700 index = encoder_vertex_push( vertex_reference, co, \
1701 norm, \
1702 uv, \
1703 colour, \
1704 groups, \
1705 weights )
1706 g_encoder['data']['indice'] += [index]
1707 #}
1708 #}
1709
1710 # How many unique verts did we add in total
1711 #
1712 sm.vertex_count = len(g_encoder['data']['vertex']) - sm.vertex_start
1713 sm.indice_count = len(g_encoder['data']['indice']) - sm.indice_start
1714
1715 # Make sure bounding box isn't -inf -> inf if no vertices
1716 #
1717 if sm.vertex_count == 0:
1718 for j in range(2):
1719 for i in range(3):
1720 sm.bbx[j][i] = 0
1721 else:
1722 #{
1723 for j in range(sm.vertex_count):
1724 #{
1725 vert = g_encoder['data']['vertex'][ sm.vertex_start + j ]
1726
1727 for i in range(3):
1728 #{
1729 sm.bbx[0][i] = min( sm.bbx[0][i], vert.co[i] )
1730 sm.bbx[1][i] = max( sm.bbx[1][i], vert.co[i] )
1731 #}
1732 #}
1733 #}
1734
1735 # Add submesh to encoder
1736 #
1737 g_encoder['data']['submesh'] += [sm]
1738 node.submesh_count += 1
1739
1740 #}
1741
1742 # Save a reference to this node since we want to reuse the submesh indices
1743 # later.
1744 g_encoder['mesh_cache'][obj.data.name] = node
1745 #}
1746
1747
1748 def encoder_compile_ent_as( name, node, node_def ):
1749 #{
1750 global g_encoder
1751
1752 if name == 'classtype_none':
1753 #{
1754 node.offset = 0
1755 node.classtype = 0
1756 return
1757 #}
1758 elif name not in globals():
1759 #{
1760 print( "Classtype '" +name + "' is unknown!" )
1761 return
1762 #}
1763
1764 buffer = g_encoder['data']['entdata']
1765 node.offset = len(buffer)
1766
1767 cl = globals()[ name ]
1768 inst = cl()
1769 inst.encode_obj( node, node_def )
1770
1771 buffer.extend( bytearray(inst) )
1772 bytearray_align_to( buffer, 4 )
1773 #}
1774
1775 # Compiles animation data into model and gives us some extra node_def entries
1776 #
1777 def encoder_compile_armature( node, node_def ):
1778 #{
1779 global g_encoder
1780
1781 entdata = g_encoder['data']['entdata']
1782 animdata = g_encoder['data']['anim']
1783 keyframedata = g_encoder['data']['keyframe']
1784 mesh_cache = g_encoder['mesh_cache']
1785 obj = node_def['obj']
1786 bones = node_def['bones']
1787
1788 # extra info
1789 node_def['anim_start'] = len(animdata)
1790 node_def['anim_count'] = 0
1791
1792 if not node_def['compile_animation']:
1793 #{
1794 return
1795 #}
1796
1797 # Compile anims
1798 #
1799 if obj.animation_data:
1800 #{
1801 # So we can restore later
1802 #
1803 previous_frame = bpy.context.scene.frame_current
1804 previous_action = obj.animation_data.action
1805 POSE_OR_REST_CACHE = obj.data.pose_position
1806 obj.data.pose_position = 'POSE'
1807
1808 for NLALayer in obj.animation_data.nla_tracks:
1809 #{
1810 for NLAStrip in NLALayer.strips:
1811 #{
1812 # set active
1813 #
1814 for a in bpy.data.actions:
1815 #{
1816 if a.name == NLAStrip.name:
1817 #{
1818 obj.animation_data.action = a
1819 break
1820 #}
1821 #}
1822
1823 # Clip to NLA settings
1824 #
1825 anim_start = int(NLAStrip.action_frame_start)
1826 anim_end = int(NLAStrip.action_frame_end)
1827
1828 # Export strips
1829 #
1830 anim = mdl_animation()
1831 anim.pstr_name = encoder_process_pstr( NLAStrip.action.name )
1832 anim.rate = 30.0
1833 anim.offset = len(keyframedata)
1834 anim.length = anim_end-anim_start
1835
1836 # Export the keyframes
1837 for frame in range(anim_start,anim_end):
1838 #{
1839 bpy.context.scene.frame_set(frame)
1840
1841 for bone_name in bones:
1842 #{
1843 for pb in obj.pose.bones:
1844 #{
1845 if pb.name != bone_name: continue
1846
1847 rb = obj.data.bones[ bone_name ]
1848
1849 # relative bone matrix
1850 if rb.parent is not None:
1851 #{
1852 offset_mtx = rb.parent.matrix_local
1853 offset_mtx = offset_mtx.inverted_safe() @ \
1854 rb.matrix_local
1855
1856 inv_parent = pb.parent.matrix @ offset_mtx
1857 inv_parent.invert_safe()
1858 fpm = inv_parent @ pb.matrix
1859 #}
1860 else:
1861 #{
1862 bone_mtx = rb.matrix.to_4x4()
1863 local_inv = rb.matrix_local.inverted_safe()
1864 fpm = bone_mtx @ local_inv @ pb.matrix
1865 #}
1866
1867 loc, rot, sca = fpm.decompose()
1868
1869 # local position
1870 final_pos = Vector(( loc[0], loc[2], -loc[1] ))
1871
1872 # rotation
1873 lc_m = pb.matrix_channel.to_3x3()
1874 if pb.parent is not None:
1875 #{
1876 smtx = pb.parent.matrix_channel.to_3x3()
1877 lc_m = smtx.inverted() @ lc_m
1878 #}
1879 rq = lc_m.to_quaternion()
1880
1881 kf = mdl_keyframe()
1882 kf.co[0] = final_pos[0]
1883 kf.co[1] = final_pos[1]
1884 kf.co[2] = final_pos[2]
1885
1886 kf.q[0] = rq[1]
1887 kf.q[1] = rq[3]
1888 kf.q[2] = -rq[2]
1889 kf.q[3] = rq[0]
1890
1891 # scale
1892 kf.s[0] = sca[0]
1893 kf.s[1] = sca[2]
1894 kf.s[2] = sca[1]
1895
1896 keyframedata += [kf]
1897 break
1898 #}
1899 #}
1900 #}
1901
1902 # Add to animation buffer
1903 #
1904 animdata += [anim]
1905 node_def['anim_count'] += 1
1906
1907 # Report progress
1908 #
1909 status_name = F" " + " |"*(node_def['depth']-1)
1910 print( F"{status_name} | *anim: {NLAStrip.action.name}" )
1911 #}
1912 #}
1913
1914 # Restore context to how it was before
1915 #
1916 bpy.context.scene.frame_set( previous_frame )
1917 obj.animation_data.action = previous_action
1918 obj.data.pose_position = POSE_OR_REST_CACHE
1919 #}
1920 #}
1921
1922 # We are trying to compile this node_def
1923 #
1924 def encoder_process_definition( node_def ):
1925 #{
1926 global g_encoder
1927
1928 # data sources for object/bone are taken differently
1929 #
1930 if 'obj' in node_def:
1931 #{
1932 obj = node_def['obj']
1933 obj_type = obj.type
1934 obj_co = obj.matrix_world @ Vector((0,0,0))
1935
1936 if obj_type == 'ARMATURE':
1937 obj_classtype = 'classtype_skeleton'
1938 elif obj_type == 'LIGHT':
1939 #{
1940 obj_classtype = 'classtype_world_light'
1941 #}
1942 else:
1943 #{
1944 obj_classtype = obj.cv_data.classtype
1945
1946 # Check for armature deform
1947 #
1948 for mod in obj.modifiers:
1949 #{
1950 if mod.type == 'ARMATURE':
1951 #{
1952 obj_classtype = 'classtype_skin'
1953
1954 # Make sure to freeze armature in rest while we collect
1955 # vertex information
1956 #
1957 armature_def = g_encoder['graph_lookup'][mod.object]
1958 POSE_OR_REST_CACHE = armature_def['obj'].data.pose_position
1959 armature_def['obj'].data.pose_position = 'REST'
1960 node_def['linked_armature'] = armature_def
1961 break
1962 #}
1963 #}
1964 #}
1965 #}
1966
1967 elif 'bone' in node_def:
1968 #{
1969 obj = node_def['bone']
1970 obj_type = 'BONE'
1971 obj_co = obj.head_local
1972 obj_classtype = 'classtype_bone'
1973 #}
1974
1975 # Create node
1976 #
1977 node = mdl_node()
1978 node.pstr_name = encoder_process_pstr( obj.name )
1979
1980 if node_def["parent"]:
1981 node.parent = node_def["parent"]["uid"]
1982
1983 # Setup transform
1984 #
1985 node.co[0] = obj_co[0]
1986 node.co[1] = obj_co[2]
1987 node.co[2] = -obj_co[1]
1988
1989 # Convert rotation quat to our space type
1990 #
1991 quat = obj.matrix_local.to_quaternion()
1992 node.q[0] = quat[1]
1993 node.q[1] = quat[3]
1994 node.q[2] = -quat[2]
1995 node.q[3] = quat[0]
1996
1997 # Bone scale is just a vector to the tail
1998 #
1999 if obj_type == 'BONE':
2000 #{
2001 node.s[0] = obj.tail_local[0] - node.co[0]
2002 node.s[1] = obj.tail_local[2] - node.co[1]
2003 node.s[2] = -obj.tail_local[1] - node.co[2]
2004 #}
2005 else:
2006 #{
2007 node.s[0] = obj.scale[0]
2008 node.s[1] = obj.scale[2]
2009 node.s[2] = obj.scale[1]
2010 #}
2011
2012 # Report status
2013 #
2014 tot_uid = g_encoder['uid_count']-1
2015 obj_uid = node_def['uid']
2016 obj_depth = node_def['depth']-1
2017
2018 status_id = F" [{obj_uid: 3}/{tot_uid}]" + " |"*obj_depth
2019 status_name = status_id + F" L {obj.name}"
2020
2021 if obj_classtype != 'classtype_none': status_type = obj_classtype
2022 else: status_type = obj_type
2023
2024 status_parent = F"{node.parent: 3}"
2025 status_armref = ""
2026
2027 if obj_classtype == 'classtype_skin':
2028 status_armref = F" [armature -> {armature_def['obj'].cv_data.uid}]"
2029
2030 print(F"{status_name:<32} {status_type:<22} {status_parent} {status_armref}")
2031
2032 # Process mesh if needed
2033 #
2034 if obj_type == 'MESH':
2035 #{
2036 encoder_compile_mesh( node, node_def )
2037 #}
2038 elif obj_type == 'ARMATURE':
2039 #{
2040 encoder_compile_armature( node, node_def )
2041 #}
2042
2043 encoder_compile_ent_as( obj_classtype, node, node_def )
2044
2045 # Make sure to reset the armature we just mucked about with
2046 #
2047 if obj_classtype == 'classtype_skin':
2048 armature_def['obj'].data.pose_position = POSE_OR_REST_CACHE
2049
2050 g_encoder['data']['node'] += [node]
2051 #}
2052
2053 # The post processing step or the pre processing to the writing step
2054 #
2055 def encoder_write_to_file( path ):
2056 #{
2057 global g_encoder
2058
2059 # Compile down to a byte array
2060 #
2061 header = g_encoder['header']
2062 file_pos = sizeof(header)
2063 file_data = bytearray()
2064 print( " Compositing data arrays" )
2065
2066 for array_name in g_encoder['data']:
2067 #{
2068 file_pos += bytearray_align_to( file_data, 16, sizeof(header) )
2069 arr = g_encoder['data'][array_name]
2070
2071 setattr( header, array_name + "_offset", file_pos )
2072
2073 print( F" {array_name:<16} @{file_pos:> 8X}[{len(arr)}]" )
2074
2075 if isinstance( arr, bytearray ):
2076 #{
2077 setattr( header, array_name + "_size", len(arr) )
2078
2079 file_data.extend( arr )
2080 file_pos += len(arr)
2081 #}
2082 else:
2083 #{
2084 setattr( header, array_name + "_count", len(arr) )
2085
2086 for item in arr:
2087 #{
2088 bbytes = bytearray(item)
2089 file_data.extend( bbytes )
2090 file_pos += sizeof(item)
2091 #}
2092 #}
2093 #}
2094
2095 # This imperitive for this field to be santized in the future!
2096 #
2097 header.file_length = file_pos
2098
2099 print( " Writing file" )
2100 # Write header and data chunk to file
2101 #
2102 fp = open( path, "wb" )
2103 fp.write( bytearray( header ) )
2104 fp.write( file_data )
2105 fp.close()
2106 #}
2107
2108 # Main compiler, uses string as the identifier for the collection
2109 #
2110 def write_model(collection_name):
2111 #{
2112 global g_encoder
2113 print( F"Model graph | Create mode '{collection_name}'" )
2114 folder = bpy.path.abspath(bpy.context.scene.cv_data.export_dir)
2115 path = F"{folder}{collection_name}.mdl"
2116 print( path )
2117
2118 collection = bpy.data.collections[collection_name]
2119
2120 encoder_init( collection )
2121 encoder_build_scene_graph( collection )
2122
2123 # Compile
2124 #
2125 print( " Comping objects" )
2126 it = encoder_graph_iterator( g_encoder['scene_graph'] )
2127 for node_def in it:
2128 encoder_process_definition( node_def )
2129
2130 # Write
2131 #
2132 encoder_write_to_file( path )
2133
2134 print( F"Completed {collection_name}.mdl" )
2135 #}
2136
2137 # ---------------------------------------------------------------------------- #
2138 # #
2139 # GUI section #
2140 # #
2141 # ---------------------------------------------------------------------------- #
2142
2143 cv_view_draw_handler = None
2144 cv_view_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR')
2145 cv_view_verts = []
2146 cv_view_colours = []
2147 cv_view_course_i = 0
2148
2149 # Draw axis alligned sphere at position with radius
2150 #
2151 def cv_draw_sphere( pos, radius, colour ):
2152 #{
2153 global cv_view_verts, cv_view_colours
2154
2155 ly = pos + Vector((0,0,radius))
2156 lx = pos + Vector((0,radius,0))
2157 lz = pos + Vector((0,0,radius))
2158
2159 pi = 3.14159265358979323846264
2160
2161 for i in range(16):
2162 #{
2163 t = ((i+1.0) * 1.0/16.0) * pi * 2.0
2164 s = math.sin(t)
2165 c = math.cos(t)
2166
2167 py = pos + Vector((s*radius,0.0,c*radius))
2168 px = pos + Vector((s*radius,c*radius,0.0))
2169 pz = pos + Vector((0.0,s*radius,c*radius))
2170
2171 cv_view_verts += [ px, lx ]
2172 cv_view_verts += [ py, ly ]
2173 cv_view_verts += [ pz, lz ]
2174
2175 cv_view_colours += [ colour, colour, colour, colour, colour, colour ]
2176
2177 ly = py
2178 lx = px
2179 lz = pz
2180 #}
2181 cv_draw_lines()
2182 #}
2183
2184 # Draw axis alligned sphere at position with radius
2185 #
2186 def cv_draw_halfsphere( pos, tx, ty, tz, radius, colour ):
2187 #{
2188 global cv_view_verts, cv_view_colours
2189
2190 ly = pos + tz*radius
2191 lx = pos + ty*radius
2192 lz = pos + tz*radius
2193
2194 pi = 3.14159265358979323846264
2195
2196 for i in range(16):
2197 #{
2198 t = ((i+1.0) * 1.0/16.0) * pi
2199 s = math.sin(t)
2200 c = math.cos(t)
2201
2202 s1 = math.sin(t*2.0)
2203 c1 = math.cos(t*2.0)
2204
2205 py = pos + s*tx*radius + c *tz*radius
2206 px = pos + s*tx*radius + c *ty*radius
2207 pz = pos + s1*ty*radius + c1*tz*radius
2208
2209 cv_view_verts += [ px, lx ]
2210 cv_view_verts += [ py, ly ]
2211 cv_view_verts += [ pz, lz ]
2212
2213 cv_view_colours += [ colour, colour, colour, colour, colour, colour ]
2214
2215 ly = py
2216 lx = px
2217 lz = pz
2218 #}
2219 cv_draw_lines()
2220 #}
2221
2222 # Draw transformed -1 -> 1 cube
2223 #
2224 def cv_draw_ucube( transform, colour ):
2225 #{
2226 global cv_view_verts, cv_view_colours
2227
2228 a = Vector((-1,-1,-1))
2229 b = Vector((1,1,1))
2230
2231 vs = [None]*8
2232 vs[0] = transform @ Vector((a[0], a[1], a[2]))
2233 vs[1] = transform @ Vector((a[0], b[1], a[2]))
2234 vs[2] = transform @ Vector((b[0], b[1], a[2]))
2235 vs[3] = transform @ Vector((b[0], a[1], a[2]))
2236 vs[4] = transform @ Vector((a[0], a[1], b[2]))
2237 vs[5] = transform @ Vector((a[0], b[1], b[2]))
2238 vs[6] = transform @ Vector((b[0], b[1], b[2]))
2239 vs[7] = transform @ Vector((b[0], a[1], b[2]))
2240
2241 indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
2242 (0,4),(1,5),(2,6),(3,7)]
2243
2244 for l in indices:
2245 #{
2246 v0 = vs[l[0]]
2247 v1 = vs[l[1]]
2248 cv_view_verts += [(v0[0],v0[1],v0[2])]
2249 cv_view_verts += [(v1[0],v1[1],v1[2])]
2250 cv_view_colours += [(0,1,0,1),(0,1,0,1)]
2251 #}
2252 cv_draw_lines()
2253 #}
2254
2255 # Draw line with colour
2256 #
2257 def cv_draw_line( p0, p1, colour ):
2258 #{
2259 global cv_view_verts, cv_view_colours
2260
2261 cv_view_verts += [p0,p1]
2262 cv_view_colours += [colour, colour]
2263 cv_draw_lines()
2264 #}
2265
2266 # Draw line with colour(s)
2267 #
2268 def cv_draw_line2( p0, p1, c0, c1 ):
2269 #{
2270 global cv_view_verts, cv_view_colours
2271
2272 cv_view_verts += [p0,p1]
2273 cv_view_colours += [c0,c1]
2274 cv_draw_lines()
2275 #}
2276
2277 #
2278 #
2279 def cv_tangent_basis( n, tx, ty ):
2280 #{
2281 if abs( n[0] ) >= 0.57735027:
2282 #{
2283 tx[0] = n[1]
2284 tx[1] = -n[0]
2285 tx[2] = 0.0
2286 #}
2287 else:
2288 #{
2289 tx[0] = 0.0
2290 tx[1] = n[2]
2291 tx[2] = -n[1]
2292 #}
2293
2294 tx.normalize()
2295 _ty = n.cross( tx )
2296
2297 ty[0] = _ty[0]
2298 ty[1] = _ty[1]
2299 ty[2] = _ty[2]
2300 #}
2301
2302 # Draw coloured arrow
2303 #
2304 def cv_draw_arrow( p0, p1, c0 ):
2305 #{
2306 global cv_view_verts, cv_view_colours
2307
2308 n = p1-p0
2309 midpt = p0 + n*0.5
2310 n.normalize()
2311
2312 tx = Vector((1,0,0))
2313 ty = Vector((1,0,0))
2314 cv_tangent_basis( n, tx, ty )
2315
2316 cv_view_verts += [p0,p1, midpt+(tx-n)*0.15,midpt, midpt+(-tx-n)*0.15,midpt ]
2317 cv_view_colours += [c0,c0,c0,c0,c0,c0]
2318 cv_draw_lines()
2319 #}
2320
2321 # Drawhandles of a bezier control point
2322 #
2323 def cv_draw_bhandle( obj, direction, colour ):
2324 #{
2325 global cv_view_verts, cv_view_colours
2326
2327 p0 = obj.location
2328 h0 = obj.matrix_world @ Vector((0,direction,0))
2329
2330 cv_view_verts += [p0]
2331 cv_view_verts += [h0]
2332 cv_view_colours += [colour,colour]
2333 cv_draw_lines()
2334 #}
2335
2336 # Draw a bezier curve (at fixed resolution 10)
2337 #
2338 def cv_draw_bezier( p0,h0,p1,h1,c0,c1 ):
2339 #{
2340 global cv_view_verts, cv_view_colours
2341
2342 last = p0
2343 for i in range(10):
2344 #{
2345 t = (i+1)/10
2346 a0 = 1-t
2347
2348 tt = t*t
2349 ttt = tt*t
2350 p=ttt*p1+(3*tt-3*ttt)*h1+(3*ttt-6*tt+3*t)*h0+(3*tt-ttt-3*t+1)*p0
2351
2352 cv_view_verts += [(last[0],last[1],last[2])]
2353 cv_view_verts += [(p[0],p[1],p[2])]
2354 cv_view_colours += [c0*a0+c1*(1-a0),c0*a0+c1*(1-a0)]
2355
2356 last = p
2357 #}
2358 cv_draw_lines()
2359 #}
2360
2361 # I think this one extends the handles of the bezier otwards......
2362 #
2363 def cv_draw_sbpath( o0,o1,c0,c1,s0,s1 ):
2364 #{
2365 global cv_view_course_i
2366
2367 offs = ((cv_view_course_i % 2)*2-1) * cv_view_course_i * 0.02
2368
2369 p0 = o0.matrix_world @ Vector((offs, 0,0))
2370 h0 = o0.matrix_world @ Vector((offs, s0,0))
2371 p1 = o1.matrix_world @ Vector((offs, 0,0))
2372 h1 = o1.matrix_world @ Vector((offs,-s1,0))
2373
2374 cv_draw_bezier( p0,h0,p1,h1,c0,c1 )
2375 cv_draw_lines()
2376 #}
2377
2378 # Flush the lines buffers. This is called often because god help you if you want
2379 # to do fixed, fast buffers in this catastrophic programming language.
2380 #
2381 def cv_draw_lines():
2382 #{
2383 global cv_view_shader, cv_view_verts, cv_view_colours
2384
2385 if len(cv_view_verts) < 2:
2386 return
2387
2388 lines = batch_for_shader(\
2389 cv_view_shader, 'LINES', \
2390 { "pos":cv_view_verts, "color":cv_view_colours })
2391
2392 lines.draw( cv_view_shader )
2393
2394 cv_view_verts = []
2395 cv_view_colours = []
2396 #}
2397
2398 # I dont remember what this does exactly
2399 #
2400 def cv_draw_bpath( o0,o1,c0,c1 ):
2401 #{
2402 cv_draw_sbpath( o0,o1,c0,c1,1.0,1.0 )
2403 #}
2404
2405 # Semi circle to show the limit. and some lines
2406 #
2407 def draw_limit( obj, center, major, minor, amin, amax, colour ):
2408 #{
2409 global cv_view_verts, cv_view_colours
2410 f = 0.05
2411 ay = major*f
2412 ax = minor*f
2413
2414 for x in range(16):
2415 #{
2416 t0 = x/16
2417 t1 = (x+1)/16
2418 a0 = amin*(1.0-t0)+amax*t0
2419 a1 = amin*(1.0-t1)+amax*t1
2420
2421 p0 = center + major*f*math.cos(a0) + minor*f*math.sin(a0)
2422 p1 = center + major*f*math.cos(a1) + minor*f*math.sin(a1)
2423
2424 p0=obj.matrix_world @ p0
2425 p1=obj.matrix_world @ p1
2426 cv_view_verts += [p0,p1]
2427 cv_view_colours += [colour,colour]
2428
2429 if x == 0:
2430 #{
2431 cv_view_verts += [p0,center]
2432 cv_view_colours += [colour,colour]
2433 #}
2434 if x == 15:
2435 #{
2436 cv_view_verts += [p1,center]
2437 cv_view_colours += [colour,colour]
2438 #}
2439 #}
2440
2441 cv_view_verts += [center+major*1.2*f,center+major*f*0.8]
2442 cv_view_colours += [colour,colour]
2443
2444 cv_draw_lines()
2445 #}
2446
2447 # Cone and twist limit
2448 #
2449 def draw_cone_twist( center, vx, vy, va ):
2450 #{
2451 global cv_view_verts, cv_view_colours
2452 axis = vy.cross( vx )
2453 axis.normalize()
2454
2455 size = 0.12
2456
2457 cv_view_verts += [center, center+va*size]
2458 cv_view_colours += [ (1,1,1,1), (1,1,1,1) ]
2459
2460 for x in range(32):
2461 #{
2462 t0 = (x/32) * math.tau
2463 t1 = ((x+1)/32) * math.tau
2464
2465 c0 = math.cos(t0)
2466 s0 = math.sin(t0)
2467 c1 = math.cos(t1)
2468 s1 = math.sin(t1)
2469
2470 p0 = center + (axis + vx*c0 + vy*s0).normalized() * size
2471 p1 = center + (axis + vx*c1 + vy*s1).normalized() * size
2472
2473 col0 = ( abs(c0), abs(s0), 0.0, 1.0 )
2474 col1 = ( abs(c1), abs(s1), 0.0, 1.0 )
2475
2476 cv_view_verts += [center, p0, p0, p1]
2477 cv_view_colours += [ (0,0,0,0), col0, col0, col1 ]
2478 #}
2479
2480 cv_draw_lines()
2481 #}
2482
2483 # Draws constraints and stuff for the skeleton. This isnt documented and wont be
2484 #
2485 def draw_skeleton_helpers( obj ):
2486 #{
2487 global cv_view_verts, cv_view_colours
2488
2489 if obj.data.pose_position != 'REST':
2490 #{
2491 return
2492 #}
2493
2494 for bone in obj.data.bones:
2495 #{
2496 c = bone.head_local
2497 a = Vector((bone.cv_data.v0[0], bone.cv_data.v0[1], bone.cv_data.v0[2]))
2498 b = Vector((bone.cv_data.v1[0], bone.cv_data.v1[1], bone.cv_data.v1[2]))
2499
2500 if bone.cv_data.collider == 'collider_box':
2501 #{
2502
2503 vs = [None]*8
2504 vs[0]=obj.matrix_world@Vector((c[0]+a[0],c[1]+a[1],c[2]+a[2]))
2505 vs[1]=obj.matrix_world@Vector((c[0]+a[0],c[1]+b[1],c[2]+a[2]))
2506 vs[2]=obj.matrix_world@Vector((c[0]+b[0],c[1]+b[1],c[2]+a[2]))
2507 vs[3]=obj.matrix_world@Vector((c[0]+b[0],c[1]+a[1],c[2]+a[2]))
2508 vs[4]=obj.matrix_world@Vector((c[0]+a[0],c[1]+a[1],c[2]+b[2]))
2509 vs[5]=obj.matrix_world@Vector((c[0]+a[0],c[1]+b[1],c[2]+b[2]))
2510 vs[6]=obj.matrix_world@Vector((c[0]+b[0],c[1]+b[1],c[2]+b[2]))
2511 vs[7]=obj.matrix_world@Vector((c[0]+b[0],c[1]+a[1],c[2]+b[2]))
2512
2513 indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
2514 (0,4),(1,5),(2,6),(3,7)]
2515
2516 for l in indices:
2517 #{
2518 v0 = vs[l[0]]
2519 v1 = vs[l[1]]
2520
2521 cv_view_verts += [(v0[0],v0[1],v0[2])]
2522 cv_view_verts += [(v1[0],v1[1],v1[2])]
2523 cv_view_colours += [(0.5,0.5,0.5,0.5),(0.5,0.5,0.5,0.5)]
2524 #}
2525 #}
2526 elif bone.cv_data.collider == 'collider_capsule':
2527 #{
2528 v0 = b-a
2529 major_axis = 0
2530 largest = -1.0
2531
2532 for i in range(3):
2533 #{
2534 if abs(v0[i]) > largest:
2535 #{
2536 largest = abs(v0[i])
2537 major_axis = i
2538 #}
2539 #}
2540
2541 v1 = Vector((0,0,0))
2542 v1[major_axis] = 1.0
2543
2544 tx = Vector((0,0,0))
2545 ty = Vector((0,0,0))
2546
2547 cv_tangent_basis( v1, tx, ty )
2548 r = (abs(tx.dot( v0 )) + abs(ty.dot( v0 ))) * 0.25
2549 l = v0[ major_axis ] - r*2
2550
2551 p0 = obj.matrix_world@Vector( c + (a+b)*0.5 + v1*l*-0.5 )
2552 p1 = obj.matrix_world@Vector( c + (a+b)*0.5 + v1*l* 0.5 )
2553
2554 colour = [0.2,0.2,0.2,1.0]
2555 colour[major_axis] = 0.5
2556
2557 cv_draw_halfsphere( p0, -v1, ty, tx, r, colour )
2558 cv_draw_halfsphere( p1, v1, ty, tx, r, colour )
2559 cv_draw_line( p0+tx* r, p1+tx* r, colour )
2560 cv_draw_line( p0+tx*-r, p1+tx*-r, colour )
2561 cv_draw_line( p0+ty* r, p1+ty* r, colour )
2562 cv_draw_line( p0+ty*-r, p1+ty*-r, colour )
2563 #}
2564 else:
2565 #{
2566 continue
2567 #}
2568
2569 center = obj.matrix_world @ c
2570 if bone.cv_data.con0:
2571 #{
2572 vx = Vector([bone.cv_data.conevx[_] for _ in range(3)])
2573 vy = Vector([bone.cv_data.conevy[_] for _ in range(3)])
2574 va = Vector([bone.cv_data.coneva[_] for _ in range(3)])
2575 draw_cone_twist( center, vx, vy, va )
2576
2577 #draw_limit( obj, c, Vector((0,0,1)),Vector((0,-1,0)), \
2578 # bone.cv_data.mins[0], bone.cv_data.maxs[0], \
2579 # (1,0,0,1))
2580 #draw_limit( obj, c, Vector((0,-1,0)),Vector((1,0,0)), \
2581 # bone.cv_data.mins[1], bone.cv_data.maxs[1], \
2582 # (0,1,0,1))
2583 #draw_limit( obj, c, Vector((1,0,0)),Vector((0,0,1)), \
2584 # bone.cv_data.mins[2], bone.cv_data.maxs[2], \
2585 # (0,0,1,1))
2586 #}
2587 #}
2588 #}
2589
2590 def cv_draw():
2591 #{
2592 global cv_view_shader
2593 global cv_view_verts
2594 global cv_view_colours
2595 global cv_view_course_i
2596
2597 cv_view_course_i = 0
2598 cv_view_verts = []
2599 cv_view_colours = []
2600
2601 cv_view_shader.bind()
2602 gpu.state.depth_mask_set(False)
2603 gpu.state.line_width_set(2.0)
2604 gpu.state.face_culling_set('BACK')
2605 gpu.state.depth_test_set('LESS')
2606 gpu.state.blend_set('NONE')
2607
2608 for obj in bpy.context.collection.objects:
2609 #{
2610 if obj.type == 'ARMATURE':
2611 #{
2612 if obj.data.pose_position == 'REST':
2613 draw_skeleton_helpers( obj )
2614 #}
2615 else:
2616 #{
2617 classtype = obj.cv_data.classtype
2618 if (classtype != 'classtype_none') and (classtype in globals()):
2619 #{
2620 cl = globals()[ classtype ]
2621
2622 if getattr( cl, "draw_scene_helpers", None ):
2623 #{
2624 cl.draw_scene_helpers( obj )
2625 #}
2626 #}
2627 #}
2628 #}
2629
2630 cv_draw_lines()
2631 return
2632 #}
2633
2634
2635 # ---------------------------------------------------------------------------- #
2636 # #
2637 # Blender #
2638 # #
2639 # ---------------------------------------------------------------------------- #
2640
2641 # Checks whether this object has a classtype assigned. we can only target other
2642 # classes
2643 def cv_poll_target(scene, obj):
2644 #{
2645 if obj == bpy.context.active_object:
2646 return False
2647 if obj.cv_data.classtype == 'classtype_none':
2648 return False
2649
2650 return True
2651 #}
2652
2653 class CV_MESH_SETTINGS(bpy.types.PropertyGroup):
2654 #{
2655 v0: bpy.props.FloatVectorProperty(name="v0",size=3)
2656 v1: bpy.props.FloatVectorProperty(name="v1",size=3)
2657 v2: bpy.props.FloatVectorProperty(name="v2",size=3)
2658 v3: bpy.props.FloatVectorProperty(name="v3",size=3)
2659 #}
2660
2661 class CV_LIGHT_SETTINGS(bpy.types.PropertyGroup):
2662 #{
2663 bp0: bpy.props.BoolProperty( name="bp0" );
2664 #}
2665
2666 class CV_LIGHT_PANEL(bpy.types.Panel):
2667 #{
2668 bl_label="[Skate Rift]"
2669 bl_idname="SCENE_PT_cv_light"
2670 bl_space_type='PROPERTIES'
2671 bl_region_type='WINDOW'
2672 bl_context='data'
2673
2674 def draw(_,context):
2675 #{
2676 active_object = context.active_object
2677 if active_object == None: return
2678
2679 if active_object.type != 'LIGHT': return
2680
2681 data = active_object.data.cv_data
2682 _.layout.prop( data, "bp0", text="Only on during night" )
2683 #}
2684 #}
2685
2686 class CV_OBJ_SETTINGS(bpy.types.PropertyGroup):
2687 #{
2688 uid: bpy.props.IntProperty( name="" )
2689
2690 strp: bpy.props.StringProperty( name="strp" )
2691 intp: bpy.props.IntProperty( name="intp" )
2692 fltp: bpy.props.FloatProperty( name="fltp" )
2693 bp0: bpy.props.BoolProperty( name="bp0" )
2694 bp1: bpy.props.BoolProperty( name="bp1" )
2695 bp2: bpy.props.BoolProperty( name="bp2" )
2696 bp3: bpy.props.BoolProperty( name="bp3" )
2697
2698 target: bpy.props.PointerProperty( type=bpy.types.Object, name="target", \
2699 poll=cv_poll_target )
2700 target1: bpy.props.PointerProperty( type=bpy.types.Object, name="target1", \
2701 poll=cv_poll_target )
2702 target2: bpy.props.PointerProperty( type=bpy.types.Object, name="target2", \
2703 poll=cv_poll_target )
2704 target3: bpy.props.PointerProperty( type=bpy.types.Object, name="target3", \
2705 poll=cv_poll_target )
2706
2707 colour: bpy.props.FloatVectorProperty( name="colour",subtype='COLOR',\
2708 min=0.0,max=1.0)
2709
2710 classtype: bpy.props.EnumProperty(
2711 name="Format",
2712 items = [
2713 ('classtype_none', "classtype_none", "", 0),
2714 ('classtype_gate', "classtype_gate", "", 1),
2715 ('classtype_spawn', "classtype_spawn", "", 3),
2716 ('classtype_water', "classtype_water", "", 4),
2717 ('classtype_route_node', "classtype_route_node", "", 8 ),
2718 ('classtype_route', "classtype_route", "", 9 ),
2719 ('classtype_audio',"classtype_audio","",14),
2720 ('classtype_trigger',"classtype_trigger","",100),
2721 ('classtype_logic_achievement',"classtype_logic_achievement","",101),
2722 ('classtype_logic_relay',"classtype_logic_relay","",102),
2723 ('classtype_spawn_link',"classtype_spawn_link","",150),
2724 ('classtype_nonlocal_gate', "classtype_nonlocal_gate", "", 300)
2725 ])
2726
2727 audio_format: bpy.props.EnumProperty(
2728 name="Loaded format",
2729 items = [
2730 ('mono', "mono", "", 0),
2731 ('stereo', "stereo", "", 1),
2732 ('remain compressed', "remain compressed", "", 2)
2733 ])
2734 #}
2735
2736 class CV_BONE_SETTINGS(bpy.types.PropertyGroup):
2737 #{
2738 collider: bpy.props.EnumProperty(
2739 name="Collider Type",
2740 items = [
2741 ('collider_none', "collider_none", "", 0),
2742 ('collider_box', "collider_box", "", 1),
2743 ('collider_capsule', "collider_capsule", "", 2),
2744 ])
2745
2746 v0: bpy.props.FloatVectorProperty(name="v0",size=3)
2747 v1: bpy.props.FloatVectorProperty(name="v1",size=3)
2748
2749 con0: bpy.props.BoolProperty(name="Constriant 0",default=False)
2750 mins: bpy.props.FloatVectorProperty(name="mins",size=3)
2751 maxs: bpy.props.FloatVectorProperty(name="maxs",size=3)
2752
2753 conevx: bpy.props.FloatVectorProperty(name="conevx",size=3)
2754 conevy: bpy.props.FloatVectorProperty(name="conevy",size=3)
2755 coneva: bpy.props.FloatVectorProperty(name="coneva",size=3)
2756 conet: bpy.props.FloatProperty(name="conet")
2757 #}
2758
2759 class CV_BONE_PANEL(bpy.types.Panel):
2760 #{
2761 bl_label="[Skate Rift]"
2762 bl_idname="SCENE_PT_cv_bone"
2763 bl_space_type='PROPERTIES'
2764 bl_region_type='WINDOW'
2765 bl_context='bone'
2766
2767 def draw(_,context):
2768 #{
2769 active_object = context.active_object
2770 if active_object == None: return
2771
2772 bone = active_object.data.bones.active
2773 if bone == None: return
2774
2775 _.layout.prop( bone.cv_data, "collider" )
2776 _.layout.prop( bone.cv_data, "v0" )
2777 _.layout.prop( bone.cv_data, "v1" )
2778
2779 _.layout.label( text="Angle Limits" )
2780 _.layout.prop( bone.cv_data, "con0" )
2781
2782 _.layout.prop( bone.cv_data, "conevx" )
2783 _.layout.prop( bone.cv_data, "conevy" )
2784 _.layout.prop( bone.cv_data, "coneva" )
2785 _.layout.prop( bone.cv_data, "conet" )
2786 #}
2787 #}
2788
2789 class CV_SCENE_SETTINGS(bpy.types.PropertyGroup):
2790 #{
2791 use_hidden: bpy.props.BoolProperty( name="use hidden", default=False )
2792 export_dir: bpy.props.StringProperty( name="Export Dir", subtype='DIR_PATH' )
2793 #}
2794
2795 class CV_COLLECTION_SETTINGS(bpy.types.PropertyGroup):
2796 #{
2797 pack_textures: bpy.props.BoolProperty( name="Pack Textures", default=False )
2798 animations: bpy.props.BoolProperty( name="Export animation", default=True)
2799 #}
2800
2801 class CV_MATERIAL_SETTINGS(bpy.types.PropertyGroup):
2802 #{
2803 shader: bpy.props.EnumProperty(
2804 name="Format",
2805 items = [
2806 ('standard',"standard","",0),
2807 ('standard_cutout', "standard_cutout", "", 1),
2808 ('terrain_blend', "terrain_blend", "", 2),
2809 ('vertex_blend', "vertex_blend", "", 3),
2810 ('water',"water","",4),
2811 ])
2812
2813 surface_prop: bpy.props.EnumProperty(
2814 name="Surface Property",
2815 items = [
2816 ('concrete','concrete','',0),
2817 ('wood','wood','',1),
2818 ('grass','grass','',2),
2819 ('tiles','tiles','',3)
2820 ])
2821
2822 collision: bpy.props.BoolProperty( \
2823 name="Collisions Enabled",\
2824 default=True,\
2825 description = "Can the player collide with this material"\
2826 )
2827 skate_surface: bpy.props.BoolProperty( \
2828 name="Skate Surface", \
2829 default=True,\
2830 description = "Should the game try to target this surface?" \
2831 )
2832 grind_surface: bpy.props.BoolProperty( \
2833 name="Grind Surface", \
2834 default=False,\
2835 description = "Grind face?" \
2836 )
2837 grow_grass: bpy.props.BoolProperty( \
2838 name="Grow Grass", \
2839 default=False,\
2840 description = "Spawn grass sprites on this surface?" \
2841 )
2842 blend_offset: bpy.props.FloatVectorProperty( \
2843 name="Blend Offset", \
2844 size=2, \
2845 default=Vector((0.5,0.0)),\
2846 description="When surface is more than 45 degrees, add this vector " +\
2847 "to the UVs" \
2848 )
2849 sand_colour: bpy.props.FloatVectorProperty( \
2850 name="Sand Colour",\
2851 subtype='COLOR',\
2852 min=0.0,max=1.0,\
2853 default=Vector((0.79,0.63,0.48)),\
2854 description="Blend to this colour near the 0 coordinate on UP axis"\
2855 )
2856 shore_colour: bpy.props.FloatVectorProperty( \
2857 name="Shore Colour",\
2858 subtype='COLOR',\
2859 min=0.0,max=1.0,\
2860 default=Vector((0.03,0.32,0.61)),\
2861 description="Water colour at the shoreline"\
2862 )
2863 ocean_colour: bpy.props.FloatVectorProperty( \
2864 name="Ocean Colour",\
2865 subtype='COLOR',\
2866 min=0.0,max=1.0,\
2867 default=Vector((0.0,0.006,0.03)),\
2868 description="Water colour in the deep bits"\
2869 )
2870 #}
2871
2872 class CV_MATERIAL_PANEL(bpy.types.Panel):
2873 #{
2874 bl_label="Skate Rift material"
2875 bl_idname="MATERIAL_PT_cv_material"
2876 bl_space_type='PROPERTIES'
2877 bl_region_type='WINDOW'
2878 bl_context="material"
2879
2880 def draw(_,context):
2881 #{
2882 active_object = bpy.context.active_object
2883 if active_object == None: return
2884 active_mat = active_object.active_material
2885 if active_mat == None: return
2886
2887 info = material_info( active_mat )
2888
2889 if 'tex_diffuse' in info:
2890 #{
2891 _.layout.label( icon='INFO', \
2892 text=F"{info['tex_diffuse'].name} will be compiled" )
2893 #}
2894
2895 _.layout.prop( active_mat.cv_data, "shader" )
2896 _.layout.prop( active_mat.cv_data, "surface_prop" )
2897 _.layout.prop( active_mat.cv_data, "collision" )
2898
2899 if active_mat.cv_data.collision:
2900 _.layout.prop( active_mat.cv_data, "skate_surface" )
2901 _.layout.prop( active_mat.cv_data, "grind_surface" )
2902 _.layout.prop( active_mat.cv_data, "grow_grass" )
2903
2904 if active_mat.cv_data.shader == "terrain_blend":
2905 #{
2906 box = _.layout.box()
2907 box.prop( active_mat.cv_data, "blend_offset" )
2908 box.prop( active_mat.cv_data, "sand_colour" )
2909 #}
2910 elif active_mat.cv_data.shader == "vertex_blend":
2911 #{
2912 box = _.layout.box()
2913 box.label( icon='INFO', text="Uses vertex colours, the R channel" )
2914 box.prop( active_mat.cv_data, "blend_offset" )
2915 #}
2916 elif active_mat.cv_data.shader == "water":
2917 #{
2918 box = _.layout.box()
2919 box.label( icon='INFO', text="Depth scale of 16 meters" )
2920 box.prop( active_mat.cv_data, "shore_colour" )
2921 box.prop( active_mat.cv_data, "ocean_colour" )
2922 #}
2923 #}
2924 #}
2925
2926 class CV_OBJ_PANEL(bpy.types.Panel):
2927 #{
2928 bl_label="Entity Config"
2929 bl_idname="SCENE_PT_cv_entity"
2930 bl_space_type='PROPERTIES'
2931 bl_region_type='WINDOW'
2932 bl_context="object"
2933
2934 def draw(_,context):
2935 #{
2936 active_object = bpy.context.active_object
2937 if active_object == None: return
2938 if active_object.type == 'ARMATURE':
2939 #{
2940 row = _.layout.row()
2941 row.enabled = False
2942 row.label( text="This object has the intrinsic classtype of skeleton" )
2943 return
2944 #}
2945
2946 _.layout.prop( active_object.cv_data, "classtype" )
2947
2948 classtype = active_object.cv_data.classtype
2949
2950 if (classtype != 'classtype_none') and (classtype in globals()):
2951 #{
2952 cl = globals()[ classtype ]
2953
2954 if getattr( cl, "editor_interface", None ):
2955 #{
2956 cl.editor_interface( _.layout, active_object )
2957 #}
2958 #}
2959 #}
2960 #}
2961
2962 class CV_COMPILE(bpy.types.Operator):
2963 #{
2964 bl_idname="carve.compile_all"
2965 bl_label="Compile All"
2966
2967 def execute(_,context):
2968 #{
2969 view_layer = bpy.context.view_layer
2970 for col in view_layer.layer_collection.children["export"].children:
2971 if not col.hide_viewport or bpy.context.scene.cv_data.use_hidden:
2972 write_model( col.name )
2973
2974 return {'FINISHED'}
2975 #}
2976 #}
2977
2978 class CV_COMPILE_THIS(bpy.types.Operator):
2979 #{
2980 bl_idname="carve.compile_this"
2981 bl_label="Compile This collection"
2982
2983 def execute(_,context):
2984 #{
2985 col = bpy.context.collection
2986 write_model( col.name )
2987
2988 return {'FINISHED'}
2989 #}
2990 #}
2991
2992 class CV_INTERFACE(bpy.types.Panel):
2993 #{
2994 bl_idname = "VIEW3D_PT_carve"
2995 bl_label = "Skate Rift"
2996 bl_space_type = 'VIEW_3D'
2997 bl_region_type = 'UI'
2998 bl_category = "Skate Rift"
2999
3000 def draw(_, context):
3001 #{
3002 layout = _.layout
3003 layout.prop( context.scene.cv_data, "export_dir" )
3004
3005 col = bpy.context.collection
3006
3007 found_in_export = False
3008 export_count = 0
3009 view_layer = bpy.context.view_layer
3010 for c1 in view_layer.layer_collection.children["export"].children:
3011 #{
3012 if not c1.hide_viewport or bpy.context.scene.cv_data.use_hidden:
3013 export_count += 1
3014
3015 if c1.name == col.name:
3016 #{
3017 found_in_export = True
3018 #}
3019 #}
3020
3021 box = layout.box()
3022 if found_in_export:
3023 #{
3024 box.label( text=col.name + ".mdl" )
3025 box.prop( col.cv_data, "pack_textures" )
3026 box.prop( col.cv_data, "animations" )
3027 box.operator( "carve.compile_this" )
3028 #}
3029 else:
3030 #{
3031 row = box.row()
3032 row.enabled=False
3033 row.label( text=col.name )
3034 box.label( text="This collection is not in the export group" )
3035 #}
3036
3037 box = layout.box()
3038 row = box.row()
3039
3040 split = row.split( factor = 0.3, align=True )
3041 split.prop( context.scene.cv_data, "use_hidden", text="hidden" )
3042
3043 row1 = split.row()
3044 if export_count == 0:
3045 row1.enabled=False
3046 row1.operator( "carve.compile_all", \
3047 text=F"Compile all ({export_count} collections)" )
3048 #}
3049 #}
3050
3051
3052 classes = [CV_OBJ_SETTINGS,CV_OBJ_PANEL,CV_COMPILE,CV_INTERFACE,\
3053 CV_MESH_SETTINGS, CV_SCENE_SETTINGS, CV_BONE_SETTINGS,\
3054 CV_BONE_PANEL, CV_COLLECTION_SETTINGS, CV_COMPILE_THIS,\
3055 CV_MATERIAL_SETTINGS, CV_MATERIAL_PANEL, CV_LIGHT_SETTINGS,\
3056 CV_LIGHT_PANEL]
3057
3058 def register():
3059 #{
3060 global cv_view_draw_handler
3061
3062 for c in classes:
3063 bpy.utils.register_class(c)
3064
3065 bpy.types.Object.cv_data = bpy.props.PointerProperty(type=CV_OBJ_SETTINGS)
3066 bpy.types.Mesh.cv_data = bpy.props.PointerProperty(type=CV_MESH_SETTINGS)
3067 bpy.types.Scene.cv_data = bpy.props.PointerProperty(type=CV_SCENE_SETTINGS)
3068 bpy.types.Bone.cv_data = bpy.props.PointerProperty(type=CV_BONE_SETTINGS)
3069 bpy.types.Collection.cv_data = \
3070 bpy.props.PointerProperty(type=CV_COLLECTION_SETTINGS)
3071 bpy.types.Material.cv_data = \
3072 bpy.props.PointerProperty(type=CV_MATERIAL_SETTINGS)
3073 bpy.types.Light.cv_data = bpy.props.PointerProperty(type=CV_LIGHT_SETTINGS)
3074
3075 cv_view_draw_handler = bpy.types.SpaceView3D.draw_handler_add(\
3076 cv_draw,(),'WINDOW','POST_VIEW')
3077 #}
3078
3079 def unregister():
3080 #{
3081 global cv_view_draw_handler
3082
3083 for c in classes:
3084 bpy.utils.unregister_class(c)
3085
3086 bpy.types.SpaceView3D.draw_handler_remove(cv_view_draw_handler,'WINDOW')
3087 #}
3088
3089 # ---------------------------------------------------------------------------- #
3090 # #
3091 # QOI encoder #
3092 # #
3093 # ---------------------------------------------------------------------------- #
3094 # #
3095 # Transliteration of: #
3096 # https://github.com/phoboslab/qoi/blob/master/qoi.h #
3097 # #
3098 # Copyright (c) 2021, Dominic Szablewski - https://phoboslab.org #
3099 # SPDX-License-Identifier: MIT #
3100 # QOI - The "Quite OK Image" format for fast, lossless image compression #
3101 # #
3102 # ---------------------------------------------------------------------------- #
3103
3104 class qoi_rgba_t(Structure):
3105 #{
3106 _pack_ = 1
3107 _fields_ = [("r",c_uint8),
3108 ("g",c_uint8),
3109 ("b",c_uint8),
3110 ("a",c_uint8)]
3111 #}
3112
3113 QOI_OP_INDEX = 0x00 # 00xxxxxx
3114 QOI_OP_DIFF = 0x40 # 01xxxxxx
3115 QOI_OP_LUMA = 0x80 # 10xxxxxx
3116 QOI_OP_RUN = 0xc0 # 11xxxxxx
3117 QOI_OP_RGB = 0xfe # 11111110
3118 QOI_OP_RGBA = 0xff # 11111111
3119
3120 QOI_MASK_2 = 0xc0 # 11000000
3121
3122 def qoi_colour_hash( c ):
3123 #{
3124 return c.r*3 + c.g*5 + c.b*7 + c.a*11
3125 #}
3126
3127 def qoi_eq( a, b ):
3128 #{
3129 return (a.r==b.r) and (a.g==b.g) and (a.b==b.b) and (a.a==b.a)
3130 #}
3131
3132 def qoi_32bit( v ):
3133 #{
3134 return bytearray([ (0xff000000 & v) >> 24, \
3135 (0x00ff0000 & v) >> 16, \
3136 (0x0000ff00 & v) >> 8, \
3137 (0x000000ff & v) ])
3138 #}
3139
3140 def qoi_encode( img ):
3141 #{
3142 data = bytearray()
3143
3144 print(F" . Encoding {img.name}.qoi[{img.size[0]},{img.size[1]}]")
3145
3146 index = [ qoi_rgba_t() for _ in range(64) ]
3147
3148 # Header
3149 #
3150 data.extend( bytearray(c_uint32(0x66696f71)) )
3151 data.extend( qoi_32bit( img.size[0] ) )
3152 data.extend( qoi_32bit( img.size[1] ) )
3153 data.extend( bytearray(c_uint8(4)) )
3154 data.extend( bytearray(c_uint8(0)) )
3155
3156 run = 0
3157 px_prev = qoi_rgba_t()
3158 px_prev.r = c_uint8(0)
3159 px_prev.g = c_uint8(0)
3160 px_prev.b = c_uint8(0)
3161 px_prev.a = c_uint8(255)
3162
3163 px = qoi_rgba_t()
3164 px.r = c_uint8(0)
3165 px.g = c_uint8(0)
3166 px.b = c_uint8(0)
3167 px.a = c_uint8(255)
3168
3169 px_len = img.size[0] * img.size[1]
3170
3171 paxels = [ int(min(max(_,0),1)*255) for _ in img.pixels ]
3172
3173 for px_pos in range( px_len ):
3174 #{
3175 idx = px_pos * img.channels
3176 nc = img.channels-1
3177
3178 px.r = paxels[idx+min(0,nc)]
3179 px.g = paxels[idx+min(1,nc)]
3180 px.b = paxels[idx+min(2,nc)]
3181 px.a = paxels[idx+min(3,nc)]
3182
3183 if qoi_eq( px, px_prev ):
3184 #{
3185 run += 1
3186
3187 if (run == 62) or (px_pos == px_len-1):
3188 #{
3189 data.extend( bytearray( c_uint8(QOI_OP_RUN | (run-1))) )
3190 run = 0
3191 #}
3192 #}
3193 else:
3194 #{
3195 if run > 0:
3196 #{
3197 data.extend( bytearray( c_uint8(QOI_OP_RUN | (run-1))) )
3198 run = 0
3199 #}
3200
3201 index_pos = qoi_colour_hash(px) % 64
3202
3203 if qoi_eq( index[index_pos], px ):
3204 #{
3205 data.extend( bytearray( c_uint8(QOI_OP_INDEX | index_pos)) )
3206 #}
3207 else:
3208 #{
3209 index[ index_pos ].r = px.r
3210 index[ index_pos ].g = px.g
3211 index[ index_pos ].b = px.b
3212 index[ index_pos ].a = px.a
3213
3214 if px.a == px_prev.a:
3215 #{
3216 vr = int(px.r) - int(px_prev.r)
3217 vg = int(px.g) - int(px_prev.g)
3218 vb = int(px.b) - int(px_prev.b)
3219
3220 vg_r = vr - vg
3221 vg_b = vb - vg
3222
3223 if (vr > -3) and (vr < 2) and\
3224 (vg > -3) and (vg < 2) and\
3225 (vb > -3) and (vb < 2):
3226 #{
3227 op = QOI_OP_DIFF | (vr+2) << 4 | (vg+2) << 2 | (vb+2)
3228 data.extend( bytearray( c_uint8(op) ))
3229 #}
3230 elif (vg_r > -9) and (vg_r < 8) and\
3231 (vg > -33) and (vg < 32 ) and\
3232 (vg_b > -9) and (vg_b < 8):
3233 #{
3234 op = QOI_OP_LUMA | (vg+32)
3235 delta = (vg_r+8) << 4 | (vg_b + 8)
3236 data.extend( bytearray( c_uint8(op) ) )
3237 data.extend( bytearray( c_uint8(delta) ))
3238 #}
3239 else:
3240 #{
3241 data.extend( bytearray( c_uint8(QOI_OP_RGB) ) )
3242 data.extend( bytearray( c_uint8(px.r) ))
3243 data.extend( bytearray( c_uint8(px.g) ))
3244 data.extend( bytearray( c_uint8(px.b) ))
3245 #}
3246 #}
3247 else:
3248 #{
3249 data.extend( bytearray( c_uint8(QOI_OP_RGBA) ) )
3250 data.extend( bytearray( c_uint8(px.r) ))
3251 data.extend( bytearray( c_uint8(px.g) ))
3252 data.extend( bytearray( c_uint8(px.b) ))
3253 data.extend( bytearray( c_uint8(px.a) ))
3254 #}
3255 #}
3256 #}
3257
3258 px_prev.r = px.r
3259 px_prev.g = px.g
3260 px_prev.b = px.b
3261 px_prev.a = px.a
3262 #}
3263
3264 # Padding
3265 for i in range(7):
3266 data.extend( bytearray( c_uint8(0) ))
3267 data.extend( bytearray( c_uint8(1) ))
3268 bytearray_align_to( data, 16, 0 )
3269
3270 return data
3271 #}