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