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