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