model fmt & heisenbug
[carveJwlIkooP6JGAAIwe30JlM.git] / blender_export.py
1 #
2 # Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
3 #
4
5 import bpy, math, gpu
6 import cProfile
7 from ctypes import *
8 from mathutils import *
9 from gpu_extras.batch import batch_for_shader
10
11 bl_info = {
12 "name":"Skate Rift model compiler",
13 "author": "Harry Godden (hgn)",
14 "version": (0,2),
15 "blender":(3,1,0),
16 "location":"Export",
17 "descriptin":"",
18 "warning":"",
19 "wiki_url":"",
20 "category":"Import/Export",
21 }
22
23 class mdl_vert(Structure): # 48 bytes. Quite large. Could compress
24 #{ # the normals and uvs to i16s. Not an
25 _pack_ = 1 # real issue, yet.
26 _fields_ = [("co",c_float*3),
27 ("norm",c_float*3),
28 ("uv",c_float*2),
29 ("colour",c_uint8*4),
30 ("weights",c_uint16*4),
31 ("groups",c_uint8*4)]
32 #}
33
34 class mdl_submesh(Structure):
35 #{
36 _pack_ = 1
37 _fields_ = [("indice_start",c_uint32),
38 ("indice_count",c_uint32),
39 ("vertex_start",c_uint32),
40 ("vertex_count",c_uint32),
41 ("bbx",(c_float*3)*2),
42 ("material_id",c_uint32)] # index into the material array
43 #}
44
45 class mdl_material(Structure):
46 #{
47 _pack_ = 1
48 _fields_ = [("pstr_name",c_uint32)]
49 #}
50
51 class mdl_node(Structure):
52 #{
53 _pack_ = 1
54 _fields_ = [("co",c_float*3),
55 ( "q",c_float*4),
56 ( "s",c_float*3),
57 ("sub_uid",c_uint32), # dont use
58 ("submesh_start",c_uint32),
59 ("submesh_count",c_uint32),
60 ("classtype",c_uint32),
61 ("offset",c_uint32),
62 ("parent",c_uint32),
63 ("pstr_name",c_uint32)]
64 #}
65
66 class mdl_header(Structure):
67 #{
68 _pack_ = 1
69 _fields_ = [("identifier",c_uint32),
70 ("version",c_uint32),
71 ("file_length",c_uint32),
72 ("pad0",c_uint32),
73
74 ("node_count",c_uint32),
75 ("node_offset",c_uint32),
76
77 ("submesh_count",c_uint32),
78 ("submesh_offset",c_uint32),
79
80 ("material_count",c_uint32),
81 ("material_offset",c_uint32),
82
83 ("anim_count",c_uint32),
84 ("anim_offset",c_uint32),
85
86 ("entdata_size",c_uint32),
87 ("entdata_offset",c_uint32),
88
89 ("strings_size",c_uint32),
90 ("strings_offset",c_uint32),
91
92 ("keyframe_count",c_uint32),
93 ("keyframe_offset",c_uint32),
94
95 ("vertex_count",c_uint32),
96 ("vertex_offset",c_uint32),
97
98 ("indice_count",c_uint32),
99 ("indice_offset",c_uint32),]
100 #}
101
102 class mdl_animation(Structure):
103 #{
104 _pack_ = 1
105 _fields_ = [("pstr_name",c_uint32),
106 ("length",c_uint32),
107 ("rate",c_float),
108 ("offset",c_uint32)]
109 #}
110
111 class mdl_keyframe(Structure):
112 #{
113 _pack_ = 1
114 _fields_ = [("co",c_float*3),
115 ("q",c_float*4),
116 ("s",c_float*3)]
117 #}
118
119 # Entity types
120 # ==========================================
121 #
122 # ctypes _fields_ defines the data which is filled in by:
123 # def encode_obj( _, node, node_def ):
124 #
125 # gizmos get drawn into the viewport via:
126 # @staticmethod
127 # def editor_interface( object ):
128 #
129
130 class classtype_gate(Structure):
131 #{
132 _pack_ = 1
133 _fields_ = [("target",c_uint32),
134 ("dims",c_float*3)]
135
136 def encode_obj(_, node,node_def):
137 #{
138 node.classtype = 1
139
140 obj = node_def['obj']
141
142 if obj.cv_data.target != None:
143 _.target = obj.cv_data.target.cv_data.uid
144
145 if obj.type == 'MESH':
146 #{
147 _.dims[0] = obj.data.cv_data.v0[0]
148 _.dims[1] = obj.data.cv_data.v0[1]
149 _.dims[2] = obj.data.cv_data.v0[2]
150 #}
151 else:
152 #{
153 _.dims[0] = obj.cv_data.v0[0]
154 _.dims[1] = obj.cv_data.v0[1]
155 _.dims[2] = obj.cv_data.v0[2]
156 #}
157 #}
158 #}
159
160 class classtype_spawn(Structure):
161 #{
162 _pack_ = 1
163 _fields_ = [("pstr_alias",c_uint32)]
164
165 def encode_obj(_, node,node_def):
166 #{
167 node.classtype = 3
168 _.pstr_alias = encoder_process_pstr( node_def['obj'].cv_data.strp )
169 #}
170 #}
171
172 class classtype_water(Structure):
173 #{
174 _pack_ = 1
175 _fields_ = [("temp",c_uint32)]
176
177 def encode_obj(_, node,node_def):
178 #{
179 node.classtype = 4
180 # no data, spooky
181 #}
182 #}
183
184 class classtype_route_node(Structure):
185 #{
186 _pack_ = 1
187 _fields_ = [("target",c_uint32),
188 ("target1",c_uint32)]
189
190 def encode_obj(_, node,node_def):
191 #{
192 node.classtype = 8
193 obj = node_def['obj']
194
195 if obj.cv_data.target != None:
196 _.target = obj.cv_data.target.cv_data.uid
197 if obj.cv_data.target1 != None:
198 _.target1 = obj.cv_data.target1.cv_data.uid
199 #}
200 #}
201
202 class classtype_route(Structure):
203 #{
204 _pack_ = 1
205 _fields_ = [("id_start",c_uint32),
206 ("colour",c_float*3)]
207
208 def encode_obj(_, node,node_def):
209 #{
210 node.classtype = 9
211 obj = node_def['obj']
212
213 _.colour[0] = obj.cv_data.colour[0]
214 _.colour[1] = obj.cv_data.colour[1]
215 _.colour[2] = obj.cv_data.colour[2]
216
217 if obj.cv_data.target != None:
218 _.id_start = obj.cv_data.target.cv_data.uid
219 #}
220 #}
221
222 class classtype_skin(Structure):
223 #{
224 _pack_ = 1
225 _fields_ = [("skeleton",c_uint32)]
226
227 def encode_obj(_, node,node_def):
228 #{
229 node.classtype = 12
230
231 armature_def = node_def['linked_armature']
232 _.skeleton = armature_def['obj'].cv_data.uid
233 #}
234 #}
235
236 class classtype_skeleton(Structure):
237 #{
238 _pack_ = 1
239 _fields_ = [("channels",c_uint32),
240 ("ik_count",c_uint32),
241 ("collider_count",c_uint32),
242 ("anim_start",c_uint32),
243 ("anim_count",c_uint32)]
244
245 def encode_obj(_, node,node_def):
246 #{
247 node.classtype = 11
248
249 _.channels = len( node_def['bones'] )
250 _.ik_count = node_def['ik_count']
251 _.collider_count = node_def['collider_count']
252 _.anim_start = node_def['anim_start']
253 _.anim_count = node_def['anim_count']
254 #}
255 #}
256
257 class classtype_bone(Structure):
258 #{
259 _pack_ = 1
260 _fields_ = [("deform",c_uint32),
261 ("ik_target",c_uint32),
262 ("ik_pole",c_uint32),
263 ("collider",c_uint32),
264 ("use_limits",c_uint32),
265 ("angle_limits",(c_float*3)*2),
266 ("hitbox",(c_float*3)*2)]
267
268 def encode_obj(_, node,node_def):
269 #{
270 node.classtype = 10
271
272 armature_def = node_def['linked_armature']
273 obj = node_def['bone']
274
275 _.deform = node_def['deform']
276
277 if 'ik_target' in node_def:
278 #{
279 _.ik_target = armature_def['bones'].index( node_def['ik_target'] )
280 _.ik_pole = armature_def['bones'].index( node_def['ik_pole'] )
281 #}
282
283 # For ragdolls
284 #
285 if obj.cv_data.collider:
286 #{
287 _.collider = 1
288 _.hitbox[0][0] = obj.cv_data.v0[0]
289 _.hitbox[0][1] = obj.cv_data.v0[2]
290 _.hitbox[0][2] = -obj.cv_data.v1[1]
291 _.hitbox[1][0] = obj.cv_data.v1[0]
292 _.hitbox[1][1] = obj.cv_data.v1[2]
293 _.hitbox[1][2] = -obj.cv_data.v0[1]
294 #}
295
296 if obj.cv_data.con0:
297 #{
298 _.use_limits = 1
299 _.angle_limits[0][0] = obj.cv_data.mins[0]
300 _.angle_limits[0][1] = obj.cv_data.mins[2]
301 _.angle_limits[0][2] = -obj.cv_data.maxs[1]
302 _.angle_limits[1][0] = obj.cv_data.maxs[0]
303 _.angle_limits[1][1] = obj.cv_data.maxs[2]
304 _.angle_limits[1][2] = -obj.cv_data.mins[1]
305 #}
306 #}
307 #}
308
309
310 # TO BE REPLACED
311 #
312 class classtype_achievement_box(Structure):
313 #{
314 _pack_ = 1
315 _fields_ = [("pstr_name",c_uint32),
316 ("trigger",c_uint32)]
317
318 def encode_obj(_, node,node_def ):
319 #{
320 node.classtype = 0
321 #}
322 #}
323
324 class classtype_audio(Structure):
325 #{
326 _pack_ = 1
327 _fields_ = [("pstr_file",c_uint32),
328 ("flags",c_uint32),
329 ("volume",c_float)]
330
331 def encode_obj(_, node,node_def ):
332 #{
333 node.classtype = 14
334
335 obj = node_def['obj']
336
337 _.pstr_file = encoder_process_pstr( obj.cv_data.strp )
338 _.flags = obj.cv_data.intp
339 _.volume = obj.cv_data.fltp
340 #}
341
342 @staticmethod
343 def editor_interface(yada):
344 #{
345 pass
346 #}
347
348 @staticmethod
349 def draw_scene_helpers(yada):
350 #{
351 pass
352 #}
353 #}
354
355
356 # Current encoder state
357 #
358 g_encoder = None
359
360
361 # Reset encoder
362 #
363 def encoder_init():
364 #{
365 global g_encoder
366
367 g_encoder = \
368 {
369 # The actual file header
370 #
371 'header': mdl_header(),
372
373 # Compiled data chunks (each can be read optionally by the client)
374 #
375 'data':
376 {
377 #1---------------------------------
378 'node': [], # Metadata 'chunk'
379 'submesh': [],
380 'material': [],
381 'anim': [],
382 'entdata': bytearray(), # variable width
383 'strings': bytearray(), # .
384 #2---------------------------------
385 'keyframe': [], # Animations
386 #3---------------------------------
387 'vertex': [], # Mesh data
388 'indice': [],
389 },
390
391 # All objects of the model in their final heirachy
392 #
393 "uid_count": 1,
394 "scene_graph":{},
395 "graph_lookup":{},
396
397 # Allows us to reuse definitions
398 #
399 'string_cache':{},
400 'mesh_cache': {},
401 'material_cache': {},
402 }
403
404 g_encoder['header'].identifier = 0xABCD0000
405 g_encoder['header'].version = 1
406
407 # Add fake NoneID material
408 #
409 none_material = c_uint32(1234)
410 none_material.name = ""
411 encoder_process_material( none_material )
412
413 # Add root node
414 #
415 root = mdl_node()
416 root.co[0] = 0
417 root.co[1] = 0
418 root.co[2] = 0
419 root.q[0] = 0
420 root.q[1] = 0
421 root.q[2] = 0
422 root.q[3] = 1
423 root.s[0] = 1
424 root.s[1] = 1
425 root.s[2] = 1
426 root.pstr_name = encoder_process_pstr('')
427 root.submesh_start = 0
428 root.submesh_count = 0
429 root.offset = 0
430 root.classtype = 0
431 root.parent = 0xffffffff
432
433 g_encoder['data']['node'] += [root]
434 #}
435
436
437 # fill with 0x00 until a multiple of align. Returns how many bytes it added
438 #
439 def bytearray_align_to( buffer, align, offset=0 ):
440 #{
441 count = 0
442
443 while ((len(buffer)+offset) % align) != 0:
444 #{
445 buffer.extend( b'\0' )
446 count += 1
447 #}
448
449 return count
450 #}
451
452 # Add a string to the string buffer except if it already exists there then we
453 # just return its ID.
454 #
455 def encoder_process_pstr( s ):
456 #{
457 global g_encoder
458
459 cache = g_encoder['string_cache']
460
461 if s in cache:
462 return cache[s]
463
464 cache[s] = len( g_encoder['data']['strings'] )
465
466 buffer = g_encoder['data']['strings']
467 buffer.extend( s.encode('utf-8') )
468 buffer.extend( b'\0' )
469
470 bytearray_align_to( buffer, 4 )
471 return cache[s]
472 #}
473
474 # Add a material to the material buffer. Returns 0 (None ID) if invalid
475 #
476 def encoder_process_material( mat ):
477 #{
478 global g_encoder
479
480 if mat == None:
481 return 0
482
483 cache = g_encoder['material_cache']
484 buffer = g_encoder['data']['material']
485
486 if mat.name in cache:
487 return cache[mat.name]
488
489 cache[mat.name] = len( buffer )
490
491 dest = mdl_material()
492 dest.pstr_name = encoder_process_pstr( mat.name )
493 buffer += [dest]
494
495 return cache[mat.name]
496 #}
497
498 # Create a tree structure containing all the objects in the collection
499 #
500 def encoder_build_scene_graph( collection ):
501 #{
502 global g_encoder
503
504 print( " creating scene graph" )
505
506 # initialize root
507 #
508 graph = g_encoder['scene_graph']
509 graph_lookup = g_encoder['graph_lookup']
510 graph["obj"] = None
511 graph["depth"] = 0
512 graph["children"] = []
513 graph["uid"] = 0
514 graph["parent"] = None
515
516 def _new_uid():
517 #{
518 global g_encoder
519 uid = g_encoder['uid_count']
520 g_encoder['uid_count'] += 1
521 return uid
522 #}
523
524 for obj in collection.all_objects:
525 #{
526 if obj.parent: continue
527
528 def _extend( p, n, d ):
529 #{
530 uid = _new_uid()
531 tree = {}
532 tree["uid"] = uid
533 tree["children"] = []
534 tree["depth"] = d
535 tree["obj"] = n
536 tree["parent"] = p
537 n.cv_data.uid = uid
538
539 # Descend into amature
540 #
541 if n.type == 'ARMATURE':
542 #{
543 tree["bones"] = [None] # None is the root transform
544 tree["ik_count"] = 0
545 tree["collider_count"] = 0
546
547 # Here also collects some information about constraints, ik and
548 # counts colliders for the armature.
549 #
550 def _extendb( p, n, d ):
551 #{
552 nonlocal tree
553
554 btree = {}
555 btree["bone"] = n
556 btree["linked_armature"] = tree
557 btree["uid"] = _new_uid()
558 btree["children"] = []
559 btree["depth"] = d
560 btree["parent"] = p
561 tree["bones"] += [n.name]
562
563 for c in n.children:
564 #{
565 _extendb( btree, c, d+1 )
566 #}
567
568 for c in tree['obj'].pose.bones[n.name].constraints:
569 #{
570 if c.type == 'IK':
571 #{
572 btree["ik_target"] = c.subtarget
573 btree["ik_pole"] = c.pole_subtarget
574 tree["ik_count"] += 1
575 #}
576 #}
577
578 if n.cv_data.collider:
579 tree['collider_count'] += 1
580
581 btree['deform'] = n.use_deform
582 p['children'] += [btree]
583 #}
584
585 for b in n.data.bones:
586 if not b.parent:
587 _extendb( tree, b, d+1 )
588 #}
589 #}
590
591 # Recurse into children of this object
592 #
593 for obj1 in n.children:
594 #{
595 nonlocal collection
596 for c1 in obj1.users_collection:
597 #{
598 if c1 == collection:
599 #{
600 _extend( tree, obj1, d+1 )
601 break
602 #}
603 #}
604 #}
605
606 p["children"] += [tree]
607 graph_lookup[n] = tree
608
609 #}
610
611 _extend( graph, obj, 1 )
612
613 #}
614 #}
615
616
617 # Kind of a useless thing i made but it looks cool and adds complexity!!1
618 #
619 def encoder_graph_iterator( root ):
620 #{
621 for c in root['children']:
622 #{
623 yield c
624 yield from encoder_graph_iterator(c)
625 #}
626 #}
627
628
629 # Push a vertex into the model file, or return a cached index (c_uint32)
630 #
631 def encoder_vertex_push( vertex_reference, co,norm,uv,colour,groups,weights ):
632 #{
633 global g_encoder
634 buffer = g_encoder['data']['vertex']
635
636 TOLERENCE = 4
637 m = float(10**TOLERENCE)
638
639 # Would be nice to know if this can be done faster than it currently runs,
640 # its quite slow.
641 #
642 key = (int(co[0]*m+0.5),
643 int(co[1]*m+0.5),
644 int(co[2]*m+0.5),
645 int(norm[0]*m+0.5),
646 int(norm[1]*m+0.5),
647 int(norm[2]*m+0.5),
648 int(uv[0]*m+0.5),
649 int(uv[1]*m+0.5),
650 colour[0]*m+0.5, # these guys are already quantized
651 colour[1]*m+0.5, # .
652 colour[2]*m+0.5, # .
653 colour[3]*m+0.5, # .
654 weights[0]*m+0.5, # v
655 weights[1]*m+0.5,
656 weights[2]*m+0.5,
657 weights[3]*m+0.5,
658 groups[0]*m+0.5,
659 groups[1]*m+0.5,
660 groups[2]*m+0.5,
661 groups[3]*m+0.5)
662
663 if key in vertex_reference:
664 return vertex_reference[key]
665 else:
666 #{
667 index = c_uint32( len(vertex_reference) )
668 vertex_reference[key] = index
669
670 v = mdl_vert()
671 v.co[0] = co[0]
672 v.co[1] = co[2]
673 v.co[2] = -co[1]
674 v.norm[0] = norm[0]
675 v.norm[1] = norm[2]
676 v.norm[2] = -norm[1]
677 v.uv[0] = uv[0]
678 v.uv[1] = uv[1]
679 v.colour[0] = colour[0]
680 v.colour[1] = colour[1]
681 v.colour[2] = colour[2]
682 v.colour[3] = colour[3]
683 v.weights[0] = weights[0]
684 v.weights[1] = weights[1]
685 v.weights[2] = weights[2]
686 v.weights[3] = weights[3]
687 v.groups[0] = groups[0]
688 v.groups[1] = groups[1]
689 v.groups[2] = groups[2]
690 v.groups[3] = groups[3]
691
692 buffer += [v]
693 return index
694 #}
695 #}
696
697
698 # Compile a mesh (or use one from the cache) onto node, based on node_def
699 # No return value
700 #
701 def encoder_compile_mesh( node, node_def ):
702 #{
703 global g_encoder
704
705 graph = g_encoder['scene_graph']
706 graph_lookup = g_encoder['graph_lookup']
707 mesh_cache = g_encoder['mesh_cache']
708 obj = node_def['obj']
709 armature_def = None
710 can_use_cache = True
711
712 # Check for modifiers that typically change the data per-instance
713 # there is no well defined rule for the choices here, its just what i've
714 # needed while producing the game.
715 #
716 # It may be possible to detect these cases automatically.
717 #
718 for mod in obj.modifiers:
719 #{
720 if mod.type == 'DATA_TRANSFER' or mod.type == 'SHRINKWRAP' or \
721 mod.type == 'BOOLEAN' or mod.type == 'CURVE' or \
722 mod.type == 'ARRAY':
723 #{
724 can_use_cache = False
725 #}
726
727 if mod.type == 'ARMATURE':
728 armature_def = graph_lookup[mod.object]
729
730 # Check the cache first
731 #
732 if can_use_cache and (obj.data.name in mesh_cache):
733 #{
734 ref = mesh_cache[obj.data.name]
735 node.submesh_start = ref.submesh_start
736 node.submesh_count = ref.submesh_count
737 return
738 #}
739
740 # Compile a whole new mesh
741 #
742 node.submesh_start = len( g_encoder['data']['submesh'] )
743 node.submesh_count = 0
744
745 default_mat = c_uint32(12345)
746 default_mat.name = ""
747
748 dgraph = bpy.context.evaluated_depsgraph_get()
749 data = obj.evaluated_get(dgraph).data
750 data.calc_loop_triangles()
751 data.calc_normals_split()
752
753 # Mesh is split into submeshes based on their material
754 #
755 mat_list = data.materials if len(data.materials) > 0 else [default_mat]
756 for material_id, mat in enumerate(mat_list):
757 #{
758 mref = {}
759
760 sm = mdl_submesh()
761 sm.indice_start = len( g_encoder['data']['indice'] )
762 sm.vertex_start = len( g_encoder['data']['vertex'] )
763 sm.vertex_count = 0
764 sm.indice_count = 0
765 sm.material_id = encoder_process_material( mat )
766
767 for i in range(3):
768 #{
769 sm.bbx[0][i] = 999999
770 sm.bbx[1][i] = -999999
771 #}
772
773 # Keep a reference to very very very similar vertices
774 #
775 vertex_reference = {}
776
777 # Write the vertex / indice data
778 #
779 for tri_index, tri in enumerate(data.loop_triangles):
780 #{
781 if tri.material_index != material_id:
782 continue
783
784 for j in range(3):
785 #{
786 vert = data.vertices[tri.vertices[j]]
787 li = tri.loops[j]
788 vi = data.loops[li].vertex_index
789
790 # Gather vertex information
791 #
792 co = vert.co
793 norm = data.loops[li].normal
794 uv = (0,0)
795 colour = (255,255,255,255)
796 groups = [0,0,0,0]
797 weights = [0,0,0,0]
798
799 # Uvs
800 #
801 if data.uv_layers:
802 uv = data.uv_layers.active.data[li].uv
803
804 # Vertex Colours
805 #
806 if data.vertex_colors:
807 #{
808 colour = data.vertex_colors.active.data[li].color
809 colour = (int(colour[0]*255.0),\
810 int(colour[1]*255.0),\
811 int(colour[2]*255.0),\
812 int(colour[3]*255.0))
813 #}
814
815 # Weight groups: truncates to the 3 with the most influence. The
816 # fourth bone ID is never used by the shader so it is
817 # always 0
818 #
819 if armature_def:
820 #{
821 src_groups = [_ for _ in data.vertices[vi].groups \
822 if obj.vertex_groups[_.group].name in \
823 armature_def['bones']]
824
825 weight_groups = sorted( src_groups, key = \
826 lambda a: a.weight, reverse=True )
827 tot = 0.0
828 for ml in range(3):
829 #{
830 if len(weight_groups) > ml:
831 #{
832 g = weight_groups[ml]
833 name = obj.vertex_groups[g.group].name
834 weight = g.weight
835
836 weights[ml] = weight
837 groups[ml] = armature_def['bones'].index(name)
838 tot += weight
839 #}
840 #}
841
842 if len(weight_groups) > 0:
843 #{
844 inv_norm = (1.0/tot) * 65535.0
845 for ml in range(3):
846 #{
847 weights[ml] = int( weights[ml] * inv_norm )
848 weights[ml] = min( weights[ml], 65535 )
849 weights[ml] = max( weights[ml], 0 )
850 #}
851 #}
852
853 # Add vertex and expand bound box
854 #
855 index = encoder_vertex_push( vertex_reference, co, \
856 norm, \
857 uv, \
858 colour, \
859 groups, \
860 weights )
861 g_encoder['data']['indice'] += [index]
862 #}
863 #}
864
865 # How many unique verts did we add in total
866 #
867 sm.vertex_count = len(g_encoder['data']['vertex']) - sm.vertex_start
868 sm.indice_count = len(g_encoder['data']['indice']) - sm.indice_start
869
870 # Make sure bounding box isn't -inf -> inf if no vertices
871 #
872 if sm.vertex_count == 0:
873 for j in range(2):
874 for i in range(3):
875 sm.bbx[j][i] = 0
876 else:
877 #{
878 for j in range(sm.vertex_count):
879 #{
880 vert = g_encoder['data']['vertex'][ sm.vertex_start + j ]
881
882 for i in range(3):
883 #{
884 sm.bbx[0][i] = min( sm.bbx[0][i], vert.co[i] )
885 sm.bbx[1][i] = max( sm.bbx[1][i], vert.co[i] )
886 #}
887 #}
888 #}
889
890 # Add submesh to encoder
891 #
892 g_encoder['data']['submesh'] += [sm]
893 node.submesh_count += 1
894
895 #}
896
897 # Save a reference to this node since we want to reuse the submesh indices
898 # later.
899 g_encoder['mesh_cache'][obj.data.name] = node
900 #}
901
902
903 def encoder_compile_ent_as( name, node, node_def ):
904 #{
905 global g_encoder
906
907 if name == 'classtype_none':
908 #{
909 node.offset = 0
910 node.classtype = 0
911 return
912 #}
913 elif name not in globals():
914 #{
915 print( "Classtype '" +name + "' is unknown!" )
916 return
917 #}
918
919 buffer = g_encoder['data']['entdata']
920 node.offset = len(buffer)
921
922 cl = globals()[ name ]
923 inst = cl()
924 inst.encode_obj( node, node_def )
925
926 buffer.extend( bytearray(inst) )
927 bytearray_align_to( buffer, 4 )
928 #}
929
930 # Compiles animation data into model and gives us some extra node_def entries
931 #
932 def encoder_compile_armature( node, node_def ):
933 #{
934 global g_encoder
935
936 entdata = g_encoder['data']['entdata']
937 animdata = g_encoder['data']['anim']
938 keyframedata = g_encoder['data']['keyframe']
939 mesh_cache = g_encoder['mesh_cache']
940 obj = node_def['obj']
941 bones = node_def['bones']
942
943 # extra info
944 node_def['anim_start'] = len(animdata)
945 node_def['anim_count'] = 0
946
947 # Compile anims
948 #
949 if obj.animation_data:
950 #{
951 # So we can restore later
952 #
953 previous_frame = bpy.context.scene.frame_current
954 previous_action = obj.animation_data.action
955 POSE_OR_REST_CACHE = obj.data.pose_position
956 obj.data.pose_position = 'POSE'
957
958 for NLALayer in obj.animation_data.nla_tracks:
959 #{
960 for NLAStrip in NLALayer.strips:
961 #{
962 # set active
963 #
964 for a in bpy.data.actions:
965 #{
966 if a.name == NLAStrip.name:
967 #{
968 obj.animation_data.action = a
969 break
970 #}
971 #}
972
973 # Clip to NLA settings
974 #
975 anim_start = int(NLAStrip.action_frame_start)
976 anim_end = int(NLAStrip.action_frame_end)
977
978 # Export strips
979 #
980 anim = mdl_animation()
981 anim.pstr_name = encoder_process_pstr( NLAStrip.action.name )
982 anim.rate = 30.0
983 anim.offset = len(keyframedata)
984 anim.length = anim_end-anim_start
985
986 # Export the keyframes
987 for frame in range(anim_start,anim_end):
988 #{
989 bpy.context.scene.frame_set(frame)
990
991 for bone_name in bones:
992 #{
993 for pb in obj.pose.bones:
994 #{
995 if pb.name != bone_name: continue
996
997 rb = obj.data.bones[ bone_name ]
998
999 # relative bone matrix
1000 if rb.parent is not None:
1001 #{
1002 offset_mtx = rb.parent.matrix_local
1003 offset_mtx = offset_mtx.inverted_safe() @ \
1004 rb.matrix_local
1005
1006 inv_parent = pb.parent.matrix @ offset_mtx
1007 inv_parent.invert_safe()
1008 fpm = inv_parent @ pb.matrix
1009 #}
1010 else:
1011 #{
1012 bone_mtx = rb.matrix.to_4x4()
1013 local_inv = rb.matrix_local.inverted_safe()
1014 fpm = bone_mtx @ local_inv @ pb.matrix
1015 #}
1016
1017 loc, rot, sca = fpm.decompose()
1018
1019 # local position
1020 final_pos = Vector(( loc[0], loc[2], -loc[1] ))
1021
1022 # rotation
1023 lc_m = pb.matrix_channel.to_3x3()
1024 if pb.parent is not None:
1025 #{
1026 smtx = pb.parent.matrix_channel.to_3x3()
1027 lc_m = smtx.inverted() @ lc_m
1028 #}
1029 rq = lc_m.to_quaternion()
1030
1031 kf = mdl_keyframe()
1032 kf.co[0] = final_pos[0]
1033 kf.co[1] = final_pos[1]
1034 kf.co[2] = final_pos[2]
1035
1036 kf.q[0] = rq[1]
1037 kf.q[1] = rq[3]
1038 kf.q[2] = -rq[2]
1039 kf.q[3] = rq[0]
1040
1041 # scale
1042 kf.s[0] = sca[0]
1043 kf.s[1] = sca[2]
1044 kf.s[2] = sca[1]
1045
1046 keyframedata += [kf]
1047 break
1048 #}
1049 #}
1050 #}
1051
1052 # Add to animation buffer
1053 #
1054 animdata += [anim]
1055 node_def['anim_count'] += 1
1056
1057 # Report progress
1058 #
1059 status_name = F" " + " |"*(node_def['depth']-1)
1060 print( F"{status_name} | *anim: {NLAStrip.action.name}" )
1061 #}
1062 #}
1063
1064 # Restore context to how it was before
1065 #
1066 bpy.context.scene.frame_set( previous_frame )
1067 obj.animation_data.action = previous_action
1068 obj.data.pose_position = POSE_OR_REST_CACHE
1069 #}
1070 #}
1071
1072 # We are trying to compile this node_def
1073 #
1074 def encoder_process_definition( node_def ):
1075 #{
1076 global g_encoder
1077
1078 # data sources for object/bone are taken differently
1079 #
1080 if 'obj' in node_def:
1081 #{
1082 obj = node_def['obj']
1083 obj_type = obj.type
1084 obj_co = obj.location
1085
1086 if obj_type == 'ARMATURE':
1087 obj_classtype = 'classtype_skeleton'
1088 else:
1089 #{
1090 obj_classtype = obj.cv_data.classtype
1091
1092 # Check for armature deform
1093 #
1094 for mod in obj.modifiers:
1095 #{
1096 if mod.type == 'ARMATURE':
1097 #{
1098 obj_classtype = 'classtype_skin'
1099
1100 # Make sure to freeze armature in rest while we collect
1101 # vertex information
1102 #
1103 armature_def = g_encoder['graph_lookup'][mod.object]
1104 POSE_OR_REST_CACHE = armature_def['obj'].data.pose_position
1105 armature_def['obj'].data.pose_position = 'REST'
1106 node_def['linked_armature'] = armature_def
1107 break
1108 #}
1109 #}
1110 #}
1111 #}
1112
1113 elif 'bone' in node_def:
1114 #{
1115 obj = node_def['bone']
1116 obj_type = 'BONE'
1117 obj_co = obj.head_local
1118 obj_classtype = 'classtype_bone'
1119 #}
1120
1121 # Create node
1122 #
1123 node = mdl_node()
1124 node.pstr_name = encoder_process_pstr( obj.name )
1125
1126 if node_def["parent"]:
1127 node.parent = node_def["parent"]["uid"]
1128
1129 # Setup transform
1130 #
1131 node.co[0] = obj_co[0]
1132 node.co[1] = obj_co[2]
1133 node.co[2] = -obj_co[1]
1134
1135 # Convert rotation quat to our space type
1136 #
1137 quat = obj.matrix_local.to_quaternion()
1138 node.q[0] = quat[1]
1139 node.q[1] = quat[3]
1140 node.q[2] = -quat[2]
1141 node.q[3] = quat[0]
1142
1143 # Bone scale is just a vector to the tail
1144 #
1145 if obj_type == 'BONE':
1146 #{
1147 node.s[0] = obj.tail_local[0] - node.co[0]
1148 node.s[1] = obj.tail_local[2] - node.co[1]
1149 node.s[2] = -obj.tail_local[1] - node.co[2]
1150 #}
1151 else:
1152 #{
1153 node.s[0] = obj.scale[0]
1154 node.s[1] = obj.scale[2]
1155 node.s[2] = obj.scale[1]
1156 #}
1157
1158 # Report status
1159 #
1160 tot_uid = g_encoder['uid_count']-1
1161 obj_uid = node_def['uid']
1162 obj_depth = node_def['depth']-1
1163
1164 status_id = F" [{obj_uid: 3}/{tot_uid}]" + " |"*obj_depth
1165 status_name = status_id + F" L {obj.name}"
1166
1167 if obj_classtype != 'classtype_none': status_type = obj_classtype
1168 else: status_type = obj_type
1169
1170 status_parent = F"{node.parent: 3}"
1171 status_armref = ""
1172
1173 if obj_classtype == 'classtype_skin':
1174 status_armref = F" [armature -> {armature_def['obj'].cv_data.uid}]"
1175
1176 print(F"{status_name:<32} {status_type:<22} {status_parent} {status_armref}")
1177
1178 # Process mesh if needed
1179 #
1180 if obj_type == 'MESH':
1181 #{
1182 encoder_compile_mesh( node, node_def )
1183 #}
1184 elif obj_type == 'ARMATURE':
1185 #{
1186 encoder_compile_armature( node, node_def )
1187 #}
1188
1189 encoder_compile_ent_as( obj_classtype, node, node_def )
1190
1191 # Make sure to reset the armature we just mucked about with
1192 #
1193 if obj_classtype == 'classtype_skin':
1194 armature_def['obj'].data.pose_position = POSE_OR_REST_CACHE
1195
1196 g_encoder['data']['node'] += [node]
1197 #}
1198
1199 # The post processing step or the pre processing to the writing step
1200 #
1201 def encoder_write_to_file( path ):
1202 #{
1203 global g_encoder
1204
1205 # Compile down to a byte array
1206 #
1207 header = g_encoder['header']
1208 file_pos = sizeof(header)
1209 file_data = bytearray()
1210 print( " Compositing data arrays" )
1211
1212 for array_name in g_encoder['data']:
1213 #{
1214 file_pos += bytearray_align_to( file_data, 16, sizeof(header) )
1215 arr = g_encoder['data'][array_name]
1216
1217 setattr( header, array_name + "_offset", file_pos )
1218
1219 print( F" {array_name:<16} @{file_pos:> 8X}[{len(arr)}]" )
1220
1221 if isinstance( arr, bytearray ):
1222 #{
1223 setattr( header, array_name + "_size", len(arr) )
1224
1225 file_data.extend( arr )
1226 file_pos += len(arr)
1227 #}
1228 else:
1229 #{
1230 setattr( header, array_name + "_count", len(arr) )
1231
1232 for item in arr:
1233 #{
1234 bbytes = bytearray(item)
1235 file_data.extend( bbytes )
1236 file_pos += sizeof(item)
1237 #}
1238 #}
1239 #}
1240
1241 # This imperitive for this field to be santized in the future!
1242 #
1243 header.file_length = file_pos
1244
1245 print( " Writing file" )
1246 # Write header and data chunk to file
1247 #
1248 fp = open( path, "wb" )
1249 fp.write( bytearray( header ) )
1250 fp.write( file_data )
1251 fp.close()
1252 #}
1253
1254 # Main compiler, uses string as the identifier for the collection
1255 #
1256 def write_model(collection_name):
1257 #{
1258 global g_encoder
1259 print( F"Model graph | Create mode '{collection_name}'" )
1260
1261 collection = bpy.data.collections[collection_name]
1262
1263 encoder_init()
1264 encoder_build_scene_graph( collection )
1265
1266 # Compile
1267 #
1268 print( " Comping objects" )
1269 it = encoder_graph_iterator( g_encoder['scene_graph'] )
1270 for node_def in it:
1271 encoder_process_definition( node_def )
1272
1273 # Write
1274 #
1275 # TODO HOLY
1276 path = F"/home/harry/Documents/carve/models_src/{collection_name}.mdl"
1277 encoder_write_to_file( path )
1278
1279 print( F"Completed {collection_name}.mdl" )
1280 #}
1281
1282
1283 # Clicky clicky GUI
1284 # ------------------------------------------------------------------------------
1285
1286 cv_view_draw_handler = None
1287 cv_view_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR')
1288
1289 def cv_draw():
1290 global cv_view_shader
1291 cv_view_shader.bind()
1292 gpu.state.depth_mask_set(False)
1293 gpu.state.line_width_set(2.0)
1294 gpu.state.face_culling_set('BACK')
1295 gpu.state.depth_test_set('LESS')
1296 gpu.state.blend_set('NONE')
1297
1298 verts = []
1299 colours = []
1300
1301 #def drawbezier(p0,h0,p1,h1,c0,c1):
1302 # nonlocal verts, colours
1303
1304 # verts += [p0]
1305 # verts += [h0]
1306 # colours += [(0.5,0.5,0.5,1.0),(0.5,0.5,0.5,1)]
1307 # verts += [p1]
1308 # verts += [h1]
1309 # colours += [(1.0,1.0,1,1),(1,1,1,1)]
1310 #
1311 # last = p0
1312 # for i in range(10):
1313 # t = (i+1)/10
1314 # a0 = 1-t
1315
1316 # tt = t*t
1317 # ttt = tt*t
1318 # p=ttt*p1+(3*tt-3*ttt)*h1+(3*ttt-6*tt+3*t)*h0+(3*tt-ttt-3*t+1)*p0
1319 # verts += [(last[0],last[1],last[2])]
1320 # verts += [(p[0],p[1],p[2])]
1321 # colours += [c0*a0+c1*(1-a0),c0*a0+c1*(1-a0)]
1322 # last = p
1323
1324 course_count = 0
1325
1326 def drawbhandle(obj, direction, colour):
1327 nonlocal verts, colours
1328 p0 = obj.location
1329 h0 = obj.matrix_world @ Vector((0,direction,0))
1330 verts += [p0]
1331 verts += [h0]
1332 colours += [colour,colour]
1333
1334 def drawbezier(p0,h0,p1,h1,c0,c1):
1335 nonlocal verts, colours
1336
1337 last = p0
1338 for i in range(10):
1339 t = (i+1)/10
1340 a0 = 1-t
1341
1342 tt = t*t
1343 ttt = tt*t
1344 p=ttt*p1+(3*tt-3*ttt)*h1+(3*ttt-6*tt+3*t)*h0+(3*tt-ttt-3*t+1)*p0
1345 verts += [(last[0],last[1],last[2])]
1346 verts += [(p[0],p[1],p[2])]
1347 colours += [c0*a0+c1*(1-a0),c0*a0+c1*(1-a0)]
1348 last = p
1349
1350 def drawsbpath(o0,o1,c0,c1,s0,s1):
1351 nonlocal course_count
1352
1353 offs = ((course_count % 2)*2-1) * course_count * 0.02
1354
1355 p0 = o0.matrix_world @ Vector((offs, 0,0))
1356 h0 = o0.matrix_world @ Vector((offs, s0,0))
1357 p1 = o1.matrix_world @ Vector((offs, 0,0))
1358 h1 = o1.matrix_world @ Vector((offs,-s1,0))
1359 drawbezier(p0,h0,p1,h1,c0,c1)
1360
1361 def drawbpath(o0,o1,c0,c1):
1362 drawsbpath(o0,o1,c0,c1,1.0,1.0)
1363
1364 def drawbline(p0,p1,c0,c1):
1365 nonlocal verts, colours
1366 verts += [p0,p1]
1367 colours += [c0,c1]
1368
1369 for obj in bpy.context.collection.objects:
1370 if obj.type == 'ARMATURE':
1371 for bone in obj.data.bones:
1372 if bone.cv_data.collider and obj.data.pose_position == 'REST':
1373 c = bone.head_local
1374 a = bone.cv_data.v0
1375 b = bone.cv_data.v1
1376
1377 vs = [None]*8
1378 vs[0]=obj.matrix_world@Vector((c[0]+a[0],c[1]+a[1],c[2]+a[2]))
1379 vs[1]=obj.matrix_world@Vector((c[0]+a[0],c[1]+b[1],c[2]+a[2]))
1380 vs[2]=obj.matrix_world@Vector((c[0]+b[0],c[1]+b[1],c[2]+a[2]))
1381 vs[3]=obj.matrix_world@Vector((c[0]+b[0],c[1]+a[1],c[2]+a[2]))
1382 vs[4]=obj.matrix_world@Vector((c[0]+a[0],c[1]+a[1],c[2]+b[2]))
1383 vs[5]=obj.matrix_world@Vector((c[0]+a[0],c[1]+b[1],c[2]+b[2]))
1384 vs[6]=obj.matrix_world@Vector((c[0]+b[0],c[1]+b[1],c[2]+b[2]))
1385 vs[7]=obj.matrix_world@Vector((c[0]+b[0],c[1]+a[1],c[2]+b[2]))
1386
1387 indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
1388 (0,4),(1,5),(2,6),(3,7)]
1389
1390 for l in indices:
1391 v0 = vs[l[0]]
1392 v1 = vs[l[1]]
1393 verts += [(v0[0],v0[1],v0[2])]
1394 verts += [(v1[0],v1[1],v1[2])]
1395 colours += [(0.5,0.5,0.5,0.5),(0.5,0.5,0.5,0.5)]
1396
1397 center=obj.matrix_world@c
1398
1399 def _angle_lim( major, minor, amin, amax, colour ):
1400 nonlocal verts, colours
1401 f = 0.05
1402 ay = major*f
1403 ax = minor*f
1404
1405 for x in range(16):
1406 t0 = x/16
1407 t1 = (x+1)/16
1408 a0 = amin*(1.0-t0)+amax*t0
1409 a1 = amin*(1.0-t1)+amax*t1
1410
1411 p0 = c + major*f*math.cos(a0) + minor*f*math.sin(a0)
1412 p1 = c + major*f*math.cos(a1) + minor*f*math.sin(a1)
1413
1414 p0=obj.matrix_world @ p0
1415 p1=obj.matrix_world @ p1
1416 verts += [p0,p1]
1417 colours += [colour,colour]
1418
1419 if x == 0:
1420 verts += [p0,c]
1421 colours += [colour,colour]
1422 if x == 15:
1423 verts += [p1,c]
1424 colours += [colour,colour]
1425
1426 verts += [c+major*1.2*f,c+major*f*0.8]
1427 colours += [colour,colour]
1428
1429 if bone.cv_data.con0:
1430 _angle_lim( Vector((0,1,0)),Vector((0,0,1)), \
1431 bone.cv_data.mins[0], bone.cv_data.maxs[0], \
1432 (1,0,0,1))
1433 _angle_lim( Vector((0,0,1)),Vector((1,0,0)), \
1434 bone.cv_data.mins[1], bone.cv_data.maxs[1], \
1435 (0,1,0,1))
1436 _angle_lim( Vector((1,0,0)),Vector((0,1,0)), \
1437 bone.cv_data.mins[2], bone.cv_data.maxs[2], \
1438 (0,0,1,1))
1439
1440
1441 if obj.cv_data.classtype == 'classtype_gate':
1442 if obj.type == 'MESH':
1443 dims = obj.data.cv_data.v0
1444 else:
1445 dims = obj.cv_data.v0
1446
1447 vs = [None]*9
1448 c = Vector((0,0,dims[2]))
1449
1450 vs[0] = obj.matrix_world @ Vector((-dims[0],0.0,-dims[1]+dims[2]))
1451 vs[1] = obj.matrix_world @ Vector((-dims[0],0.0, dims[1]+dims[2]))
1452 vs[2] = obj.matrix_world @ Vector(( dims[0],0.0, dims[1]+dims[2]))
1453 vs[3] = obj.matrix_world @ Vector(( dims[0],0.0,-dims[1]+dims[2]))
1454 vs[4] = obj.matrix_world @ (c+Vector((-1,0,-2)))
1455 vs[5] = obj.matrix_world @ (c+Vector((-1,0, 2)))
1456 vs[6] = obj.matrix_world @ (c+Vector(( 1,0, 2)))
1457 vs[7] = obj.matrix_world @ (c+Vector((-1,0, 0)))
1458 vs[8] = obj.matrix_world @ (c+Vector(( 1,0, 0)))
1459
1460 indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(7,8)]
1461
1462 for l in indices:
1463 v0 = vs[l[0]]
1464 v1 = vs[l[1]]
1465 verts += [(v0[0],v0[1],v0[2])]
1466 verts += [(v1[0],v1[1],v1[2])]
1467 colours += [(1,1,0,1),(1,1,0,1)]
1468
1469 sw = (0.4,0.4,0.4,0.2)
1470 if obj.cv_data.target != None:
1471 drawbline( obj.location, obj.cv_data.target.location, sw,sw )
1472
1473 elif obj.cv_data.classtype == 'classtype_route_node':
1474 sw = Vector((0.4,0.4,0.4,0.2))
1475 sw2 = Vector((1.5,0.2,0.2,0.0))
1476 if obj.cv_data.target != None:
1477 drawbpath( obj, obj.cv_data.target, sw, sw )
1478 if obj.cv_data.target1 != None:
1479 drawbpath( obj, obj.cv_data.target1, sw, sw )
1480
1481 drawbhandle( obj, 1.0, (0.8,0.8,0.8,1.0) )
1482 drawbhandle( obj, -1.0, (0.4,0.4,0.4,1.0) )
1483
1484 p1 = obj.location+ \
1485 obj.matrix_world.to_quaternion() @ Vector((0,0,-6+1.5))
1486 drawbline( obj.location, p1, sw,sw2 )
1487
1488 elif obj.cv_data.classtype == 'classtype_achievement_box':
1489 a = Vector((-1,-1,-1))
1490 b = Vector((1,1,1))
1491
1492 vs = [None]*8
1493 vs[0] = obj.matrix_world @ Vector((a[0], a[1], a[2]))
1494 vs[1] = obj.matrix_world @ Vector((a[0], b[1], a[2]))
1495 vs[2] = obj.matrix_world @ Vector((b[0], b[1], a[2]))
1496 vs[3] = obj.matrix_world @ Vector((b[0], a[1], a[2]))
1497 vs[4] = obj.matrix_world @ Vector((a[0], a[1], b[2]))
1498 vs[5] = obj.matrix_world @ Vector((a[0], b[1], b[2]))
1499 vs[6] = obj.matrix_world @ Vector((b[0], b[1], b[2]))
1500 vs[7] = obj.matrix_world @ Vector((b[0], a[1], b[2]))
1501
1502 indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
1503 (0,4),(1,5),(2,6),(3,7)]
1504
1505 for l in indices:
1506 v0 = vs[l[0]]
1507 v1 = vs[l[1]]
1508 verts += [(v0[0],v0[1],v0[2])]
1509 verts += [(v1[0],v1[1],v1[2])]
1510 colours += [(0,1,0,1),(0,1,0,1)]
1511
1512 if obj.cv_data.target != None:
1513 vs = [None]*2
1514 vs[0] = obj.location
1515 vs[1] = obj.cv_data.target.location
1516 indices = [(0,1)]
1517 for l in indices:
1518 v0 = vs[l[0]]
1519 v1 = vs[l[1]]
1520 verts += [(v0[0],v0[1],v0[2])]
1521 verts += [(v1[0],v1[1],v1[2])]
1522 colours += [(0,1,1,1),(0,1,1,1)]
1523
1524
1525 elif obj.cv_data.classtype == 'classtype_block':
1526 a = obj.data.cv_data.v0
1527 b = obj.data.cv_data.v1
1528
1529 vs = [None]*8
1530 vs[0] = obj.matrix_world @ Vector((a[0], a[1], a[2]))
1531 vs[1] = obj.matrix_world @ Vector((a[0], b[1], a[2]))
1532 vs[2] = obj.matrix_world @ Vector((b[0], b[1], a[2]))
1533 vs[3] = obj.matrix_world @ Vector((b[0], a[1], a[2]))
1534 vs[4] = obj.matrix_world @ Vector((a[0], a[1], b[2]))
1535 vs[5] = obj.matrix_world @ Vector((a[0], b[1], b[2]))
1536 vs[6] = obj.matrix_world @ Vector((b[0], b[1], b[2]))
1537 vs[7] = obj.matrix_world @ Vector((b[0], a[1], b[2]))
1538
1539 indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
1540 (0,4),(1,5),(2,6),(3,7)]
1541
1542 for l in indices:
1543 v0 = vs[l[0]]
1544 v1 = vs[l[1]]
1545 verts += [(v0[0],v0[1],v0[2])]
1546 verts += [(v1[0],v1[1],v1[2])]
1547 colours += [(1,1,0,1),(1,1,0,1)]
1548
1549 elif obj.cv_data.classtype == 'classtype_capsule':
1550 h = obj.data.cv_data.v0[0]
1551 r = obj.data.cv_data.v0[1]
1552
1553 vs = [None]*10
1554 vs[0] = obj.matrix_world @ Vector((0.0,0.0, h*0.5 ))
1555 vs[1] = obj.matrix_world @ Vector((0.0,0.0,-h*0.5 ))
1556 vs[2] = obj.matrix_world @ Vector(( r,0.0, h*0.5-r))
1557 vs[3] = obj.matrix_world @ Vector(( -r,0.0, h*0.5-r))
1558 vs[4] = obj.matrix_world @ Vector(( r,0.0,-h*0.5+r))
1559 vs[5] = obj.matrix_world @ Vector(( -r,0.0,-h*0.5+r))
1560 vs[6] = obj.matrix_world @ Vector((0.0, r , h*0.5-r))
1561 vs[7] = obj.matrix_world @ Vector((0.0,-r , h*0.5-r))
1562 vs[8] = obj.matrix_world @ Vector((0.0, r ,-h*0.5+r))
1563 vs[9] = obj.matrix_world @ Vector((0.0,-r ,-h*0.5+r))
1564
1565 indices = [(0,1),(2,3),(4,5),(6,7),(8,9)]
1566
1567 for l in indices:
1568 v0 = vs[l[0]]
1569 v1 = vs[l[1]]
1570 verts += [(v0[0],v0[1],v0[2])]
1571 verts += [(v1[0],v1[1],v1[2])]
1572 colours += [(0.5,1,0,1),(0.5,1,0,1)]
1573
1574 elif obj.cv_data.classtype == 'classtype_spawn':
1575 vs = [None]*4
1576 vs[0] = obj.matrix_world @ Vector((0,0,0))
1577 vs[1] = obj.matrix_world @ Vector((0,2,0))
1578 vs[2] = obj.matrix_world @ Vector((0.5,1,0))
1579 vs[3] = obj.matrix_world @ Vector((-0.5,1,0))
1580 indices = [(0,1),(1,2),(1,3)]
1581 for l in indices:
1582 v0 = vs[l[0]]
1583 v1 = vs[l[1]]
1584 verts += [(v0[0],v0[1],v0[2])]
1585 verts += [(v1[0],v1[1],v1[2])]
1586 colours += [(0,1,1,1),(0,1,1,1)]
1587
1588 elif obj.cv_data.classtype == 'classtype_route':
1589 vs = [None]*2
1590 vs[0] = obj.location
1591 vs[1] = obj.cv_data.target.location
1592 indices = [(0,1)]
1593 for l in indices:
1594 v0 = vs[l[0]]
1595 v1 = vs[l[1]]
1596 verts += [(v0[0],v0[1],v0[2])]
1597 verts += [(v1[0],v1[1],v1[2])]
1598 colours += [(0,1,1,1),(0,1,1,1)]
1599
1600 stack = [None]*64
1601 stack_i = [0]*64
1602 stack[0] = obj.cv_data.target
1603 si = 1
1604 loop_complete = False
1605
1606 while si > 0:
1607 if stack_i[si-1] == 2:
1608 si -= 1
1609 continue
1610
1611 if si == 0: # Loop failed to complete
1612 break
1613
1614 node = stack[si-1]
1615
1616 targets = [None,None]
1617 targets[0] = node.cv_data.target
1618
1619 if node.cv_data.classtype == 'classtype_route_node':
1620 targets[1] = node.cv_data.target1
1621
1622 nextnode = targets[stack_i[si-1]]
1623 stack_i[si-1] += 1
1624
1625 if nextnode != None: # branch
1626 if nextnode == stack[0]: # Loop completed
1627 loop_complete = True
1628 break
1629
1630 valid=True
1631 for sj in range(si):
1632 if stack[sj] == nextnode: # invalidated path
1633 valid=False
1634 break
1635
1636 if valid:
1637 stack_i[si] = 0
1638 stack[si] = nextnode
1639 si += 1
1640 continue
1641
1642 if loop_complete:
1643 cc = Vector((obj.cv_data.colour[0],\
1644 obj.cv_data.colour[1],\
1645 obj.cv_data.colour[2],\
1646 1.0))
1647
1648 for sj in range(si):
1649 sk = (sj+1)%si
1650
1651 if stack[sj].cv_data.classtype == 'classtype_gate' and \
1652 stack[sk].cv_data.classtype == 'classtype_gate':
1653 dist = (stack[sj].location-stack[sk].location).magnitude
1654 drawsbpath( stack[sj], stack[sk], cc*0.4, cc, dist, dist )
1655
1656 else:
1657 drawbpath( stack[sj], stack[sk], cc, cc )
1658
1659 course_count += 1
1660
1661 elif obj.cv_data.classtype == 'classtype_car_path':
1662 v0 = obj.matrix_world.to_quaternion() @ Vector((0,1,0))
1663 c0 = Vector((v0.x*0.5+0.5, v0.y*0.5+0.5, 0.0, 1.0))
1664 drawbhandle( obj, 1.0, (0.9,0.9,0.9,1.0) )
1665
1666 if obj.cv_data.target != None:
1667 v1 = obj.cv_data.target.matrix_world.to_quaternion()@Vector((0,1,0))
1668 c1 = Vector((v1.x*0.5+0.5, v1.y*0.5+0.5, 0.0, 1.0))
1669
1670 drawbhandle( obj.cv_data.target, -1.0, (0.5,0.5,0.5,1.0) )
1671 drawbpath( obj, obj.cv_data.target, c0, c1 )
1672
1673 if obj.cv_data.target1 != None:
1674 v1 = obj.cv_data.target1.matrix_world.to_quaternion()@Vector((0,1,0))
1675 c1 = Vector((v1.x*0.5+0.5, v1.y*0.5+0.5, 0.0, 1.0))
1676
1677 drawbhandle( obj.cv_data.target1, -1.0, (0.5,0.5,0.5,1.0) )
1678 drawbpath( obj, obj.cv_data.target1, c0, c1 )
1679
1680 lines = batch_for_shader(\
1681 cv_view_shader, 'LINES', \
1682 { "pos":verts, "color":colours })
1683
1684 lines.draw( cv_view_shader )
1685
1686 def cv_poll_target(scene, obj):
1687 if obj == bpy.context.active_object:
1688 return False
1689 if obj.cv_data.classtype == 'classtype_none':
1690 return False
1691 return True
1692
1693 class CV_MESH_SETTINGS(bpy.types.PropertyGroup):
1694 v0: bpy.props.FloatVectorProperty(name="v0",size=3)
1695 v1: bpy.props.FloatVectorProperty(name="v1",size=3)
1696 v2: bpy.props.FloatVectorProperty(name="v2",size=3)
1697 v3: bpy.props.FloatVectorProperty(name="v3",size=3)
1698
1699 class CV_OBJ_SETTINGS(bpy.types.PropertyGroup):
1700 uid: bpy.props.IntProperty( name="" )
1701
1702 strp: bpy.props.StringProperty( name="strp" )
1703 intp: bpy.props.IntProperty( name="intp" )
1704 fltp: bpy.props.FloatProperty( name="fltp" )
1705
1706 target: bpy.props.PointerProperty( type=bpy.types.Object, name="target", \
1707 poll=cv_poll_target )
1708 target1: bpy.props.PointerProperty( type=bpy.types.Object, name="target1", \
1709 poll=cv_poll_target )
1710
1711 colour: bpy.props.FloatVectorProperty(name="colour",subtype='COLOR',\
1712 min=0.0,max=1.0)
1713
1714 classtype: bpy.props.EnumProperty(
1715 name="Format",
1716 items = [
1717 ('classtype_none', "classtype_none", "", 0),
1718 ('classtype_gate', "classtype_gate", "", 1),
1719 ('classtype_block', "classtype_block", "", 2),
1720 ('classtype_spawn', "classtype_spawn", "", 3),
1721 ('classtype_water', "classtype_water", "", 4),
1722 ('classtype_car_path', "classtype_car_path", "", 5),
1723 ('classtype_INSTANCE', "","", 6 ),
1724 ('classtype_capsule', "classtype_capsule", "", 7 ),
1725 ('classtype_route_node', "classtype_route_node", "", 8 ),
1726 ('classtype_route', "classtype_route", "", 9 ),
1727 ('classtype_bone',"classtype_bone","",10),
1728 ('classtype_SKELETON', "","", 11 ),
1729 ('classtype_SKIN',"","",12),
1730 ('classtype_achievement_box',"classtype_achievement_box","",13),
1731 ('classtype_audio',"classtype_audio","",14),
1732 ])
1733
1734 class CV_BONE_SETTINGS(bpy.types.PropertyGroup):
1735 collider: bpy.props.BoolProperty(name="Collider",default=False)
1736 v0: bpy.props.FloatVectorProperty(name="v0",size=3)
1737 v1: bpy.props.FloatVectorProperty(name="v1",size=3)
1738
1739 con0: bpy.props.BoolProperty(name="Constriant 0",default=False)
1740 mins: bpy.props.FloatVectorProperty(name="mins",size=3)
1741 maxs: bpy.props.FloatVectorProperty(name="maxs",size=3)
1742
1743 class CV_BONE_PANEL(bpy.types.Panel):
1744 bl_label="Bone Config"
1745 bl_idname="SCENE_PT_cv_bone"
1746 bl_space_type='PROPERTIES'
1747 bl_region_type='WINDOW'
1748 bl_context='bone'
1749
1750 def draw(_,context):
1751 active_object = context.active_object
1752 if active_object == None: return
1753
1754 bone = active_object.data.bones.active
1755 if bone == None: return
1756
1757 _.layout.prop( bone.cv_data, "collider" )
1758 _.layout.prop( bone.cv_data, "v0" )
1759 _.layout.prop( bone.cv_data, "v1" )
1760
1761 _.layout.label( text="Angle Limits" )
1762 _.layout.prop( bone.cv_data, "con0" )
1763 _.layout.prop( bone.cv_data, "mins" )
1764 _.layout.prop( bone.cv_data, "maxs" )
1765
1766 class CV_SCENE_SETTINGS(bpy.types.PropertyGroup):
1767 use_hidden: bpy.props.BoolProperty( name="use hidden", default=False )
1768
1769 class CV_OBJ_PANEL(bpy.types.Panel):
1770 bl_label="Entity Config"
1771 bl_idname="SCENE_PT_cv_entity"
1772 bl_space_type='PROPERTIES'
1773 bl_region_type='WINDOW'
1774 bl_context="object"
1775
1776 def draw(_,context):
1777 active_object = bpy.context.active_object
1778 if active_object == None: return
1779 if active_object.type == 'ARMATURE':
1780 #{
1781 row = _.layout.row()
1782 row.enabled = False
1783 row.label( text="This object has the intrinsic classtype of skeleton" )
1784 return
1785 #}
1786
1787 _.layout.prop( active_object.cv_data, "classtype" )
1788
1789 if active_object.cv_data.classtype == 'classtype_gate':
1790 _.layout.prop( active_object.cv_data, "target" )
1791
1792 mesh = active_object.data
1793 _.layout.label( text=F"(i) Data is stored in {mesh.name}" )
1794 _.layout.prop( mesh.cv_data, "v0" )
1795
1796 elif active_object.cv_data.classtype == 'classtype_car_path' or \
1797 active_object.cv_data.classtype == 'classtype_route_node':
1798 _.layout.prop( active_object.cv_data, "target" )
1799 _.layout.prop( active_object.cv_data, "target1" )
1800
1801 elif active_object.cv_data.classtype == 'classtype_route':
1802 _.layout.prop( active_object.cv_data, "target" )
1803 _.layout.prop( active_object.cv_data, "colour" )
1804
1805 elif active_object.cv_data.classtype == 'classtype_block':
1806 mesh = active_object.data
1807
1808 _.layout.label( text=F"(i) Data is stored in {mesh.name}" )
1809 _.layout.prop( mesh.cv_data, "v0" )
1810 _.layout.prop( mesh.cv_data, "v1" )
1811 _.layout.prop( mesh.cv_data, "v2" )
1812 _.layout.prop( mesh.cv_data, "v3" )
1813 elif active_object.cv_data.classtype == 'classtype_capsule':
1814 mesh = active_object.data
1815 _.layout.label( text=F"(i) Data is stored in {mesh.name}" )
1816 _.layout.prop( mesh.cv_data, "v0" )
1817 elif active_object.cv_data.classtype == 'classtype_achievement_box':
1818 _.layout.prop( active_object.cv_data, "strp" )
1819 _.layout.prop( active_object.cv_data, "target" )
1820 elif active_object.cv_data.classtype == 'classtype_audio':
1821 _.layout.prop( active_object.cv_data, "strp" )
1822 _.layout.prop( active_object.cv_data, "intp" )
1823 _.layout.prop( active_object.cv_data, "fltp" )
1824
1825 class CV_INTERFACE(bpy.types.Panel):
1826 bl_idname = "VIEW3D_PT_carve"
1827 bl_label = "Carve"
1828 bl_space_type = 'VIEW_3D'
1829 bl_region_type = 'UI'
1830 bl_category = "Carve"
1831
1832 def draw(_, context):
1833 layout = _.layout
1834 layout.prop( context.scene.cv_data, "use_hidden")
1835 layout.operator( "carve.compile_all" )
1836
1837 def test_compile():
1838 view_layer = bpy.context.view_layer
1839 for col in view_layer.layer_collection.children["export"].children:
1840 if not col.hide_viewport or bpy.context.scene.cv_data.use_hidden:
1841 write_model( col.name )
1842
1843 class CV_COMPILE(bpy.types.Operator):
1844 bl_idname="carve.compile_all"
1845 bl_label="Compile All"
1846
1847 def execute(_,context):
1848 test_compile()
1849 #cProfile.runctx("test_compile()",globals(),locals(),sort=1)
1850 #for col in bpy.data.collections["export"].children:
1851 # write_model( col.name )
1852
1853 return {'FINISHED'}
1854
1855 classes = [CV_OBJ_SETTINGS,CV_OBJ_PANEL,CV_COMPILE,CV_INTERFACE,\
1856 CV_MESH_SETTINGS, CV_SCENE_SETTINGS, CV_BONE_SETTINGS,\
1857 CV_BONE_PANEL]
1858
1859 def register():
1860 global cv_view_draw_handler
1861
1862 for c in classes:
1863 bpy.utils.register_class(c)
1864
1865 bpy.types.Object.cv_data = bpy.props.PointerProperty(type=CV_OBJ_SETTINGS)
1866 bpy.types.Mesh.cv_data = bpy.props.PointerProperty(type=CV_MESH_SETTINGS)
1867 bpy.types.Scene.cv_data = bpy.props.PointerProperty(type=CV_SCENE_SETTINGS)
1868 bpy.types.Bone.cv_data = bpy.props.PointerProperty(type=CV_BONE_SETTINGS)
1869
1870 cv_view_draw_handler = bpy.types.SpaceView3D.draw_handler_add(\
1871 cv_draw,(),'WINDOW','POST_VIEW')
1872
1873 def unregister():
1874 global cv_view_draw_handler
1875
1876 for c in classes:
1877 bpy.utils.unregister_class(c)
1878
1879 bpy.types.SpaceView3D.draw_handler_remove(cv_view_draw_handler,'WINDOW')