well yeah i guess
[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":"Carve exporter",
13 "author": "Harry Godden (hgn)",
14 "version": (0,1),
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):
24 _pack_ = 1
25 _fields_ = [("co",c_float*3),
26 ("norm",c_float*3),
27 ("uv",c_float*2),
28 ("colour",c_uint8*4),
29 ("weights",c_uint16*4),
30 ("groups",c_uint8*4)]
31
32 class mdl_submesh(Structure):
33 _pack_ = 1
34 _fields_ = [("indice_start",c_uint32),
35 ("indice_count",c_uint32),
36 ("vertex_start",c_uint32),
37 ("vertex_count",c_uint32),
38 ("bbx",(c_float*3)*2),
39 ("material_id",c_uint32)] # index into the material array
40
41 class mdl_material(Structure):
42 _pack_ = 1
43 _fields_ = [("pstr_name",c_uint32)]
44
45 class mdl_node(Structure):
46 _pack_ = 1
47 _fields_ = [("co",c_float*3),
48 ( "q",c_float*4),
49 ( "s",c_float*3),
50 ("sub_uid",c_uint32), # dont use
51 ("submesh_start",c_uint32),
52 ("submesh_count",c_uint32),
53 ("classtype",c_uint32),
54 ("offset",c_uint32),
55 ("parent",c_uint32),
56 ("pstr_name",c_uint32)]
57
58 class mdl_header(Structure):
59 _pack_ = 1
60 _fields_ = [("identifier",c_uint32),
61 ("version",c_uint32),
62 ("file_length",c_uint32),
63 ("pad0",c_uint32),
64
65 ("vertex_count",c_uint32),
66 ("vertex_offset",c_uint32),
67
68 ("indice_count",c_uint32),
69 ("indice_offset",c_uint32),
70
71 ("submesh_count",c_uint32),
72 ("submesh_offset",c_uint32),
73
74 ("material_count",c_uint32),
75 ("material_offset",c_uint32),
76
77 ("node_count",c_uint32),
78 ("node_offset",c_uint32),
79
80 ("anim_count",c_uint32),
81 ("anim_offset",c_uint32),
82
83 ("strings_length",c_uint32),
84 ("strings_offset",c_uint32),
85
86 ("entdata_length",c_uint32),
87 ("entdata_offset",c_uint32),
88
89 ("keyframe_count",c_uint32),
90 ("keyframe_offset",c_uint32)]
91
92
93 class mdl_animation(Structure):
94 _pack_ = 1
95 _fields_ = [("pstr_name",c_uint32),
96 ("length",c_uint32),
97 ("rate",c_float),
98 ("offset",c_uint32)]
99
100 class mdl_keyframe(Structure):
101 _pack_ = 1
102 _fields_ = [("co",c_float*3),
103 ("q",c_float*4),
104 ("s",c_float*3)]
105
106 # Entity types
107 # ==========================================
108
109 class classtype_gate(Structure):
110 _pack_ = 1
111 _fields_ = [("target",c_uint32),
112 ("dims",c_float*3)]
113
114 class classtype_block(Structure):
115 _pack_ = 1
116 _fields_ = [("bbx",(c_float*3)*2)]
117
118 class classtype_spawn(Structure):
119 _pack_ = 1
120 _fields_ = [("temp",c_uint32)]
121
122 class classtype_water(Structure):
123 _pack_ = 1
124 _fields_ = [("temp",c_uint32)]
125
126 class classtype_car_path(Structure):
127 _pack_ = 1
128 _fields_ = [("target",c_uint32),
129 ("target1",c_uint32)]
130
131 class classtype_instance(Structure):
132 _pack_ = 1
133 _fields_ = [("pstr_file",c_uint32)]
134
135 class classtype_capsule(Structure):
136 _pack_ = 1
137 _fields_ = [("height",c_float),
138 ("radius",c_float)]
139
140 class classtype_route_node(Structure):
141 _pack_ = 1
142 _fields_ = [("target",c_uint32),
143 ("target1",c_uint32)]
144
145 class classtype_route(Structure):
146 _pack_ = 1
147 _fields_ = [("id_start",c_uint32),
148 ("colour",c_float*3)]
149
150 class classtype_skin(Structure):
151 _pack_ = 1
152 _fields_ = [("skeleton",c_uint32)]
153
154 class classtype_skeleton(Structure):
155 _pack_ = 1
156 _fields_ = [("channels",c_uint32),
157 ("ik_count",c_uint32),
158 ("collider_count",c_uint32),
159 ("anim_start",c_uint32),
160 ("anim_count",c_uint32)]
161
162 class classtype_bone(Structure):
163 _pack_ = 1
164 _fields_ = [("deform",c_uint32),
165 ("ik_target",c_uint32),
166 ("ik_pole",c_uint32),
167 ("collider",c_uint32),
168 ("use_limits",c_uint32),
169 ("angle_limits",(c_float*3)*2),
170 ("hitbox",(c_float*3)*2)]
171
172 class classtype_achievement_box(Structure):
173 _pack_ = 1
174 _fields_ = [("pstr_name",c_uint32),
175 ("trigger",c_uint32)]
176
177 class classtype_audio(Structure):
178 _pack_ = 1
179 _fields_ = [("pstr_file",c_uint32),
180 ("flags",c_uint32),
181 ("volume",c_float)]
182
183 # Exporter
184 # ==============================================================================
185
186 def write_model(collection_name):
187 print( F"Model graph | Create mode '{collection_name}'" )
188
189 header = mdl_header()
190 header.identifier = 0xABCD0000
191 header.version = 0
192 header.vertex_count = 0
193 header.indice_count = 0
194 header.submesh_count = 0
195 header.node_count = 0
196 header.material_count = 0
197 header.file_length = 0
198
199 header.strings_length = 0
200 header.entdata_length = 0
201 header.keyframe_count = 0
202
203 mesh_cache = {}
204 string_cache = {}
205 material_cache = {}
206
207 strings_buffer = b''
208
209 material_buffer = []
210 submesh_buffer = []
211 vertex_buffer = []
212 indice_buffer = []
213 node_buffer = []
214 entdata_buffer = []
215
216 anim_buffer = []
217 animdata_buffer = []
218
219 def emplace_string( s ):
220 nonlocal string_cache, strings_buffer
221
222 if s in string_cache:
223 return string_cache[s]
224
225 string_cache[s] = len( strings_buffer )
226 strings_buffer += (s+'\0').encode('utf-8')
227 return string_cache[s]
228
229 def emplace_material( mat ):
230 nonlocal material_cache, material_buffer
231
232 if mat == None:
233 return 0
234
235 if mat.name in material_cache:
236 return material_cache[mat.name]
237
238 material_cache[mat.name] = header.material_count
239 dest = mdl_material()
240 dest.pstr_name = emplace_string( mat.name )
241 material_buffer += [dest]
242
243 header.material_count += 1
244 return material_cache[mat.name]
245
246 # Create root or empty node and materials
247 # this is to designate id 0 as 'NULL'
248 #
249 none_material = c_uint32(69)
250 none_material.name = ""
251 emplace_material( none_material )
252
253 root = mdl_node()
254 root.co[0] = 0
255 root.co[1] = 0
256 root.co[2] = 0
257 root.q[0] = 0
258 root.q[1] = 0
259 root.q[2] = 0
260 root.q[3] = 1
261 root.s[0] = 1
262 root.s[1] = 1
263 root.s[2] = 1
264 root.pstr_name = emplace_string('')
265 root.submesh_start = 0
266 root.submesh_count = 0
267 root.offset = 0
268 root.classtype = 0
269 node_buffer += [root]
270
271 # Do exporting
272 #
273 print( " assigning ids" )
274 collection = bpy.data.collections[collection_name]
275
276 # Scene graph
277 # ==========================================
278
279 header.node_count = 0
280 def _uid():
281 nonlocal header
282 uid = header.node_count
283 header.node_count += 1
284 return uid
285
286 print( " creating scene graph" )
287 graph = {}
288 graph["obj"] = None
289 graph["depth"] = 0
290 graph["children"] = []
291 graph["uid"] = _uid()
292 graph["parent"] = None
293
294 graph_lookup = {} # object can lookup its graph def here
295
296 for obj in collection.all_objects:
297 if not obj.parent:
298
299 def _extend( p, n, d ):
300 uid = _uid()
301 tree = {}
302 tree["uid"] = uid
303 tree["children"] = []
304 tree["depth"] = d
305 tree["obj"] = n
306 tree["parent"] = p
307 n.cv_data.uid = uid
308
309 if n.type == 'ARMATURE':
310 tree["bones"] = [None] # None is the root transform
311 tree["ik_count"] = 0
312 tree["collider_count"] = 0
313
314 def _extendb( p, n, d ):
315 nonlocal tree
316
317 btree = {}
318 btree["bone"] = n
319 btree["uid"] = _uid()
320 btree["children"] = []
321 btree["depth"] = d
322 btree["parent"] = p
323 tree["bones"] += [n.name]
324
325 for c in n.children:
326 _extendb( btree, c, d+1 )
327
328 for c in tree['obj'].pose.bones[n.name].constraints:
329 if c.type == 'IK':
330 btree["target"] = c.subtarget
331 btree["pole"] = c.pole_subtarget
332 tree["ik_count"] += 1
333
334 if n.cv_data.collider:
335 tree['collider_count'] += 1
336
337 btree['deform'] = n.use_deform
338 p['children'] += [btree]
339
340 for b in n.data.bones:
341 if not b.parent:
342 _extendb( tree, b, d+1 )
343
344 for obj1 in n.children:
345 nonlocal collection
346 for c1 in obj1.users_collection:
347 if c1 == collection:
348 _extend( tree, obj1, d+1 )
349 break
350
351 p["children"] += [tree]
352 graph_lookup[n] = tree
353
354 _extend( graph, obj, 1 )
355
356
357 def _graph_iter(p):
358 for c in p['children']:
359 yield c
360 yield from _graph_iter(c)
361
362 it = _graph_iter(graph)
363
364 root.parent = 0xffffffff
365
366 # Compile
367 # ==============================================
368 it = _graph_iter(graph)
369 print( " compiling data" )
370 for node_def in it:
371 if 'obj' in node_def:
372 obj = node_def['obj']
373 objt = obj.type
374 objco = obj.location
375 elif 'bone' in node_def:
376 obj = node_def['bone']
377 objt = 'BONE'
378 objco = obj.head_local
379
380 depth = node_def['depth']
381 uid = node_def['uid']
382
383 node = mdl_node()
384 node.co[0] = objco[0]
385 node.co[1] = objco[2]
386 node.co[2] = -objco[1]
387
388 # Convert rotation quat to our space type
389 quat = obj.matrix_local.to_quaternion()
390 node.q[0] = quat[1]
391 node.q[1] = quat[3]
392 node.q[2] = -quat[2]
393 node.q[3] = quat[0]
394
395 if objt == 'BONE':
396 node.s[0] = obj.tail_local[0] - node.co[0]
397 node.s[1] = obj.tail_local[2] - node.co[1]
398 node.s[2] = -obj.tail_local[1] - node.co[2]
399 else:
400 node.s[0] = obj.scale[0]
401 node.s[1] = obj.scale[2]
402 node.s[2] = obj.scale[1]
403
404 node.pstr_name = emplace_string( obj.name )
405
406 if node_def["parent"]:
407 node.parent = node_def["parent"]["uid"]
408
409 if objt == 'BONE':
410 classtype = 'k_classtype_bone'
411 elif objt == 'ARMATURE':
412 classtype = 'k_classtype_skeleton'
413 else:
414 classtype = obj.cv_data.classtype
415
416 # Process type: MESH
417 # =================================================================
418 #
419
420 # Dont use the cache if we have modifiers that affect the normals
421 #
422 compile_mesh = False
423 if objt == 'MESH':
424 armature_def = None
425 compile_mesh = True
426 can_use_cache = True
427
428 for mod in obj.modifiers:
429 if mod.type == 'DATA_TRANSFER' or mod.type == 'SHRINKWRAP' or \
430 mod.type == 'BOOLEAN' or mod.type == 'CURVE' or \
431 mod.type == 'ARRAY':
432 can_use_cache = False
433
434 if mod.type == 'ARMATURE':
435 classtype = 'k_classtype_skin'
436 armature_def = graph_lookup[mod.object]
437 POSE_OR_REST_CACHE = armature_def['obj'].data.pose_position
438
439 armature_def['obj'].data.pose_position = 'REST'
440
441 if can_use_cache and obj.data.name in mesh_cache:
442 ref = mesh_cache[obj.data.name]
443 node.submesh_start = ref.submesh_start
444 node.submesh_count = ref.submesh_count
445 compile_mesh = False
446
447 if compile_mesh:
448 node.submesh_start = header.submesh_count
449 node.submesh_count = 0
450
451 default_mat = c_uint32(69)
452 default_mat.name = ""
453
454 dgraph = bpy.context.evaluated_depsgraph_get()
455 data = obj.evaluated_get(dgraph).data
456 data.calc_loop_triangles()
457 data.calc_normals_split()
458
459 mat_list = data.materials if len(data.materials) > 0 else [default_mat]
460 for material_id, mat in enumerate(mat_list):
461 mref = {}
462
463 sm = mdl_submesh()
464 sm.indice_start = header.indice_count
465 sm.vertex_start = header.vertex_count
466 sm.vertex_count = 0
467 sm.indice_count = 0
468 sm.material_id = emplace_material( mat )
469
470 for i in range(3):
471 sm.bbx[0][i] = 999999
472 sm.bbx[1][i] = -999999
473
474 boffa = {}
475
476 # Write the vertex / indice data
477 #
478 for tri_index, tri in enumerate(data.loop_triangles):
479 if tri.material_index != material_id:
480 continue
481
482 for j in range(3):
483 vert = data.vertices[tri.vertices[j]]
484 li = tri.loops[j]
485 vi = data.loops[li].vertex_index
486
487 co = vert.co
488 norm = data.loops[li].normal
489 uv = (0,0)
490 colour = (255,255,255,255)
491 groups = [0,0,0,0]
492 weights = [0,0,0,0]
493
494 if data.uv_layers:
495 uv = data.uv_layers.active.data[li].uv
496
497 if data.vertex_colors:
498 colour = data.vertex_colors.active.data[li].color
499 colour = (int(colour[0]*255.0),\
500 int(colour[1]*255.0),\
501 int(colour[2]*255.0),\
502 int(colour[3]*255.0))
503
504 # WEight groups
505 #
506 if armature_def:
507 src_groups = [_ for _ in data.vertices[vi].groups \
508 if obj.vertex_groups[_.group].name in \
509 armature_def['bones']]
510
511 weight_groups = sorted( src_groups, key = \
512 lambda a: a.weight, reverse=True )
513 tot = 0.0
514 for ml in range(3):
515 if len(weight_groups) > ml:
516 g = weight_groups[ml]
517 name = obj.vertex_groups[g.group].name
518 weight = g.weight
519
520 weights[ml] = weight
521 groups[ml] = armature_def['bones'].index(name)
522 tot += weight
523
524 if len(weight_groups) > 0:
525 inv_norm = (1.0/tot) * 65535.0
526 for ml in range(3):
527 weights[ml] = int( weights[ml] * inv_norm )
528 weights[ml] = min( weights[ml], 65535 )
529 weights[ml] = max( weights[ml], 0 )
530
531 TOLERENCE = 4
532 m = float(10**TOLERENCE)
533
534 key = (int(co[0]*m+0.5),\
535 int(co[1]*m+0.5),\
536 int(co[2]*m+0.5),\
537 int(norm[0]*m+0.5),\
538 int(norm[1]*m+0.5),\
539 int(norm[2]*m+0.5),\
540 int(uv[0]*m+0.5),\
541 int(uv[1]*m+0.5),\
542 colour[0],\
543 colour[1],\
544 colour[2],\
545 colour[3],\
546 weights[0],\
547 weights[1],\
548 weights[2],\
549 weights[3],\
550 groups[0],\
551 groups[1],\
552 groups[2],\
553 groups[3])
554
555 if key in boffa:
556 indice_buffer += [boffa[key]]
557 else:
558 index = c_uint32(sm.vertex_count)
559 sm.vertex_count += 1
560
561 boffa[key] = index
562 indice_buffer += [index]
563
564 v = mdl_vert()
565 v.co[0] = co[0]
566 v.co[1] = co[2]
567 v.co[2] = -co[1]
568 v.norm[0] = norm[0]
569 v.norm[1] = norm[2]
570 v.norm[2] = -norm[1]
571 v.uv[0] = uv[0]
572 v.uv[1] = uv[1]
573 v.colour[0] = colour[0]
574 v.colour[1] = colour[1]
575 v.colour[2] = colour[2]
576 v.colour[3] = colour[3]
577 v.weights[0] = weights[0]
578 v.weights[1] = weights[1]
579 v.weights[2] = weights[2]
580 v.weights[3] = weights[3]
581 v.groups[0] = groups[0]
582 v.groups[1] = groups[1]
583 v.groups[2] = groups[2]
584 v.groups[3] = groups[3]
585
586 vertex_buffer += [v]
587
588 for i in range(3):
589 sm.bbx[0][i] = min( sm.bbx[0][i], v.co[i] )
590 sm.bbx[1][i] = max( sm.bbx[1][i], v.co[i] )
591
592 sm.indice_count += 1
593
594 if sm.vertex_count == 0:
595 for j in range(2):
596 for i in range(3):
597 sm.bbx[j][i] = 0
598
599 submesh_buffer += [sm]
600 node.submesh_count += 1
601 header.submesh_count += 1
602 header.vertex_count += sm.vertex_count
603 header.indice_count += sm.indice_count
604
605 mesh_cache[obj.data.name] = node
606
607 # Process entity data
608 # ==================================================================
609 node.offset = header.entdata_length
610
611 if classtype != 'k_classtype_none':
612 disptype = classtype
613 else:
614 disptype = objt
615
616 s000 = F" [{uid: 3}/{header.node_count-1}]" + " |"*(depth-1)
617 s001 = F" L {obj.name}"
618 s002 = s000+s001
619 s003 = F"{disptype}"
620 s004 = F"{node.parent: 3}"
621 s005 = ""
622
623 if classtype == 'k_classtype_skin':
624 armature_def['obj'].data.pose_position = POSE_OR_REST_CACHE
625 s005 = F" [armature -> {armature_def['obj'].cv_data.uid}]"
626
627 scmp = F"{s002:<32} {s003:<22} {s004} {s005}"
628 print( scmp )
629
630 if classtype == 'k_classtype_INSTANCE' or \
631 classtype == 'k_classtype_BONE' or \
632 classtype == 'k_classtype_SKELETON' or \
633 classtype == 'k_classtype_SKIN':
634 print( "ERROR: user classtype cannot be _INSTANCE or _BONE" )
635 node.classtype = 0
636 node.offset = 0
637
638 elif classtype == 'k_classtype_skin':
639 node.classtype = 12
640
641 armature = armature_def['obj']
642 header.entdata_length += sizeof( classtype_skin )
643
644 skin = classtype_skin()
645 skin.skeleton = armature.cv_data.uid
646 entdata_buffer += [skin]
647
648 elif classtype == 'k_classtype_skeleton':
649 node.classtype = 11
650 header.entdata_length += sizeof( classtype_skeleton )
651 skeleton = classtype_skeleton()
652
653 armature_def = graph_lookup[obj]
654 armature = obj
655 bones = armature_def['bones']
656 skeleton.channels = len(bones)
657 skeleton.ik_count = armature_def["ik_count"]
658 skeleton.collider_count = armature_def["collider_count"]
659
660 if armature.animation_data:
661 previous_frame = bpy.context.scene.frame_current
662 previous_action = armature.animation_data.action
663
664 skeleton.anim_start = len(anim_buffer)
665 skeleton.anim_count = 0
666
667 for NLALayer in obj.animation_data.nla_tracks:
668 for NLAStrip in NLALayer.strips:
669 # Use action
670 for a in bpy.data.actions:
671 if a.name == NLAStrip.name:
672 armature.animation_data.action = a
673 break
674
675 anim_start = int(NLAStrip.action_frame_start)
676 anim_end = int(NLAStrip.action_frame_end)
677
678 # export strips
679 anim = mdl_animation()
680 anim.pstr_name = emplace_string( NLAStrip.action.name )
681 anim.rate = 30.0
682 anim.offset = header.keyframe_count
683 anim.length = anim_end-anim_start
684
685 # Export the fucking keyframes
686 for frame in range(anim_start,anim_end):
687 bpy.context.scene.frame_set(frame)
688
689 for bone_name in bones:
690 for pb in armature.pose.bones:
691 if pb.name == bone_name:
692 rb = armature.data.bones[ bone_name ]
693
694 # relative bone matrix
695 if rb.parent is not None:
696 offset_mtx = rb.parent.matrix_local
697 offset_mtx = offset_mtx.inverted_safe() @ \
698 rb.matrix_local
699
700 inv_parent = pb.parent.matrix @ offset_mtx
701 inv_parent.invert_safe()
702 fpm = inv_parent @ pb.matrix
703 else:
704 bone_mtx = rb.matrix.to_4x4()
705 local_inv = rb.matrix_local.inverted_safe()
706 fpm = bone_mtx @ local_inv @ pb.matrix
707
708 loc, rot, sca = fpm.decompose()
709
710 # local position
711 final_pos = Vector(( loc[0], loc[2], -loc[1] ))
712
713 # rotation
714 lc_m = pb.matrix_channel.to_3x3()
715 if pb.parent is not None:
716 smtx = pb.parent.matrix_channel.to_3x3()
717 lc_m = smtx.inverted() @ lc_m
718 rq = lc_m.to_quaternion()
719
720 kf = mdl_keyframe()
721 kf.co[0] = final_pos[0]
722 kf.co[1] = final_pos[1]
723 kf.co[2] = final_pos[2]
724
725 kf.q[0] = rq[1]
726 kf.q[1] = rq[3]
727 kf.q[2] = -rq[2]
728 kf.q[3] = rq[0]
729
730 # scale
731 kf.s[0] = sca[0]
732 kf.s[1] = sca[2]
733 kf.s[2] = sca[1]
734
735 animdata_buffer += [kf]
736 header.keyframe_count += 1
737 break
738
739 anim_buffer += [anim]
740 skeleton.anim_count += 1
741
742 s000 = F" [{uid: 3}/{header.node_count-1}]" + " |"*(depth-1)
743 print( F"{s000} | *anim: {NLAStrip.action.name}" )
744
745 bpy.context.scene.frame_set( previous_frame )
746 armature.animation_data.action = previous_action
747
748 entdata_buffer += [skeleton]
749
750 elif classtype == 'k_classtype_bone':
751 node.classtype = 10
752 header.entdata_length += sizeof( classtype_bone )
753
754 bone = classtype_bone()
755 bone.deform = node_def['deform']
756
757 if 'target' in node_def:
758 bone.ik_target = armature_def['bones'].index( node_def['target'] )
759 bone.ik_pole = armature_def['bones'].index( node_def['pole'] )
760 else:
761 bone.ik_target = 0
762 bone.ik_pole = 0
763
764 bone.collider = 1 if obj.cv_data.collider else 0
765 if obj.cv_data.collider:
766 bone.hitbox[0][0] = obj.cv_data.v0[0]
767 bone.hitbox[0][1] = obj.cv_data.v0[2]
768 bone.hitbox[0][2] = -obj.cv_data.v1[1]
769 bone.hitbox[1][0] = obj.cv_data.v1[0]
770 bone.hitbox[1][1] = obj.cv_data.v1[2]
771 bone.hitbox[1][2] = -obj.cv_data.v0[1]
772 else:
773 bone.hitbox[0][0] = 0.0
774 bone.hitbox[0][1] = 0.0
775 bone.hitbox[0][2] = 0.0
776 bone.hitbox[1][0] = 0.0
777 bone.hitbox[1][1] = 0.0
778 bone.hitbox[1][2] = 0.0
779
780 if obj.cv_data.con0:
781 bone.use_limits = 1
782 bone.angle_limits[0][0] = obj.cv_data.mins[0]
783 bone.angle_limits[0][1] = obj.cv_data.mins[2]
784 bone.angle_limits[0][2] = -obj.cv_data.maxs[1]
785 bone.angle_limits[1][0] = obj.cv_data.maxs[0]
786 bone.angle_limits[1][1] = obj.cv_data.maxs[2]
787 bone.angle_limits[1][2] = -obj.cv_data.mins[1]
788 else:
789 bone.use_limits = 0
790 bone.angle_limits[0][0] = 0.0
791 bone.angle_limits[0][1] = 0.0
792 bone.angle_limits[0][2] = 0.0
793 bone.angle_limits[1][0] = 0.0
794 bone.angle_limits[1][1] = 0.0
795 bone.angle_limits[1][2] = 0.0
796
797 bone.deform = node_def['deform']
798 entdata_buffer += [bone]
799
800 elif classtype == 'k_classtype_gate':
801 node.classtype = 1
802 header.entdata_length += sizeof( classtype_gate )
803
804 gate = classtype_gate()
805 gate.target = 0
806 if obj.cv_data.target != None:
807 gate.target = obj.cv_data.target.cv_data.uid
808
809 if obj.type == 'MESH':
810 gate.dims[0] = obj.data.cv_data.v0[0]
811 gate.dims[1] = obj.data.cv_data.v0[1]
812 gate.dims[2] = obj.data.cv_data.v0[2]
813 else:
814 gate.dims[0] = obj.cv_data.v0[0]
815 gate.dims[1] = obj.cv_data.v0[1]
816 gate.dims[2] = obj.cv_data.v0[2]
817
818 entdata_buffer += [gate]
819
820 elif classtype == 'k_classtype_block':
821 node.classtype = 2
822 header.entdata_length += sizeof( classtype_block )
823
824 source = obj.data.cv_data
825
826 block = classtype_block()
827 block.bbx[0][0] = source.v0[0]
828 block.bbx[0][1] = source.v0[2]
829 block.bbx[0][2] = -source.v1[1]
830
831 block.bbx[1][0] = source.v1[0]
832 block.bbx[1][1] = source.v1[2]
833 block.bbx[1][2] = -source.v0[1]
834 entdata_buffer += [block]
835
836 elif classtype == 'k_classtype_achievement_box':
837 node.classtype = 13
838
839 header.entdata_length += sizeof( classtype_achievement_box )
840 ach = classtype_achievement_box()
841 ach.pstr_name = emplace_string( obj.cv_data.strp )
842 ach.trigger = 0
843
844 if obj.cv_data.target != None:
845 ach.trigger = obj.cv_data.target.cv_data.uid
846
847 entdata_buffer += [ach]
848
849 elif classtype == 'k_classtype_audio':
850 node.classtype = 14
851
852 header.entdata_length += sizeof( classtype_audio )
853 aud = classtype_audio()
854 aud.pstr_file = emplace_string( obj.cv_data.strp )
855 aud.flags = obj.cv_data.intp
856 aud.volume = obj.cv_data.fltp
857
858 entdata_buffer += [aud]
859
860 elif classtype == 'k_classtype_spawn':
861 node.classtype = 3
862
863 elif classtype == 'k_classtype_water':
864 node.classtype = 4
865
866 elif classtype == 'k_classtype_car_path':
867 node.classtype = 5
868 header.entdata_length += sizeof( classtype_car_path )
869
870 pn = classtype_car_path()
871 pn.target = 0
872 pn.target1 = 0
873
874 if obj.cv_data.target != None:
875 pn.target = obj.cv_data.target.cv_data.uid
876 if obj.cv_data.target1 != None:
877 pn.target1 = obj.cv_data.target1.cv_data.uid
878
879 entdata_buffer += [pn]
880
881 elif obj.is_instancer:
882 target = obj.instance_collection
883
884 node.classtype = 6
885 header.entdata_length += sizeof( classtype_instance )
886
887 inst = classtype_instance()
888 inst.pstr_file = emplace_string( F"models/{target.name}.mdl" )
889 entdata_buffer += [inst]
890
891 elif classtype == 'k_classtype_capsule':
892 node.classtype = 7
893
894 elif classtype == 'k_classtype_route_node':
895 node.classtype = 8
896 header.entdata_length += sizeof( classtype_route_node )
897
898 rn = classtype_route_node()
899 if obj.cv_data.target != None:
900 rn.target = obj.cv_data.target.cv_data.uid
901 if obj.cv_data.target1 != None:
902 rn.target1 = obj.cv_data.target1.cv_data.uid
903
904 entdata_buffer += [rn]
905
906 elif classtype == 'k_classtype_route':
907 node.classtype = 9
908 header.entdata_length += sizeof( classtype_route )
909 r = classtype_route()
910 r.colour[0] = obj.cv_data.colour[0]
911 r.colour[1] = obj.cv_data.colour[1]
912 r.colour[2] = obj.cv_data.colour[2]
913
914 if obj.cv_data.target != None:
915 r.id_start = obj.cv_data.target.cv_data.uid
916
917 entdata_buffer += [r]
918
919 # classtype == 'k_classtype_none':
920 else:
921 node.classtype = 0
922 node.offset = 0
923
924 node_buffer += [node]
925
926 # Write data arrays
927 # TODO: 8 BYTE ALIGNMENT
928
929 print( "Writing data" )
930 fpos = sizeof(header)
931
932 print( F"Nodes: {header.node_count}" )
933 header.node_offset = fpos
934 fpos += sizeof(mdl_node)*header.node_count
935
936 print( F"Submeshes: {header.submesh_count}" )
937 header.submesh_offset = fpos
938 fpos += sizeof(mdl_submesh)*header.submesh_count
939
940 print( F"Materials: {header.material_count}" )
941 header.material_offset = fpos
942 fpos += sizeof(mdl_material)*header.material_count
943
944 print( F"Animation count: {len(anim_buffer)}" )
945 header.anim_count = len(anim_buffer)
946 header.anim_offset = fpos
947 fpos += sizeof(mdl_animation)*header.anim_count
948
949 print( F"Entdata length: {header.entdata_length}" )
950 header.entdata_offset = fpos
951 fpos += header.entdata_length
952
953 print( F"Strings length: {len(strings_buffer)}" )
954 header.strings_offset = fpos
955 header.strings_length = len(strings_buffer)
956 fpos += header.strings_length
957
958 # Optional array things
959 print( F"Keyframe count: {header.keyframe_count}" )
960 header.keyframe_offset = fpos
961 fpos += sizeof(mdl_keyframe)*header.keyframe_count
962
963 print( F"Vertex count: {header.vertex_count}" )
964 header.vertex_offset = fpos
965 fpos += sizeof(mdl_vert)*header.vertex_count
966
967 print( F"Indice count: {header.indice_count}" )
968 header.indice_offset = fpos
969 fpos += sizeof(c_uint32)*header.indice_count
970
971 header.file_length = fpos
972
973 path = F"/home/harry/Documents/carve/models_src/{collection_name}.mdl"
974 fp = open( path, "wb" )
975
976 fp.write( bytearray( header ) )
977
978 for node in node_buffer:
979 fp.write( bytearray(node) )
980 for sm in submesh_buffer:
981 fp.write( bytearray(sm) )
982 for mat in material_buffer:
983 fp.write( bytearray(mat) )
984 for a in anim_buffer:
985 fp.write( bytearray(a) )
986 for ed in entdata_buffer:
987 fp.write( bytearray(ed) )
988
989 fp.write( strings_buffer )
990
991 for kf in animdata_buffer:
992 fp.write( bytearray(kf) )
993
994 for v in vertex_buffer:
995 fp.write( bytearray(v) )
996 for i in indice_buffer:
997 fp.write( bytearray(i) )
998
999 fp.close()
1000
1001 print( F"Completed {collection_name}.mdl" )
1002
1003 # Clicky clicky GUI
1004 # ------------------------------------------------------------------------------
1005
1006 cv_view_draw_handler = None
1007 cv_view_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR')
1008
1009 def cv_draw():
1010 global cv_view_shader
1011 cv_view_shader.bind()
1012 gpu.state.depth_mask_set(False)
1013 gpu.state.line_width_set(2.0)
1014 gpu.state.face_culling_set('BACK')
1015 gpu.state.depth_test_set('LESS')
1016 gpu.state.blend_set('NONE')
1017
1018 verts = []
1019 colours = []
1020
1021 #def drawbezier(p0,h0,p1,h1,c0,c1):
1022 # nonlocal verts, colours
1023
1024 # verts += [p0]
1025 # verts += [h0]
1026 # colours += [(0.5,0.5,0.5,1.0),(0.5,0.5,0.5,1)]
1027 # verts += [p1]
1028 # verts += [h1]
1029 # colours += [(1.0,1.0,1,1),(1,1,1,1)]
1030 #
1031 # last = p0
1032 # for i in range(10):
1033 # t = (i+1)/10
1034 # a0 = 1-t
1035
1036 # tt = t*t
1037 # ttt = tt*t
1038 # p=ttt*p1+(3*tt-3*ttt)*h1+(3*ttt-6*tt+3*t)*h0+(3*tt-ttt-3*t+1)*p0
1039 # verts += [(last[0],last[1],last[2])]
1040 # verts += [(p[0],p[1],p[2])]
1041 # colours += [c0*a0+c1*(1-a0),c0*a0+c1*(1-a0)]
1042 # last = p
1043
1044 course_count = 0
1045
1046 def drawbhandle(obj, direction, colour):
1047 nonlocal verts, colours
1048 p0 = obj.location
1049 h0 = obj.matrix_world @ Vector((0,direction,0))
1050 verts += [p0]
1051 verts += [h0]
1052 colours += [colour,colour]
1053
1054 def drawbezier(p0,h0,p1,h1,c0,c1):
1055 nonlocal verts, colours
1056
1057 last = p0
1058 for i in range(10):
1059 t = (i+1)/10
1060 a0 = 1-t
1061
1062 tt = t*t
1063 ttt = tt*t
1064 p=ttt*p1+(3*tt-3*ttt)*h1+(3*ttt-6*tt+3*t)*h0+(3*tt-ttt-3*t+1)*p0
1065 verts += [(last[0],last[1],last[2])]
1066 verts += [(p[0],p[1],p[2])]
1067 colours += [c0*a0+c1*(1-a0),c0*a0+c1*(1-a0)]
1068 last = p
1069
1070 def drawsbpath(o0,o1,c0,c1,s0,s1):
1071 nonlocal course_count
1072
1073 offs = ((course_count % 2)*2-1) * course_count * 0.02
1074
1075 p0 = o0.matrix_world @ Vector((offs, 0,0))
1076 h0 = o0.matrix_world @ Vector((offs, s0,0))
1077 p1 = o1.matrix_world @ Vector((offs, 0,0))
1078 h1 = o1.matrix_world @ Vector((offs,-s1,0))
1079 drawbezier(p0,h0,p1,h1,c0,c1)
1080
1081 def drawbpath(o0,o1,c0,c1):
1082 drawsbpath(o0,o1,c0,c1,1.0,1.0)
1083
1084 def drawbline(p0,p1,c0,c1):
1085 nonlocal verts, colours
1086 verts += [p0,p1]
1087 colours += [c0,c1]
1088
1089 for obj in bpy.context.collection.objects:
1090 if obj.type == 'ARMATURE':
1091 for bone in obj.data.bones:
1092 if bone.cv_data.collider and obj.data.pose_position == 'REST':
1093 c = bone.head_local
1094 a = bone.cv_data.v0
1095 b = bone.cv_data.v1
1096
1097 vs = [None]*8
1098 vs[0]=obj.matrix_world@Vector((c[0]+a[0],c[1]+a[1],c[2]+a[2]))
1099 vs[1]=obj.matrix_world@Vector((c[0]+a[0],c[1]+b[1],c[2]+a[2]))
1100 vs[2]=obj.matrix_world@Vector((c[0]+b[0],c[1]+b[1],c[2]+a[2]))
1101 vs[3]=obj.matrix_world@Vector((c[0]+b[0],c[1]+a[1],c[2]+a[2]))
1102 vs[4]=obj.matrix_world@Vector((c[0]+a[0],c[1]+a[1],c[2]+b[2]))
1103 vs[5]=obj.matrix_world@Vector((c[0]+a[0],c[1]+b[1],c[2]+b[2]))
1104 vs[6]=obj.matrix_world@Vector((c[0]+b[0],c[1]+b[1],c[2]+b[2]))
1105 vs[7]=obj.matrix_world@Vector((c[0]+b[0],c[1]+a[1],c[2]+b[2]))
1106
1107 indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
1108 (0,4),(1,5),(2,6),(3,7)]
1109
1110 for l in indices:
1111 v0 = vs[l[0]]
1112 v1 = vs[l[1]]
1113 verts += [(v0[0],v0[1],v0[2])]
1114 verts += [(v1[0],v1[1],v1[2])]
1115 colours += [(0.5,0.5,0.5,0.5),(0.5,0.5,0.5,0.5)]
1116
1117 center=obj.matrix_world@c
1118
1119 def _angle_lim( major, minor, amin, amax, colour ):
1120 nonlocal verts, colours
1121 f = 0.05
1122 ay = major*f
1123 ax = minor*f
1124
1125 for x in range(16):
1126 t0 = x/16
1127 t1 = (x+1)/16
1128 a0 = amin*(1.0-t0)+amax*t0
1129 a1 = amin*(1.0-t1)+amax*t1
1130
1131 p0 = c + major*f*math.cos(a0) + minor*f*math.sin(a0)
1132 p1 = c + major*f*math.cos(a1) + minor*f*math.sin(a1)
1133
1134 p0=obj.matrix_world @ p0
1135 p1=obj.matrix_world @ p1
1136 verts += [p0,p1]
1137 colours += [colour,colour]
1138
1139 if x == 0:
1140 verts += [p0,c]
1141 colours += [colour,colour]
1142 if x == 15:
1143 verts += [p1,c]
1144 colours += [colour,colour]
1145
1146 verts += [c+major*1.2*f,c+major*f*0.8]
1147 colours += [colour,colour]
1148
1149 if bone.cv_data.con0:
1150 _angle_lim( Vector((0,1,0)),Vector((0,0,1)), \
1151 bone.cv_data.mins[0], bone.cv_data.maxs[0], \
1152 (1,0,0,1))
1153 _angle_lim( Vector((0,0,1)),Vector((1,0,0)), \
1154 bone.cv_data.mins[1], bone.cv_data.maxs[1], \
1155 (0,1,0,1))
1156 _angle_lim( Vector((1,0,0)),Vector((0,1,0)), \
1157 bone.cv_data.mins[2], bone.cv_data.maxs[2], \
1158 (0,0,1,1))
1159
1160
1161 if obj.cv_data.classtype == 'k_classtype_gate':
1162 if obj.type == 'MESH':
1163 dims = obj.data.cv_data.v0
1164 else:
1165 dims = obj.cv_data.v0
1166
1167 vs = [None]*9
1168 c = Vector((0,0,dims[2]))
1169
1170 vs[0] = obj.matrix_world @ Vector((-dims[0],0.0,-dims[1]+dims[2]))
1171 vs[1] = obj.matrix_world @ Vector((-dims[0],0.0, dims[1]+dims[2]))
1172 vs[2] = obj.matrix_world @ Vector(( dims[0],0.0, dims[1]+dims[2]))
1173 vs[3] = obj.matrix_world @ Vector(( dims[0],0.0,-dims[1]+dims[2]))
1174 vs[4] = obj.matrix_world @ (c+Vector((-1,0,-2)))
1175 vs[5] = obj.matrix_world @ (c+Vector((-1,0, 2)))
1176 vs[6] = obj.matrix_world @ (c+Vector(( 1,0, 2)))
1177 vs[7] = obj.matrix_world @ (c+Vector((-1,0, 0)))
1178 vs[8] = obj.matrix_world @ (c+Vector(( 1,0, 0)))
1179
1180 indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(7,8)]
1181
1182 for l in indices:
1183 v0 = vs[l[0]]
1184 v1 = vs[l[1]]
1185 verts += [(v0[0],v0[1],v0[2])]
1186 verts += [(v1[0],v1[1],v1[2])]
1187 colours += [(1,1,0,1),(1,1,0,1)]
1188
1189 sw = (0.4,0.4,0.4,0.2)
1190 if obj.cv_data.target != None:
1191 drawbline( obj.location, obj.cv_data.target.location, sw,sw )
1192
1193 elif obj.cv_data.classtype == 'k_classtype_route_node':
1194 sw = Vector((0.4,0.4,0.4,0.2))
1195 sw2 = Vector((1.5,0.2,0.2,0.0))
1196 if obj.cv_data.target != None:
1197 drawbpath( obj, obj.cv_data.target, sw, sw )
1198 if obj.cv_data.target1 != None:
1199 drawbpath( obj, obj.cv_data.target1, sw, sw )
1200
1201 drawbhandle( obj, 1.0, (0.8,0.8,0.8,1.0) )
1202 drawbhandle( obj, -1.0, (0.4,0.4,0.4,1.0) )
1203
1204 p1 = obj.location+ \
1205 obj.matrix_world.to_quaternion() @ Vector((0,0,-6+1.5))
1206 drawbline( obj.location, p1, sw,sw2 )
1207
1208 elif obj.cv_data.classtype == 'k_classtype_achievement_box':
1209 a = Vector((-1,-1,-1))
1210 b = Vector((1,1,1))
1211
1212 vs = [None]*8
1213 vs[0] = obj.matrix_world @ Vector((a[0], a[1], a[2]))
1214 vs[1] = obj.matrix_world @ Vector((a[0], b[1], a[2]))
1215 vs[2] = obj.matrix_world @ Vector((b[0], b[1], a[2]))
1216 vs[3] = obj.matrix_world @ Vector((b[0], a[1], a[2]))
1217 vs[4] = obj.matrix_world @ Vector((a[0], a[1], b[2]))
1218 vs[5] = obj.matrix_world @ Vector((a[0], b[1], b[2]))
1219 vs[6] = obj.matrix_world @ Vector((b[0], b[1], b[2]))
1220 vs[7] = obj.matrix_world @ Vector((b[0], a[1], b[2]))
1221
1222 indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
1223 (0,4),(1,5),(2,6),(3,7)]
1224
1225 for l in indices:
1226 v0 = vs[l[0]]
1227 v1 = vs[l[1]]
1228 verts += [(v0[0],v0[1],v0[2])]
1229 verts += [(v1[0],v1[1],v1[2])]
1230 colours += [(0,1,0,1),(0,1,0,1)]
1231
1232 if obj.cv_data.target != None:
1233 vs = [None]*2
1234 vs[0] = obj.location
1235 vs[1] = obj.cv_data.target.location
1236 indices = [(0,1)]
1237 for l in indices:
1238 v0 = vs[l[0]]
1239 v1 = vs[l[1]]
1240 verts += [(v0[0],v0[1],v0[2])]
1241 verts += [(v1[0],v1[1],v1[2])]
1242 colours += [(0,1,1,1),(0,1,1,1)]
1243
1244
1245 elif obj.cv_data.classtype == 'k_classtype_block':
1246 a = obj.data.cv_data.v0
1247 b = obj.data.cv_data.v1
1248
1249 vs = [None]*8
1250 vs[0] = obj.matrix_world @ Vector((a[0], a[1], a[2]))
1251 vs[1] = obj.matrix_world @ Vector((a[0], b[1], a[2]))
1252 vs[2] = obj.matrix_world @ Vector((b[0], b[1], a[2]))
1253 vs[3] = obj.matrix_world @ Vector((b[0], a[1], a[2]))
1254 vs[4] = obj.matrix_world @ Vector((a[0], a[1], b[2]))
1255 vs[5] = obj.matrix_world @ Vector((a[0], b[1], b[2]))
1256 vs[6] = obj.matrix_world @ Vector((b[0], b[1], b[2]))
1257 vs[7] = obj.matrix_world @ Vector((b[0], a[1], b[2]))
1258
1259 indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
1260 (0,4),(1,5),(2,6),(3,7)]
1261
1262 for l in indices:
1263 v0 = vs[l[0]]
1264 v1 = vs[l[1]]
1265 verts += [(v0[0],v0[1],v0[2])]
1266 verts += [(v1[0],v1[1],v1[2])]
1267 colours += [(1,1,0,1),(1,1,0,1)]
1268
1269 elif obj.cv_data.classtype == 'k_classtype_capsule':
1270 h = obj.data.cv_data.v0[0]
1271 r = obj.data.cv_data.v0[1]
1272
1273 vs = [None]*10
1274 vs[0] = obj.matrix_world @ Vector((0.0,0.0, h*0.5 ))
1275 vs[1] = obj.matrix_world @ Vector((0.0,0.0,-h*0.5 ))
1276 vs[2] = obj.matrix_world @ Vector(( r,0.0, h*0.5-r))
1277 vs[3] = obj.matrix_world @ Vector(( -r,0.0, h*0.5-r))
1278 vs[4] = obj.matrix_world @ Vector(( r,0.0,-h*0.5+r))
1279 vs[5] = obj.matrix_world @ Vector(( -r,0.0,-h*0.5+r))
1280 vs[6] = obj.matrix_world @ Vector((0.0, r , h*0.5-r))
1281 vs[7] = obj.matrix_world @ Vector((0.0,-r , h*0.5-r))
1282 vs[8] = obj.matrix_world @ Vector((0.0, r ,-h*0.5+r))
1283 vs[9] = obj.matrix_world @ Vector((0.0,-r ,-h*0.5+r))
1284
1285 indices = [(0,1),(2,3),(4,5),(6,7),(8,9)]
1286
1287 for l in indices:
1288 v0 = vs[l[0]]
1289 v1 = vs[l[1]]
1290 verts += [(v0[0],v0[1],v0[2])]
1291 verts += [(v1[0],v1[1],v1[2])]
1292 colours += [(0.5,1,0,1),(0.5,1,0,1)]
1293
1294 elif obj.cv_data.classtype == 'k_classtype_spawn':
1295 vs = [None]*4
1296 vs[0] = obj.matrix_world @ Vector((0,0,0))
1297 vs[1] = obj.matrix_world @ Vector((0,2,0))
1298 vs[2] = obj.matrix_world @ Vector((0.5,1,0))
1299 vs[3] = obj.matrix_world @ Vector((-0.5,1,0))
1300 indices = [(0,1),(1,2),(1,3)]
1301 for l in indices:
1302 v0 = vs[l[0]]
1303 v1 = vs[l[1]]
1304 verts += [(v0[0],v0[1],v0[2])]
1305 verts += [(v1[0],v1[1],v1[2])]
1306 colours += [(0,1,1,1),(0,1,1,1)]
1307
1308 elif obj.cv_data.classtype == 'k_classtype_route':
1309 vs = [None]*2
1310 vs[0] = obj.location
1311 vs[1] = obj.cv_data.target.location
1312 indices = [(0,1)]
1313 for l in indices:
1314 v0 = vs[l[0]]
1315 v1 = vs[l[1]]
1316 verts += [(v0[0],v0[1],v0[2])]
1317 verts += [(v1[0],v1[1],v1[2])]
1318 colours += [(0,1,1,1),(0,1,1,1)]
1319
1320 stack = [None]*64
1321 stack_i = [0]*64
1322 stack[0] = obj.cv_data.target
1323 si = 1
1324 loop_complete = False
1325
1326 while si > 0:
1327 if stack_i[si-1] == 2:
1328 si -= 1
1329 continue
1330
1331 if si == 0: # Loop failed to complete
1332 break
1333
1334 node = stack[si-1]
1335
1336 targets = [None,None]
1337 targets[0] = node.cv_data.target
1338
1339 if node.cv_data.classtype == 'k_classtype_route_node':
1340 targets[1] = node.cv_data.target1
1341
1342 nextnode = targets[stack_i[si-1]]
1343 stack_i[si-1] += 1
1344
1345 if nextnode != None: # branch
1346 if nextnode == stack[0]: # Loop completed
1347 loop_complete = True
1348 break
1349
1350 valid=True
1351 for sj in range(si):
1352 if stack[sj] == nextnode: # invalidated path
1353 valid=False
1354 break
1355
1356 if valid:
1357 stack_i[si] = 0
1358 stack[si] = nextnode
1359 si += 1
1360 continue
1361
1362 if loop_complete:
1363 cc = Vector((obj.cv_data.colour[0],\
1364 obj.cv_data.colour[1],\
1365 obj.cv_data.colour[2],\
1366 1.0))
1367
1368 for sj in range(si):
1369 sk = (sj+1)%si
1370
1371 if stack[sj].cv_data.classtype == 'k_classtype_gate' and \
1372 stack[sk].cv_data.classtype == 'k_classtype_gate':
1373 dist = (stack[sj].location-stack[sk].location).magnitude
1374 drawsbpath( stack[sj], stack[sk], cc*0.4, cc, dist, dist )
1375
1376 else:
1377 drawbpath( stack[sj], stack[sk], cc, cc )
1378
1379 course_count += 1
1380
1381 elif obj.cv_data.classtype == 'k_classtype_car_path':
1382 v0 = obj.matrix_world.to_quaternion() @ Vector((0,1,0))
1383 c0 = Vector((v0.x*0.5+0.5, v0.y*0.5+0.5, 0.0, 1.0))
1384 drawbhandle( obj, 1.0, (0.9,0.9,0.9,1.0) )
1385
1386 if obj.cv_data.target != None:
1387 v1 = obj.cv_data.target.matrix_world.to_quaternion()@Vector((0,1,0))
1388 c1 = Vector((v1.x*0.5+0.5, v1.y*0.5+0.5, 0.0, 1.0))
1389
1390 drawbhandle( obj.cv_data.target, -1.0, (0.5,0.5,0.5,1.0) )
1391 drawbpath( obj, obj.cv_data.target, c0, c1 )
1392
1393 if obj.cv_data.target1 != None:
1394 v1 = obj.cv_data.target1.matrix_world.to_quaternion()@Vector((0,1,0))
1395 c1 = Vector((v1.x*0.5+0.5, v1.y*0.5+0.5, 0.0, 1.0))
1396
1397 drawbhandle( obj.cv_data.target1, -1.0, (0.5,0.5,0.5,1.0) )
1398 drawbpath( obj, obj.cv_data.target1, c0, c1 )
1399
1400 lines = batch_for_shader(\
1401 cv_view_shader, 'LINES', \
1402 { "pos":verts, "color":colours })
1403
1404 lines.draw( cv_view_shader )
1405
1406 def cv_poll_target(scene, obj):
1407 if obj == bpy.context.active_object:
1408 return False
1409 if obj.cv_data.classtype == 'k_classtype_none':
1410 return False
1411 return True
1412
1413 class CV_MESH_SETTINGS(bpy.types.PropertyGroup):
1414 v0: bpy.props.FloatVectorProperty(name="v0",size=3)
1415 v1: bpy.props.FloatVectorProperty(name="v1",size=3)
1416 v2: bpy.props.FloatVectorProperty(name="v2",size=3)
1417 v3: bpy.props.FloatVectorProperty(name="v3",size=3)
1418
1419 class CV_OBJ_SETTINGS(bpy.types.PropertyGroup):
1420 uid: bpy.props.IntProperty( name="" )
1421
1422 strp: bpy.props.StringProperty( name="strp" )
1423 intp: bpy.props.IntProperty( name="intp" )
1424 fltp: bpy.props.FloatProperty( name="fltp" )
1425
1426 target: bpy.props.PointerProperty( type=bpy.types.Object, name="target", \
1427 poll=cv_poll_target )
1428 target1: bpy.props.PointerProperty( type=bpy.types.Object, name="target1", \
1429 poll=cv_poll_target )
1430
1431 colour: bpy.props.FloatVectorProperty(name="colour",subtype='COLOR',\
1432 min=0.0,max=1.0)
1433
1434 classtype: bpy.props.EnumProperty(
1435 name="Format",
1436 items = [
1437 ('k_classtype_none', "k_classtype_none", "", 0),
1438 ('k_classtype_gate', "k_classtype_gate", "", 1),
1439 ('k_classtype_block', "k_classtype_block", "", 2),
1440 ('k_classtype_spawn', "k_classtype_spawn", "", 3),
1441 ('k_classtype_water', "k_classtype_water", "", 4),
1442 ('k_classtype_car_path', "k_classtype_car_path", "", 5),
1443 ('k_classtype_INSTANCE', "","", 6 ),
1444 ('k_classtype_capsule', "k_classtype_capsule", "", 7 ),
1445 ('k_classtype_route_node', "k_classtype_route_node", "", 8 ),
1446 ('k_classtype_route', "k_classtype_route", "", 9 ),
1447 ('k_classtype_bone',"k_classtype_bone","",10),
1448 ('k_classtype_SKELETON', "","", 11 ),
1449 ('k_classtype_SKIN',"","",12),
1450 ('k_classtype_achievement_box',"k_classtype_achievement_box","",13),
1451 ('k_classtype_audio',"k_classtype_audio","",14),
1452 ])
1453
1454 class CV_BONE_SETTINGS(bpy.types.PropertyGroup):
1455 collider: bpy.props.BoolProperty(name="Collider",default=False)
1456 v0: bpy.props.FloatVectorProperty(name="v0",size=3)
1457 v1: bpy.props.FloatVectorProperty(name="v1",size=3)
1458
1459 con0: bpy.props.BoolProperty(name="Constriant 0",default=False)
1460 mins: bpy.props.FloatVectorProperty(name="mins",size=3)
1461 maxs: bpy.props.FloatVectorProperty(name="maxs",size=3)
1462
1463 class CV_BONE_PANEL(bpy.types.Panel):
1464 bl_label="Bone Config"
1465 bl_idname="SCENE_PT_cv_bone"
1466 bl_space_type='PROPERTIES'
1467 bl_region_type='WINDOW'
1468 bl_context='bone'
1469
1470 def draw(_,context):
1471 active_object = context.active_object
1472 if active_object == None: return
1473
1474 bone = active_object.data.bones.active
1475 if bone == None: return
1476
1477 _.layout.prop( bone.cv_data, "collider" )
1478 _.layout.prop( bone.cv_data, "v0" )
1479 _.layout.prop( bone.cv_data, "v1" )
1480
1481 _.layout.label( text="Angle Limits" )
1482 _.layout.prop( bone.cv_data, "con0" )
1483 _.layout.prop( bone.cv_data, "mins" )
1484 _.layout.prop( bone.cv_data, "maxs" )
1485
1486 class CV_SCENE_SETTINGS(bpy.types.PropertyGroup):
1487 use_hidden: bpy.props.BoolProperty( name="use hidden", default=False )
1488
1489 class CV_OBJ_PANEL(bpy.types.Panel):
1490 bl_label="Entity Config"
1491 bl_idname="SCENE_PT_cv_entity"
1492 bl_space_type='PROPERTIES'
1493 bl_region_type='WINDOW'
1494 bl_context="object"
1495
1496 def draw(_,context):
1497 active_object = bpy.context.active_object
1498 if active_object == None: return
1499 _.layout.prop( active_object.cv_data, "classtype" )
1500
1501 if active_object.cv_data.classtype == 'k_classtype_gate':
1502 _.layout.prop( active_object.cv_data, "target" )
1503
1504 mesh = active_object.data
1505 _.layout.label( text=F"(i) Data is stored in {mesh.name}" )
1506 _.layout.prop( mesh.cv_data, "v0" )
1507
1508 elif active_object.cv_data.classtype == 'k_classtype_car_path' or \
1509 active_object.cv_data.classtype == 'k_classtype_route_node':
1510 _.layout.prop( active_object.cv_data, "target" )
1511 _.layout.prop( active_object.cv_data, "target1" )
1512
1513 elif active_object.cv_data.classtype == 'k_classtype_route':
1514 _.layout.prop( active_object.cv_data, "target" )
1515 _.layout.prop( active_object.cv_data, "colour" )
1516
1517 elif active_object.cv_data.classtype == 'k_classtype_block':
1518 mesh = active_object.data
1519
1520 _.layout.label( text=F"(i) Data is stored in {mesh.name}" )
1521 _.layout.prop( mesh.cv_data, "v0" )
1522 _.layout.prop( mesh.cv_data, "v1" )
1523 _.layout.prop( mesh.cv_data, "v2" )
1524 _.layout.prop( mesh.cv_data, "v3" )
1525 elif active_object.cv_data.classtype == 'k_classtype_capsule':
1526 mesh = active_object.data
1527 _.layout.label( text=F"(i) Data is stored in {mesh.name}" )
1528 _.layout.prop( mesh.cv_data, "v0" )
1529 elif active_object.cv_data.classtype == 'k_classtype_achievement_box':
1530 _.layout.prop( active_object.cv_data, "strp" )
1531 _.layout.prop( active_object.cv_data, "target" )
1532 elif active_object.cv_data.classtype == 'k_classtype_audio':
1533 _.layout.prop( active_object.cv_data, "strp" )
1534 _.layout.prop( active_object.cv_data, "intp" )
1535 _.layout.prop( active_object.cv_data, "fltp" )
1536
1537 class CV_INTERFACE(bpy.types.Panel):
1538 bl_idname = "VIEW3D_PT_carve"
1539 bl_label = "Carve"
1540 bl_space_type = 'VIEW_3D'
1541 bl_region_type = 'UI'
1542 bl_category = "Carve"
1543
1544 def draw(_, context):
1545 layout = _.layout
1546 layout.prop( context.scene.cv_data, "use_hidden")
1547 layout.operator( "carve.compile_all" )
1548
1549 def test_compile():
1550 view_layer = bpy.context.view_layer
1551 for col in view_layer.layer_collection.children["export"].children:
1552 if not col.hide_viewport or bpy.context.scene.cv_data.use_hidden:
1553 write_model( col.name )
1554
1555 class CV_COMPILE(bpy.types.Operator):
1556 bl_idname="carve.compile_all"
1557 bl_label="Compile All"
1558
1559 def execute(_,context):
1560 test_compile()
1561 #cProfile.runctx("test_compile()",globals(),locals(),sort=1)
1562 #for col in bpy.data.collections["export"].children:
1563 # write_model( col.name )
1564
1565 return {'FINISHED'}
1566
1567 classes = [CV_OBJ_SETTINGS,CV_OBJ_PANEL,CV_COMPILE,CV_INTERFACE,\
1568 CV_MESH_SETTINGS, CV_SCENE_SETTINGS, CV_BONE_SETTINGS,\
1569 CV_BONE_PANEL]
1570
1571 def register():
1572 global cv_view_draw_handler
1573
1574 for c in classes:
1575 bpy.utils.register_class(c)
1576
1577 bpy.types.Object.cv_data = bpy.props.PointerProperty(type=CV_OBJ_SETTINGS)
1578 bpy.types.Mesh.cv_data = bpy.props.PointerProperty(type=CV_MESH_SETTINGS)
1579 bpy.types.Scene.cv_data = bpy.props.PointerProperty(type=CV_SCENE_SETTINGS)
1580 bpy.types.Bone.cv_data = bpy.props.PointerProperty(type=CV_BONE_SETTINGS)
1581
1582 cv_view_draw_handler = bpy.types.SpaceView3D.draw_handler_add(\
1583 cv_draw,(),'WINDOW','POST_VIEW')
1584
1585 def unregister():
1586 global cv_view_draw_handler
1587
1588 for c in classes:
1589 bpy.utils.unregister_class(c)
1590
1591 bpy.types.SpaceView3D.draw_handler_remove(cv_view_draw_handler,'WINDOW')