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