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