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