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