small bugs
[carveJwlIkooP6JGAAIwe30JlM.git] / blender_export.py
1 import bpy, math, gpu, os
2 import cProfile
3 from ctypes import *
4 from mathutils import *
5 from gpu_extras.batch import batch_for_shader
6 from bpy_extras import mesh_utils
7
8 bl_info = {
9 "name":"Skaterift .mdl exporter",
10 "author": "Harry Godden (hgn)",
11 "version": (0,2),
12 "blender":(3,1,0),
13 "location":"Export",
14 "description":"",
15 "warning":"",
16 "wiki_url":"",
17 "category":"Import/Export",
18 }
19
20 sr_entity_alias = {
21 'ent_gate': 1,
22 'ent_spawn': 2,
23 'ent_route_node': 3,
24 'ent_route': 4,
25 'ent_water': 5,
26 'ent_volume': 6,
27 'ent_audio': 7,
28 'ent_marker': 8,
29 'ent_glyph': 9
30 }
31
32 class mdl_vert(Structure): # 48 bytes. Quite large. Could compress
33 #{ # the normals and uvs to i16s. Not an
34 _pack_ = 1 # real issue, yet.
35 _fields_ = [("co",c_float*3),
36 ("norm",c_float*3),
37 ("uv",c_float*2),
38 ("colour",c_uint8*4),
39 ("weights",c_uint16*4),
40 ("groups",c_uint8*4)]
41 #}
42
43 class mdl_transform(Structure):
44 #{
45 _fields_ = [("co",c_float*3),
46 ( "s",c_float*3),
47 ( "q",c_float*4)]
48 #}
49
50 class mdl_submesh(Structure):
51 #{
52 _fields_ = [("indice_start",c_uint32),
53 ("indice_count",c_uint32),
54 ("vertex_start",c_uint32),
55 ("vertex_count",c_uint32),
56 ("bbx",(c_float*3)*2),
57 ("material_id",c_uint32)] # index into the material array
58 #}
59
60 class mdl_material(Structure):
61 #{
62 _fields_ = [("pstr_name",c_uint32),
63 ("shader",c_uint32),
64 ("flags",c_uint32),
65 ("surface_prop",c_uint32),
66 ("colour",c_float*4),
67 ("colour1",c_float*4),
68 ("tex_diffuse",c_uint32),
69 ("tex_none0",c_uint32),
70 ("tex_none1",c_uint32)]
71 #}
72
73 class mdl_bone(Structure):
74 #{
75 _fields_ = [("co",c_float*3),("end",c_float*3),
76 ("parent",c_uint32),
77 ("collider",c_uint32),
78 ("ik_target",c_uint32),
79 ("ik_pole",c_uint32),
80 ("flags",c_uint32),
81 ("pstr_name",c_uint32),
82 ("hitbox",(c_float*3)*2),
83 ("conevx",c_float*3),("conevy",c_float*3),("coneva",c_float*3),
84 ("conet",c_float)]
85 #}
86
87 class mdl_armature(Structure):
88 #{
89 _fields_ = [("transform",mdl_transform),
90 ("bone_start",c_uint32),
91 ("bone_count",c_uint32),
92 ("anim_start",c_uint32),
93 ("anim_count",c_uint32)]
94 #}
95
96 class mdl_animation(Structure):
97 #{
98 _fields_ = [("pstr_name",c_uint32),
99 ("length",c_uint32),
100 ("rate",c_float),
101 ("keyframe_start",c_uint32)]
102 #}
103
104 class mdl_mesh(Structure):
105 #{
106 _fields_ = [("transform",mdl_transform),
107 ("submesh_start",c_uint32),
108 ("submesh_count",c_uint32),
109 ("pstr_name",c_uint32),
110 ("flags",c_uint32),
111 ("armature_id",c_uint32)]
112 #}
113
114 class mdl_file(Structure):
115 #{
116 _fields_ = [("path",c_uint32),
117 ("pack_offset",c_uint32),
118 ("pack_size",c_uint32)]
119 #}
120
121 class mdl_texture(Structure):
122 #{
123 _fields_ = [("file",mdl_file),
124 ("type",c_uint32)]
125 #}
126
127 class mdl_array(Structure):
128 #{
129 _fields_ = [("file_offset",c_uint32),
130 ("item_count",c_uint32),
131 ("item_size",c_uint32),
132 ("name",c_byte*16)]
133 #}
134
135 class mdl_header(Structure):
136 #{
137 _fields_ = [("version",c_uint32),
138 ("arrays",mdl_array)]
139 #}
140
141 class ent_spawn(Structure):
142 #{
143 _fields_ = [("transform",mdl_transform),
144 ("pstr_name",c_uint32)]
145 #}
146
147 class ent_light(Structure):
148 #{
149 _fields_ = [("transform",mdl_transform),
150 ("daytime",c_uint32),
151 ("type",c_uint32),
152 ("colour",c_float*4),
153 ("angle",c_float),
154 ("range",c_float),
155 ("inverse_world",(c_float*3)*4), # Runtime
156 ("angle_sin_cos",(c_float*2))] # Runtime
157 #}
158
159 class version_refcount_union(Union):
160 #{
161 _fields_ = [("timing_version",c_uint32),
162 ("ref_count",c_uint8)]
163 #}
164
165 class ent_gate(Structure):
166 #{
167 _fields_ = [("type",c_uint32),
168 ("target", c_uint32),
169 ("dimensions", c_float*3),
170 ("co", (c_float*3)*2),
171 ("q", (c_float*4)*2),
172 ("to_world",(c_float*3)*4),
173 ("transport",(c_float*3)*4),
174 ("_anonymous_union",version_refcount_union),
175 ("timing_time",c_double),
176 ("routes",c_uint16*4),
177 ("route_count",c_uint8)]
178 #}
179
180 class ent_route_node(Structure):
181 #{
182 _fields_ = [("co",c_float*3),
183 ("ref_count",c_uint8),
184 ("ref_total",c_uint8)]
185 #}
186
187 class ent_path_index(Structure):
188 #{
189 _fields_ = [("index",c_uint16)]
190 #}
191
192 class vg_audio_clip(Structure):
193 #{
194 _fields_ = [("path",c_uint64),
195 ("flags",c_uint32),
196 ("size",c_uint32),
197 ("data",c_uint64)]
198 #}
199
200 class union_file_audio_clip(Union):
201 #{
202 _fields_ = [("file",mdl_file),
203 ("reserved",vg_audio_clip)]
204 #}
205
206 class ent_audio_clip(Structure):
207 #{
208 _fields_ = [("_anon",union_file_audio_clip),
209 ("probability",c_float)]
210 #}
211
212 class ent_checkpoint(Structure):
213 #{
214 _fields_ = [("gate_index",c_uint16),
215 ("path_start",c_uint16),
216 ("path_count",c_uint16)]
217 #}
218
219 class ent_route(Structure):
220 #{
221 _fields_ = [("transform",mdl_transform),
222 ("pstr_name",c_uint32),
223 ("checkpoints_start",c_uint16),
224 ("checkpoints_count",c_uint16),
225 ("colour",c_float*4),
226 ("active",c_uint32), #runtime
227 ("factive",c_float),
228 ("board_transform",(c_float*3)*4),
229 ("sm",mdl_submesh),
230 ("latest_pass",c_double)]
231 #}
232
233 class ent_water(Structure):
234 #{
235 _fields_ = [("transform",mdl_transform),
236 ("max_dist",c_float),
237 ("reserved0",c_uint32),
238 ("reserved1",c_uint32)]
239 #}
240
241 class volume_trigger(Structure):
242 #{
243 _fields_ = [("event",c_uint32),
244 ("blank",c_uint32)]
245 #}
246
247 class volume_particles(Structure):
248 #{
249 _fields_ = [("blank",c_uint32),
250 ("blank2",c_uint32)]
251 #}
252
253 class volume_union(Union):
254 #{
255 _fields_ = [("trigger",volume_trigger),
256 ("particles",volume_particles)]
257 #}
258
259 class ent_index(Structure):
260 #{
261 _fields_ = [("type",c_uint32),
262 ("index",c_uint32)]
263 #}
264
265 class ent_volume(Structure):
266 #{
267 _fields_ = [("transform",mdl_transform),
268 ("to_world",(c_float*3)*4),
269 ("to_local",(c_float*3)*4),
270 ("type",c_uint32),
271 ("target",ent_index),
272 ("_anon",volume_union)]
273 #}
274
275 class ent_audio(Structure):
276 #{
277 _fields_ = [("transform",mdl_transform),
278 ("flags",c_uint32),
279 ("clip_start",c_uint32),
280 ("clip_count",c_uint32),
281 ("volume",c_float),
282 ("crossfade",c_float),
283 ("channel_behaviour",c_uint32),
284 ("group",c_uint32),
285 ("probability_curve",c_uint32),
286 ("max_channels",c_uint32)]
287 #}
288
289 class ent_marker(Structure):
290 #{
291 _fields_ = [("transform",mdl_transform),
292 ("name",c_uint32)]
293 #}
294
295 class ent_glyph(Structure):
296 #{
297 _fields_ = [("size",c_float*2),
298 ("indice_start",c_uint32),
299 ("indice_count",c_uint32)]
300 #}
301
302 class ent_font_variant(Structure):
303 #{
304 _fields_ = [("name",c_uint32),
305 ("material_id",c_uint32)]
306 #}
307
308 class ent_font(Structure):
309 #{
310 _fields_ = [("alias",c_uint32),
311 ("variant_start",c_uint32),
312 ("variant_count",c_uint32),
313 ("glyph_start",c_uint32),
314 ("glyph_count",c_uint32),
315 ("glyph_utf32_base",c_uint32)]
316 #}
317
318 def obj_ent_type( obj ):
319 #{
320 if obj.type == 'ARMATURE': return 'mdl_armature'
321 elif obj.type == 'LIGHT': return 'ent_light'
322 else: return obj.SR_data.ent_type
323 #}
324
325 def sr_filter_ent_type( obj, ent_types ):
326 #{
327 if obj == bpy.context.active_object: return False
328
329 for c0 in obj.users_collection:#{
330 for c1 in bpy.context.active_object.users_collection:#{
331 if c0 == c1:#{
332 return obj_ent_type( obj ) in ent_types
333 #}
334 #}
335 #}
336
337 return False
338 #}
339
340 def compile_obj_transform( obj, transform ):
341 #{
342 co = obj.matrix_world @ Vector((0,0,0))
343 q = obj.matrix_local.to_quaternion()
344 s = obj.scale
345
346 # Setup transform
347 #
348 transform.co[0] = co[0]
349 transform.co[1] = co[2]
350 transform.co[2] = -co[1]
351 transform.q[0] = q[1]
352 transform.q[1] = q[3]
353 transform.q[2] = -q[2]
354 transform.q[3] = q[0]
355 transform.s[0] = s[0]
356 transform.s[1] = s[2]
357 transform.s[2] = s[1]
358 #}
359
360 def int_align_to( v, align ):
361 #{
362 while(v%align)!=0: v += 1
363 return v
364 #}
365
366 def bytearray_align_to( buffer, align, w=b'\xaa' ):
367 #{
368 while (len(buffer) % align) != 0: buffer.extend(w)
369 return buffer
370 #}
371
372 def bytearray_print_hex( s, w=16 ):
373 #{
374 for r in range((len(s)+(w-1))//w):#{
375 i0=(r+0)*w
376 i1=min((r+1)*w,len(s))
377 print( F'{r*w:06x}| \x1B[31m', end='')
378 print( F"{' '.join('{:02x}'.format(x) for x in s[i0:i1]):<48}",end='' )
379 print( "\x1B[0m", end='')
380 print( ''.join(chr(x) if (x>=33 and x<=126) else '.' for x in s[i0:i1] ) )
381 #}
382 #}
383
384 def sr_compile_string( s ):
385 #{
386 if s in sr_compile.string_cache: return sr_compile.string_cache[s]
387
388 index = len( sr_compile.string_data )
389 sr_compile.string_cache[s] = index
390 sr_compile.string_data.extend( s.encode('utf-8') )
391 sr_compile.string_data.extend( b'\0' )
392
393 bytearray_align_to( sr_compile.string_data, 4 )
394 return index
395 #}
396
397 def material_tex_image(v):
398 #{
399 return {
400 "Image Texture":
401 {
402 "image": F"{v}"
403 }
404 }
405 #}
406
407 cxr_graph_mapping = \
408 {
409 # Default shader setup
410 "Principled BSDF":
411 {
412 "Base Color":
413 {
414 "Image Texture":
415 {
416 "image": "tex_diffuse"
417 },
418 "Mix":
419 {
420 "A": material_tex_image("tex_diffuse"),
421 "B": material_tex_image("tex_decal")
422 },
423 },
424 "Normal":
425 {
426 "Normal Map":
427 {
428 "Color": material_tex_image("tex_normal")
429 }
430 }
431 }
432 }
433
434 # https://harrygodden.com/git/?p=convexer.git;a=blob;f=__init__.py;#l1164
435 #
436 def material_info(mat):
437 #{
438 info = {}
439
440 # Using the cxr_graph_mapping as a reference, go through the shader
441 # graph and gather all $props from it.
442 #
443 def _graph_read( node_def, node=None, depth=0 ):#{
444 nonlocal mat
445 nonlocal info
446
447 # Find rootnodes
448 #
449 if node == None:#{
450 _graph_read.extracted = []
451
452 for node_idname in node_def:#{
453 for n in mat.node_tree.nodes:#{
454 if n.name == node_idname:#{
455 node_def = node_def[node_idname]
456 node = n
457 break
458 #}
459 #}
460 #}
461 #}
462
463 for link in node_def:#{
464 link_def = node_def[link]
465
466 if isinstance( link_def, dict ):#{
467 node_link = None
468 for x in node.inputs:#{
469 if isinstance( x, bpy.types.NodeSocketColor ):#{
470 if link == x.name:#{
471 node_link = x
472 break
473 #}
474 #}
475 #}
476
477 if node_link and node_link.is_linked:#{
478 # look for definitions for the connected node type
479 #
480 from_node = node_link.links[0].from_node
481
482 node_name = from_node.name.split('.')[0]
483 if node_name in link_def:#{
484 from_node_def = link_def[ node_name ]
485
486 _graph_read( from_node_def, from_node, depth+1 )
487 #}
488 #}
489 else:#{
490 if "default" in link_def:#{
491 prop = link_def['default']
492 info[prop] = node_link.default_value
493 #}
494 #}
495 #}
496 else:#{
497 prop = link_def
498 info[prop] = getattr( node, link )
499 #}
500 #}
501 #}
502
503 _graph_read( cxr_graph_mapping )
504 return info
505 #}
506
507 def vg_str_bin( s ):
508 #{
509 decoded = bytearray()
510 for i in range(len(s)//2):#{
511 c = (ord(s[i*2+0])-0x41)
512 c |= (ord(s[i*2+1])-0x41)<<4
513 decoded.extend(bytearray(c_uint8(c))) #??
514 #}
515 return decoded
516 #}
517
518 def sr_pack_file( file, path, data ):
519 #{
520 file.path = sr_compile_string( path )
521 file.pack_offset = len( sr_compile.pack_data )
522 file.pack_size = len( data )
523
524 sr_compile.pack_data.extend( data )
525 bytearray_align_to( sr_compile.pack_data, 16 )
526 #}
527
528 def sr_compile_texture( img ):
529 #{
530 if img == None:
531 return 0
532
533 name = os.path.splitext( img.name )[0]
534
535 if name in sr_compile.texture_cache:
536 return sr_compile.texture_cache[name]
537
538 texture_index = (len(sr_compile.texture_data)//sizeof(mdl_texture)) +1
539
540 tex = mdl_texture()
541 tex.type = 0
542
543 if sr_compile.pack_textures:#{
544 filedata = qoi_encode( img )
545 sr_pack_file( tex.file, name, filedata )
546 #}
547
548 sr_compile.texture_cache[name] = texture_index
549 sr_compile.texture_data.extend( bytearray(tex) )
550 return texture_index
551 #}
552
553 def sr_compile_material( mat ):
554 #{
555 if mat == None:
556 return 0
557 if mat.name in sr_compile.material_cache:
558 return sr_compile.material_cache[mat.name]
559
560 index = (len(sr_compile.material_data)//sizeof(mdl_material))+1
561 sr_compile.material_cache[mat.name] = index
562
563 m = mdl_material()
564 m.pstr_name = sr_compile_string( mat.name )
565
566 flags = 0x00
567 if mat.SR_data.collision:#{
568 flags |= 0x2
569 if mat.SR_data.skate_surface: flags |= 0x1
570 if mat.SR_data.grind_surface: flags |= (0x8|0x1)
571 #}
572
573 if mat.SR_data.grow_grass: flags |= 0x4
574 m.flags = flags
575
576 m.surface_prop = int(mat.SR_data.surface_prop)
577
578 if mat.SR_data.shader == 'standard': m.shader = 0
579 if mat.SR_data.shader == 'standard_cutout': m.shader = 1
580 if mat.SR_data.shader == 'terrain_blend':#{
581 m.shader = 2
582
583 m.colour[0] = pow( mat.SR_data.sand_colour[0], 1.0/2.2 )
584 m.colour[1] = pow( mat.SR_data.sand_colour[1], 1.0/2.2 )
585 m.colour[2] = pow( mat.SR_data.sand_colour[2], 1.0/2.2 )
586 m.colour[3] = 1.0
587
588 m.colour1[0] = mat.SR_data.blend_offset[0]
589 m.colour1[1] = mat.SR_data.blend_offset[1]
590 #}
591
592 if mat.SR_data.shader == 'vertex_blend':#{
593 m.shader = 3
594
595 m.colour1[0] = mat.SR_data.blend_offset[0]
596 m.colour1[1] = mat.SR_data.blend_offset[1]
597 #}
598
599 if mat.SR_data.shader == 'water':#{
600 m.shader = 4
601
602 m.colour[0] = pow( mat.SR_data.shore_colour[0], 1.0/2.2 )
603 m.colour[1] = pow( mat.SR_data.shore_colour[1], 1.0/2.2 )
604 m.colour[2] = pow( mat.SR_data.shore_colour[2], 1.0/2.2 )
605 m.colour[3] = 1.0
606 m.colour1[0] = pow( mat.SR_data.ocean_colour[0], 1.0/2.2 )
607 m.colour1[1] = pow( mat.SR_data.ocean_colour[1], 1.0/2.2 )
608 m.colour1[2] = pow( mat.SR_data.ocean_colour[2], 1.0/2.2 )
609 m.colour1[3] = 1.0
610 #}
611
612 inf = material_info( mat )
613
614 if mat.SR_data.shader == 'standard' or \
615 mat.SR_data.shader == 'standard_cutout' or \
616 mat.SR_data.shader == 'terrain_blend' or \
617 mat.SR_data.shader == 'vertex_blend':
618 #{
619 if 'tex_diffuse' in inf:
620 m.tex_diffuse = sr_compile_texture(inf['tex_diffuse'])
621 #}
622
623 sr_compile.material_data.extend( bytearray(m) )
624 return index
625 #}
626
627 def sr_armature_bones( armature ):
628 #{
629 def _recurse_bone( b ):
630 #{
631 yield b
632 for c in b.children: yield from _recurse_bone( c )
633 #}
634
635 for b in armature.data.bones:
636 if not b.parent:
637 yield from _recurse_bone( b )
638 #}
639
640 def sr_compile_mesh( obj ):
641 #{
642 node=mdl_mesh()
643 compile_obj_transform(obj, node.transform)
644 node.pstr_name = sr_compile_string(obj.name)
645 node.flags = 0
646
647 can_use_cache = True
648 armature = None
649
650 for mod in obj.modifiers:#{
651 if mod.type == 'DATA_TRANSFER' or mod.type == 'SHRINKWRAP' or \
652 mod.type == 'BOOLEAN' or mod.type == 'CURVE' or \
653 mod.type == 'ARRAY':
654 #{
655 can_use_cache = False
656 #}
657
658 if mod.type == 'ARMATURE': #{
659 node.flags = 1
660 armature = mod.object
661 rig_weight_groups = \
662 ['0 [ROOT]']+[_.name for _ in sr_armature_bones(mod.object)]
663 node.armature_id = sr_compile.entity_ids[armature.name]
664
665 POSE_OR_REST_CACHE = armature.data.pose_position
666 armature.data.pose_position = 'REST'
667 #}
668 #}
669
670 # Check the cache first
671 #
672 if can_use_cache and (obj.data.name in sr_compile.mesh_cache):#{
673 ref = sr_compile.mesh_cache[obj.data.name]
674 node.submesh_start = ref[0]
675 node.submesh_count = ref[1]
676 sr_compile.mesh_data.extend(bytearray(node))
677 return
678 #}
679
680 # Compile a whole new mesh
681 #
682 node.submesh_start = len(sr_compile.submesh_data)//sizeof(mdl_submesh)
683 node.submesh_count = 0
684
685 dgraph = bpy.context.evaluated_depsgraph_get()
686 data = obj.evaluated_get(dgraph).data
687 data.calc_loop_triangles()
688 data.calc_normals_split()
689
690 # Mesh is split into submeshes based on their material
691 #
692 mat_list = data.materials if len(data.materials) > 0 else [None]
693 for material_id, mat in enumerate(mat_list): #{
694 mref = {}
695
696 sm = mdl_submesh()
697 sm.indice_start = len(sr_compile.indice_data)//sizeof(c_uint32)
698 sm.vertex_start = len(sr_compile.vertex_data)//sizeof(mdl_vert)
699 sm.vertex_count = 0
700 sm.indice_count = 0
701 sm.material_id = sr_compile_material( mat )
702
703 INF=99999999.99999999
704 for i in range(3):#{
705 sm.bbx[0][i] = INF
706 sm.bbx[1][i] = -INF
707 #}
708
709 # Keep a reference to very very very similar vertices
710 # i have no idea how to speed it up.
711 #
712 vertex_reference = {}
713
714 # Write the vertex / indice data
715 #
716 for tri_index, tri in enumerate(data.loop_triangles):#{
717 if tri.material_index != material_id: continue
718
719 for j in range(3):#{
720 vert = data.vertices[tri.vertices[j]]
721 li = tri.loops[j]
722 vi = data.loops[li].vertex_index
723
724 # Gather vertex information
725 #
726 co = vert.co
727 norm = data.loops[li].normal
728 uv = (0,0)
729 colour = (255,255,255,255)
730 groups = [0,0,0,0]
731 weights = [0,0,0,0]
732
733 # Uvs
734 #
735 if data.uv_layers:
736 uv = data.uv_layers.active.data[li].uv
737
738 # Vertex Colours
739 #
740 if data.vertex_colors:#{
741 colour = data.vertex_colors.active.data[li].color
742 colour = (int(colour[0]*255.0),\
743 int(colour[1]*255.0),\
744 int(colour[2]*255.0),\
745 int(colour[3]*255.0))
746 #}
747
748 # Weight groups: truncates to the 3 with the most influence. The
749 # fourth bone ID is never used by the shader so it
750 # is always 0
751 #
752 if armature:#{
753 src_groups = [_ for _ in data.vertices[vi].groups \
754 if obj.vertex_groups[_.group].name in \
755 rig_weight_groups ]
756
757 weight_groups = sorted( src_groups, key = \
758 lambda a: a.weight, reverse=True )
759 tot = 0.0
760 for ml in range(3):#{
761 if len(weight_groups) > ml:#{
762 g = weight_groups[ml]
763 name = obj.vertex_groups[g.group].name
764 weight = g.weight
765 weights[ml] = weight
766 groups[ml] = rig_weight_groups.index(name)
767 tot += weight
768 #}
769 #}
770
771 if len(weight_groups) > 0:#{
772 inv_norm = (1.0/tot) * 65535.0
773 for ml in range(3):#{
774 weights[ml] = int( weights[ml] * inv_norm )
775 weights[ml] = min( weights[ml], 65535 )
776 weights[ml] = max( weights[ml], 0 )
777 #}
778 #}
779 #}
780 else:#{
781 li1 = tri.loops[(j+1)%3]
782 vi1 = data.loops[li1].vertex_index
783 e0 = data.edges[ data.loops[li].edge_index ]
784
785 if e0.use_freestyle_mark and \
786 ((e0.vertices[0] == vi and e0.vertices[1] == vi1) or \
787 (e0.vertices[0] == vi1 and e0.vertices[1] == vi)):
788 #{
789 weights[0] = 1
790 #}
791 #}
792
793 TOLERENCE = float(10**4)
794 key = (int(co[0]*TOLERENCE+0.5),
795 int(co[1]*TOLERENCE+0.5),
796 int(co[2]*TOLERENCE+0.5),
797 int(norm[0]*TOLERENCE+0.5),
798 int(norm[1]*TOLERENCE+0.5),
799 int(norm[2]*TOLERENCE+0.5),
800 int(uv[0]*TOLERENCE+0.5),
801 int(uv[1]*TOLERENCE+0.5),
802 colour[0], # these guys are already quantized
803 colour[1], # .
804 colour[2], # .
805 colour[3], # .
806 weights[0], # v
807 weights[1],
808 weights[2],
809 weights[3],
810 groups[0],
811 groups[1],
812 groups[2],
813 groups[3])
814
815 if key in vertex_reference:
816 index = vertex_reference[key]
817 else:#{
818 index = bytearray(c_uint32(sm.vertex_count))
819 sm.vertex_count+=1
820
821 vertex_reference[key] = index
822 v = mdl_vert()
823 v.co[0] = co[0]
824 v.co[1] = co[2]
825 v.co[2] = -co[1]
826 v.norm[0] = norm[0]
827 v.norm[1] = norm[2]
828 v.norm[2] = -norm[1]
829 v.uv[0] = uv[0]
830 v.uv[1] = uv[1]
831 v.colour[0] = colour[0]
832 v.colour[1] = colour[1]
833 v.colour[2] = colour[2]
834 v.colour[3] = colour[3]
835 v.weights[0] = weights[0]
836 v.weights[1] = weights[1]
837 v.weights[2] = weights[2]
838 v.weights[3] = weights[3]
839 v.groups[0] = groups[0]
840 v.groups[1] = groups[1]
841 v.groups[2] = groups[2]
842 v.groups[3] = groups[3]
843
844 for i in range(3):#{
845 sm.bbx[0][i] = min( sm.bbx[0][i], v.co[i] )
846 sm.bbx[1][i] = max( sm.bbx[1][i], v.co[i] )
847 #}
848
849 sr_compile.vertex_data.extend(bytearray(v))
850 #}
851
852 sm.indice_count += 1
853 sr_compile.indice_data.extend( index )
854 #}
855 #}
856
857 # Make sure bounding box isn't -inf -> inf if no vertices
858 #
859 if sm.vertex_count == 0:
860 for j in range(2):
861 for i in range(3):
862 sm.bbx[j][i] = 0
863
864 # Add submesh to encoder
865 #
866 sr_compile.submesh_data.extend( bytearray(sm) )
867 node.submesh_count += 1
868 #}
869
870 if armature:#{
871 armature.data.pose_position = POSE_OR_REST_CACHE
872 #}
873
874 # Save a reference to this node since we want to reuse the submesh indices
875 # later.
876 sr_compile.mesh_cache[obj.data.name]=(node.submesh_start,node.submesh_count)
877 sr_compile.mesh_data.extend(bytearray(node))
878 #}
879
880 def sr_compile_fonts( collection ):
881 #{
882 print( F"[SR] Compiling fonts" )
883
884 glyph_count = 0
885 variant_count = 0
886
887 for obj in collection.all_objects:#{
888 if obj_ent_type(obj) != 'ent_font': continue
889
890 data = obj.SR_data.ent_font[0]
891
892 font=ent_font()
893 font.alias = sr_compile_string( data.alias )
894 font.variant_start = variant_count
895 font.variant_count = 0
896 font.glyph_start = glyph_count
897
898 glyph_base = data.glyphs[0].utf32
899 glyph_range = data.glyphs[-1].utf32 - glyph_base
900
901 font.glyph_utf32_base = glyph_base
902 font.glyph_count = glyph_range
903
904 for i in range(len(data.variants)):#{
905 data_var = data.variants[i]
906 if not data_var.mesh: continue
907
908 mesh = data_var.mesh.data
909
910 variant = ent_font_variant()
911 variant.name = sr_compile_string( data_var.tipo )
912
913 # fonts (variants) only support one material each
914 mat = None
915 if len(mesh.materials) != 0:
916 mat = mesh.materials[0]
917 variant.material_id = sr_compile_material( mat )
918
919 font.variant_count += 1
920
921 islands = mesh_utils.mesh_linked_triangles(mesh)
922 centroids = [Vector((0,0)) for _ in range(len(islands))]
923
924 for j in range(len(islands)):#{
925 for tri in islands[j]:#{
926 centroids[j].x += tri.center[0]
927 centroids[j].y += tri.center[2]
928 #}
929
930 centroids[j] /= len(islands[j])
931 #}
932
933 for j in range(glyph_range):#{
934 data_glyph = data.glyphs[j]
935 glyph = ent_glyph()
936 glyph.indice_start = len(sr_compile.indice_data)//sizeof(c_uint32)
937 glyph.indice_count = 0
938 glyph.size[0] = data_glyph.bounds[2]
939 glyph.size[1] = data_glyph.bounds[3]
940
941 vertex_reference = {}
942
943 for k in range(len(islands)):#{
944 if centroids[k].x < data_glyph.bounds[0] or \
945 centroids[k].x > data_glyph.bounds[0]+data_glyph.bounds[2] or\
946 centroids[k].y < data_glyph.bounds[1] or \
947 centroids[k].y > data_glyph.bounds[1]+data_glyph.bounds[3]:
948 #{
949 continue
950 #}
951
952 for l in range(len(islands[k])):#{
953 tri = islands[k][l]
954 for m in range(3):#{
955 vert = mesh.vertices[tri.vertices[m]]
956 li = tri.loops[m]
957 vi = mesh.loops[li].vertex_index
958
959 # Gather vertex information
960 #
961 co = [vert.co[_] for _ in range(3)]
962 co[0] -= data_glyph.bounds[0]
963 co[2] -= data_glyph.bounds[1]
964 norm = mesh.loops[li].normal
965 uv = (0,0)
966 if mesh.uv_layers: uv = mesh.uv_layers.active.data[li].uv
967
968 TOLERENCE = float(10**4)
969 key = (int(co[0]*TOLERENCE+0.5),
970 int(co[1]*TOLERENCE+0.5),
971 int(co[2]*TOLERENCE+0.5),
972 int(norm[0]*TOLERENCE+0.5),
973 int(norm[1]*TOLERENCE+0.5),
974 int(norm[2]*TOLERENCE+0.5),
975 int(uv[0]*TOLERENCE+0.5),
976 int(uv[1]*TOLERENCE+0.5))
977
978 if key in vertex_reference:
979 index = vertex_reference[key]
980 else:#{
981 vindex = len(sr_compile.vertex_data)//sizeof(mdl_vert)
982 index = bytearray(c_uint32(vindex))
983 vertex_reference[key] = index
984 v = mdl_vert()
985 v.co[0] = co[0]
986 v.co[1] = co[2]
987 v.co[2] = -co[1]
988 v.norm[0] = norm[0]
989 v.norm[1] = norm[2]
990 v.norm[2] = -norm[1]
991 v.uv[0] = uv[0]
992 v.uv[1] = uv[1]
993
994 sr_compile.vertex_data.extend(bytearray(v))
995 #}
996
997 glyph.indice_count += 1
998 sr_compile.indice_data.extend( index )
999 #}
1000 #}
1001 #}
1002 sr_ent_push( glyph )
1003 #}
1004 sr_ent_push( variant )
1005 #}
1006 sr_ent_push( font )
1007 #}
1008 #}
1009
1010 def sr_compile_armature( obj ):
1011 #{
1012 node = mdl_armature()
1013 node.bone_start = len(sr_compile.bone_data)//sizeof(mdl_bone)
1014 node.bone_count = 0
1015 node.anim_start = len(sr_compile.anim_data)//sizeof(mdl_animation)
1016 node.anim_count = 0
1017
1018 bones = [_ for _ in sr_armature_bones(obj)]
1019 bones_names = [None]+[_.name for _ in bones]
1020
1021 for b in bones:#{
1022 bone = mdl_bone()
1023 if b.use_deform: bone.flags = 0x1
1024 if b.parent: bone.parent = bones_names.index(b.parent.name)
1025
1026 bone.collider = int(b.SR_data.collider)
1027
1028 if bone.collider>0:#{
1029 bone.hitbox[0][0] = b.SR_data.collider_min[0]
1030 bone.hitbox[0][1] = b.SR_data.collider_min[2]
1031 bone.hitbox[0][2] = -b.SR_data.collider_max[1]
1032 bone.hitbox[1][0] = b.SR_data.collider_max[0]
1033 bone.hitbox[1][1] = b.SR_data.collider_max[2]
1034 bone.hitbox[1][2] = -b.SR_data.collider_min[1]
1035 #}
1036
1037 if b.SR_data.cone_constraint:#{
1038 bone.flags |= 0x4
1039 bone.conevx[0] = b.SR_data.conevx[0]
1040 bone.conevx[1] = b.SR_data.conevx[2]
1041 bone.conevx[2] = -b.SR_data.conevx[1]
1042 bone.conevy[0] = b.SR_data.conevy[0]
1043 bone.conevy[1] = b.SR_data.conevy[2]
1044 bone.conevy[2] = -b.SR_data.conevy[1]
1045 bone.coneva[0] = b.SR_data.coneva[0]
1046 bone.coneva[1] = b.SR_data.coneva[2]
1047 bone.coneva[2] = -b.SR_data.coneva[1]
1048 bone.conet = b.SR_data.conet
1049 #}
1050
1051 bone.co[0] = b.head_local[0]
1052 bone.co[1] = b.head_local[2]
1053 bone.co[2] = -b.head_local[1]
1054 bone.end[0] = b.tail_local[0] - bone.co[0]
1055 bone.end[1] = b.tail_local[2] - bone.co[1]
1056 bone.end[2] = -b.tail_local[1] - bone.co[2]
1057 bone.pstr_name = sr_compile_string( b.name )
1058
1059 for c in obj.pose.bones[b.name].constraints:#{
1060 if c.type == 'IK':#{
1061 bone.flags |= 0x2
1062 bone.ik_target = bones_names.index(c.subtarget)
1063 bone.ik_pole = bones_names.index(c.pole_subtarget)
1064 #}
1065 #}
1066
1067 node.bone_count += 1
1068 sr_compile.bone_data.extend(bytearray(bone))
1069 #}
1070
1071 # Compile anims
1072 #
1073 if obj.animation_data and sr_compile.pack_animations: #{
1074 # So we can restore later
1075 #
1076 previous_frame = bpy.context.scene.frame_current
1077 previous_action = obj.animation_data.action
1078 POSE_OR_REST_CACHE = obj.data.pose_position
1079 obj.data.pose_position = 'POSE'
1080
1081 for NLALayer in obj.animation_data.nla_tracks:#{
1082 for NLAStrip in NLALayer.strips:#{
1083 # set active
1084 #
1085 for a in bpy.data.actions:#{
1086 if a.name == NLAStrip.name:#{
1087 obj.animation_data.action = a
1088 break
1089 #}
1090 #}
1091
1092 # Clip to NLA settings
1093 #
1094 anim_start = int(NLAStrip.action_frame_start)
1095 anim_end = int(NLAStrip.action_frame_end)
1096
1097 # Export strips
1098 #
1099 anim = mdl_animation()
1100 anim.pstr_name = sr_compile_string( NLAStrip.action.name )
1101 anim.rate = 30.0
1102 anim.keyframe_start = len(sr_compile.keyframe_data)//\
1103 sizeof(mdl_transform)
1104 anim.length = anim_end-anim_start
1105
1106 i = 0
1107 # Export the keyframes
1108 for frame in range(anim_start,anim_end):#{
1109 bpy.context.scene.frame_set(frame)
1110
1111 for rb in bones:#{
1112 pb = obj.pose.bones[rb.name]
1113
1114 # relative bone matrix
1115 if rb.parent is not None:#{
1116 offset_mtx = rb.parent.matrix_local
1117 offset_mtx = offset_mtx.inverted_safe() @ \
1118 rb.matrix_local
1119
1120 inv_parent = pb.parent.matrix @ offset_mtx
1121 inv_parent.invert_safe()
1122 fpm = inv_parent @ pb.matrix
1123 #}
1124 else:#{
1125 bone_mtx = rb.matrix.to_4x4()
1126 local_inv = rb.matrix_local.inverted_safe()
1127 fpm = bone_mtx @ local_inv @ pb.matrix
1128 #}
1129
1130 loc, rot, sca = fpm.decompose()
1131
1132 # rotation
1133 lc_m = pb.matrix_channel.to_3x3()
1134 if pb.parent is not None:#{
1135 smtx = pb.parent.matrix_channel.to_3x3()
1136 lc_m = smtx.inverted() @ lc_m
1137 #}
1138 rq = lc_m.to_quaternion()
1139
1140 kf = mdl_transform()
1141 kf.co[0] = loc[0]
1142 kf.co[1] = loc[2]
1143 kf.co[2] = -loc[1]
1144 kf.q[0] = rq[1]
1145 kf.q[1] = rq[3]
1146 kf.q[2] = -rq[2]
1147 kf.q[3] = rq[0]
1148 kf.s[0] = sca[0]
1149 kf.s[1] = sca[1]
1150 kf.s[2] = sca[2]
1151 sr_compile.keyframe_data.extend(bytearray(kf))
1152
1153 i+=1
1154 #}
1155 #}
1156
1157 # Add to animation buffer
1158 #
1159 sr_compile.anim_data.extend(bytearray(anim))
1160 node.anim_count += 1
1161
1162 # Report progress
1163 #
1164 print( F"[SR] | anim( {NLAStrip.action.name} )" )
1165 #}
1166 #}
1167
1168 # Restore context to how it was before
1169 #
1170 bpy.context.scene.frame_set( previous_frame )
1171 obj.animation_data.action = previous_action
1172 obj.data.pose_position = POSE_OR_REST_CACHE
1173 #}
1174
1175 sr_compile.armature_data.extend(bytearray(node))
1176 #}
1177
1178 def sr_ent_push( struct ):
1179 #{
1180 clase = type(struct).__name__
1181
1182 if clase not in sr_compile.entity_data:#{
1183 sr_compile.entity_data[ clase ] = bytearray()
1184 sr_compile.entity_info[ clase ] = { 'size': sizeof(struct) }
1185 #}
1186
1187 index = len(sr_compile.entity_data[ clase ])//sizeof(struct)
1188 sr_compile.entity_data[ clase ].extend( bytearray(struct) )
1189 return index
1190 #}
1191
1192 def sr_array_title( arr, name, count, size, offset ):
1193 #{
1194 for i in range(len(name)):#{
1195 arr.name[i] = ord(name[i])
1196 #}
1197 arr.file_offset = offset
1198 arr.item_count = count
1199 arr.item_size = size
1200 #}
1201
1202 def sr_compile( collection ):
1203 #{
1204 print( F"[SR] compiler begin ({collection.name}.mdl)" )
1205
1206 #settings
1207 sr_compile.pack_textures = collection.SR_data.pack_textures
1208 sr_compile.pack_animations = collection.SR_data.animations
1209
1210 # caches
1211 sr_compile.string_cache = {}
1212 sr_compile.mesh_cache = {}
1213 sr_compile.material_cache = {}
1214 sr_compile.texture_cache = {}
1215
1216 # compiled data
1217 sr_compile.mesh_data = bytearray()
1218 sr_compile.submesh_data = bytearray()
1219 sr_compile.vertex_data = bytearray()
1220 sr_compile.indice_data = bytearray()
1221 sr_compile.bone_data = bytearray()
1222 sr_compile.material_data = bytearray()
1223 sr_compile.armature_data = bytearray()
1224 sr_compile.anim_data = bytearray()
1225 sr_compile.keyframe_data = bytearray()
1226 sr_compile.texture_data = bytearray()
1227
1228 # just bytes not structures
1229 sr_compile.string_data = bytearray()
1230 sr_compile.pack_data = bytearray()
1231
1232 # variable
1233 sr_compile.entity_data = {}
1234 sr_compile.entity_info = {}
1235
1236 print( F"[SR] assign entity ID's" )
1237 sr_compile.entities = {}
1238 sr_compile.entity_ids = {}
1239
1240 mesh_count = 0
1241 for obj in collection.all_objects: #{
1242 if obj.type == 'MESH': mesh_count += 1
1243
1244 ent_type = obj_ent_type( obj )
1245 if ent_type == 'none': continue
1246
1247 if ent_type not in sr_compile.entities: sr_compile.entities[ent_type] = []
1248 sr_compile.entity_ids[obj.name] = len( sr_compile.entities[ent_type] )
1249 sr_compile.entities[ent_type] += [obj]
1250 #}
1251
1252 print( F"[SR] Compiling geometry" )
1253 i=0
1254 for obj in collection.all_objects:#{
1255 if obj.type == 'MESH':#{
1256 i+=1
1257 print( F'[SR] {i: 3}/{mesh_count} {obj.name:<40}', end='\r' )
1258 sr_compile_mesh( obj )
1259 #}
1260 #}
1261
1262 audio_clip_count = 0
1263
1264 for ent_type, arr in sr_compile.entities.items():#{
1265 print(F"[SR] Compiling {len(arr)} {ent_type}{'s' if len(arr)>1 else ''}")
1266
1267 for i in range(len(arr)):#{
1268 obj = arr[i]
1269
1270 print( F"[SR] {i+1: 3}/{len(arr)} {obj.name:<40} ",end='\r' )
1271
1272 if ent_type == 'mdl_armature': sr_compile_armature(obj)
1273 elif ent_type == 'ent_light': #{
1274 light = ent_light()
1275 compile_obj_transform( obj, light.transform )
1276 light.daytime = obj.data.SR_data.daytime
1277 if obj.data.type == 'POINT':#{
1278 light.type = 0
1279 #}
1280 elif obj.data.type == 'SPOT':#{
1281 light.type = 1
1282 light.angle = obj.data.spot_size*0.5
1283 #}
1284 light.range = obj.data.cutoff_distance
1285 light.colour[0] = obj.data.color[0]
1286 light.colour[1] = obj.data.color[1]
1287 light.colour[2] = obj.data.color[2]
1288 light.colour[3] = obj.data.energy
1289 sr_ent_push( light )
1290 #}
1291 elif ent_type == 'ent_gate': #{
1292 gate = ent_gate()
1293 gate.type = 0
1294 obj_data = obj.SR_data.ent_gate[0]
1295 mesh_data = obj.data.SR_data.ent_gate[0]
1296 if obj_data.target:#{
1297 gate.target = sr_compile.entity_ids[obj_data.target.name]
1298 gate.type = 1
1299 #}
1300 gate.dimensions[0] = mesh_data.dimensions[0]
1301 gate.dimensions[1] = mesh_data.dimensions[1]
1302 gate.dimensions[2] = mesh_data.dimensions[2]
1303
1304 q = [obj.matrix_local.to_quaternion(), (0,0,0,1)]
1305 co = [obj.matrix_world @ Vector((0,0,0)), (0,0,0)]
1306
1307 if obj_data.target:#{
1308 q[1] = obj_data.target.matrix_local.to_quaternion()
1309 co[1]= obj_data.target.matrix_world @ Vector((0,0,0))
1310 #}
1311
1312 # Setup transform
1313 #
1314 for x in range(2):#{
1315 gate.co[x][0] = co[x][0]
1316 gate.co[x][1] = co[x][2]
1317 gate.co[x][2] = -co[x][1]
1318 gate.q[x][0] = q[x][1]
1319 gate.q[x][1] = q[x][3]
1320 gate.q[x][2] = -q[x][2]
1321 gate.q[x][3] = q[x][0]
1322 #}
1323
1324 sr_ent_push( gate )
1325 #}
1326 elif ent_type == 'ent_spawn': #{
1327 spawn = ent_spawn()
1328 compile_obj_transform( obj, spawn.transform )
1329 obj_data = obj.SR_data.ent_spawn[0]
1330 spawn.pstr_name = sr_compile_string( obj_data.alias )
1331 sr_ent_push( spawn )
1332 #}
1333 elif ent_type == 'ent_water':#{
1334 water = ent_water()
1335 compile_obj_transform( obj, water.transform )
1336 water.max_dist = 0.0
1337 sr_ent_push( water )
1338 #}
1339 elif ent_type == 'ent_audio':#{
1340 obj_data = obj.SR_data.ent_audio[0]
1341 audio = ent_audio()
1342 compile_obj_transform( obj, audio.transform )
1343 audio.clip_start = audio_clip_count
1344 audio.clip_count = len(obj_data.files)
1345 audio_clip_count += audio.clip_count
1346 audio.max_channels = obj_data.max_channels
1347 audio.volume = obj_data.volume
1348
1349 # TODO flags:
1350 # - allow/disable doppler
1351 # - channel group tags with random colours
1352 # - transition properties
1353
1354 if obj_data.flag_loop: audio.flags |= 0x1
1355 if obj_data.flag_nodoppler: audio.flags |= 0x2
1356 if obj_data.flag_3d: audio.flags |= 0x4
1357 if obj_data.flag_auto: audio.flags |= 0x8
1358 if obj_data.formato == '0': audio.flags |= 0x000
1359 elif obj_data.formato == '1': audio.flags |= 0x400
1360 elif obj_data.formato == '2': audio.flags |= 0x1000
1361
1362 audio.channel_behaviour = int(obj_data.channel_behaviour)
1363 if audio.channel_behaviour >= 1:#{
1364 audio.group = obj_data.group
1365 #}
1366 if audio.channel_behaviour == 2:#{
1367 audio.crossfade = obj_data.transition_duration
1368 #}
1369 audio.probability_curve = int(obj_data.probability_curve)
1370
1371 for ci in range(audio.clip_count):#{
1372 entry = obj_data.files[ci]
1373 clip = ent_audio_clip()
1374 clip.probability = entry.probability
1375 if obj_data.formato == '2':#{
1376 sr_pack_file( clip._anon.file, '', vg_str_bin(entry.path) )
1377 #}
1378 else:#{
1379 clip._anon.file.path = sr_compile_string( entry.path )
1380 clip._anon.file.pack_offset = 0
1381 clip._anon.file.pack_size = 0
1382 #}
1383 sr_ent_push( clip )
1384 #}
1385 sr_ent_push( audio )
1386 #}
1387 elif ent_type == 'ent_volume':#{
1388 obj_data = obj.SR_data.ent_volume[0]
1389 volume = ent_volume()
1390 volume.type = int(obj_data.subtype)
1391 compile_obj_transform( obj, volume.transform )
1392
1393 if obj_data.target:#{
1394 target = obj_data.target
1395 volume.target.type = sr_entity_alias[obj_ent_type(target)]
1396 volume.target.index = sr_compile.entity_ids[ target.name ]
1397 #}
1398
1399 sr_ent_push(volume)
1400 #}
1401 elif ent_type == 'ent_marker':#{
1402 marker = ent_marker()
1403 marker.name = sr_compile_string( obj.SR_data.ent_marker[0].alias )
1404 compile_obj_transform( obj, marker.transform )
1405 sr_ent_push(marker)
1406 #}
1407 #}
1408 #}
1409
1410 sr_compile_fonts(collection)
1411
1412 def _children( col ):#{
1413 yield col
1414 for c in col.children:#{
1415 yield from _children(c)
1416 #}
1417 #}
1418
1419 checkpoint_count = 0
1420 pathindice_count = 0
1421 routenode_count = 0
1422
1423 for col in _children(collection):#{
1424 print( F"Adding routes for subcollection: {col.name}" )
1425 route_gates = []
1426 route_curves = []
1427 routes = []
1428
1429 for obj in col.objects:#{
1430 if obj.type == 'ARMATURE': pass
1431 else:#{
1432 ent_type = obj_ent_type( obj )
1433
1434 if ent_type == 'ent_gate':
1435 route_gates += [obj]
1436 elif ent_type == 'ent_route_node':#{
1437 if obj.type == 'CURVE':#{
1438 route_curves += [obj]
1439 #}
1440 #}
1441 elif ent_type == 'ent_route':
1442 routes += [obj]
1443 #}
1444 #}
1445
1446 dij = create_node_graph( route_curves, route_gates )
1447
1448 for obj in routes:#{
1449 obj_data = obj.SR_data.ent_route[0]
1450 route = ent_route()
1451 route.pstr_name = sr_compile_string( obj_data.alias )
1452 route.checkpoints_start = checkpoint_count
1453 route.checkpoints_count = 0
1454
1455 for ci in range(3):
1456 route.colour[ci] = obj_data.colour[ci]
1457 route.colour[3] = 1.0
1458
1459 compile_obj_transform( obj, route.transform )
1460 checkpoints = obj_data.gates
1461
1462 for i in range(len(checkpoints)):#{
1463 gi = checkpoints[i].target
1464 gj = checkpoints[(i+1)%len(checkpoints)].target
1465 gate = gi
1466
1467 if gi:#{
1468 dest = gi.SR_data.ent_gate[0].target
1469 gi = dest
1470 #}
1471
1472 if gi==gj: continue # error?
1473 if not gi or not gj: continue
1474
1475 checkpoint = ent_checkpoint()
1476 checkpoint.gate_index = sr_compile.entity_ids[gate.name]
1477 checkpoint.path_start = pathindice_count
1478 checkpoint.path_count = 0
1479
1480 path = solve_graph( dij, gi.name, gj.name )
1481
1482 if path:#{
1483 for pi in range(len(path)):#{
1484 pathindice = ent_path_index()
1485 pathindice.index = routenode_count + path[pi]
1486 sr_ent_push( pathindice )
1487
1488 checkpoint.path_count += 1
1489 pathindice_count += 1
1490 #}
1491 #}
1492
1493 sr_ent_push( checkpoint )
1494 route.checkpoints_count += 1
1495 checkpoint_count += 1
1496 #}
1497
1498 sr_ent_push( route )
1499 #}
1500
1501 for point in dij.points:#{
1502 rn = ent_route_node()
1503 rn.co[0] = point[0]
1504 rn.co[1] = point[2]
1505 rn.co[2] = -point[1]
1506 sr_ent_push( rn )
1507 #}
1508
1509 routenode_count += len(dij.points)
1510 #}
1511
1512
1513 print( F"[SR] Writing file" )
1514
1515 file_array_instructions = {}
1516 file_offset = 0
1517
1518 def _write_array( name, item_size, data ):#{
1519 nonlocal file_array_instructions, file_offset
1520
1521 count = len(data)//item_size
1522 file_array_instructions[name] = {'count':count, 'size':item_size,\
1523 'data':data, 'offset': file_offset}
1524 file_offset += len(data)
1525 file_offset = int_align_to( file_offset, 8 )
1526 #}
1527
1528 _write_array( 'strings', 1, sr_compile.string_data )
1529 _write_array( 'mdl_mesh', sizeof(mdl_mesh), sr_compile.mesh_data )
1530 _write_array( 'mdl_submesh', sizeof(mdl_submesh), sr_compile.submesh_data )
1531 _write_array( 'mdl_material', sizeof(mdl_material), sr_compile.material_data)
1532 _write_array( 'mdl_texture', sizeof(mdl_texture), sr_compile.texture_data)
1533 _write_array( 'mdl_armature', sizeof(mdl_armature), sr_compile.armature_data)
1534 _write_array( 'mdl_bone', sizeof(mdl_bone), sr_compile.bone_data )
1535
1536 for name, buffer in sr_compile.entity_data.items():#{
1537 _write_array( name, sr_compile.entity_info[name]['size'], buffer )
1538 #}
1539
1540 _write_array( 'mdl_animation', sizeof(mdl_animation), sr_compile.anim_data)
1541 _write_array( 'mdl_keyframe', sizeof(mdl_transform),sr_compile.keyframe_data)
1542 _write_array( 'mdl_vert', sizeof(mdl_vert), sr_compile.vertex_data )
1543 _write_array( 'mdl_indice', sizeof(c_uint32), sr_compile.indice_data )
1544 _write_array( 'pack', 1, sr_compile.pack_data )
1545
1546 header_size = int_align_to( sizeof(mdl_header), 8 )
1547 index_size = int_align_to( sizeof(mdl_array)*len(file_array_instructions),8 )
1548
1549 folder = bpy.path.abspath(bpy.context.scene.SR_data.export_dir)
1550 path = F"{folder}{collection.name}.mdl"
1551 print( path )
1552
1553 fp = open( path, "wb" )
1554 header = mdl_header()
1555 header.version = 40
1556 sr_array_title( header.arrays, \
1557 'index', len(file_array_instructions), \
1558 sizeof(mdl_array), header_size )
1559
1560 fp.write( bytearray_align_to( bytearray(header), 8 ) )
1561
1562 print( F'[SR] {"name":>16}| count | offset' )
1563 index = bytearray()
1564 for name,info in file_array_instructions.items():#{
1565 arr = mdl_array()
1566 offset = info['offset'] + header_size + index_size
1567 sr_array_title( arr, name, info['count'], info['size'], offset )
1568 index.extend( bytearray(arr) )
1569
1570 print( F'[SR] {name:>16}| {info["count"]: 8} '+\
1571 F' 0x{info["offset"]:02x}' )
1572 #}
1573 fp.write( bytearray_align_to( index, 8 ) )
1574 #bytearray_print_hex( index )
1575
1576 for name,info in file_array_instructions.items():#{
1577 fp.write( bytearray_align_to( info['data'], 8 ) )
1578 #}
1579
1580 fp.close()
1581
1582 print( '[SR] done' )
1583 #}
1584
1585 class SR_SCENE_SETTINGS(bpy.types.PropertyGroup):
1586 #{
1587 use_hidden: bpy.props.BoolProperty( name="use hidden", default=False )
1588 export_dir: bpy.props.StringProperty( name="Export Dir", subtype='DIR_PATH' )
1589 gizmos: bpy.props.BoolProperty( name="Draw Gizmos", default=True )
1590
1591 panel: bpy.props.EnumProperty(
1592 name='Panel',
1593 description='',
1594 items=[
1595 ('EXPORT', 'Export', '', 'MOD_BUILD',0),
1596 ('ENTITY', 'Entity', '', 'MONKEY',1),
1597 ('SETTINGS', 'Settings', 'Settings', 'PREFERENCES',2),
1598 ],
1599 )
1600 #}
1601
1602 class SR_COLLECTION_SETTINGS(bpy.types.PropertyGroup):
1603 #{
1604 pack_textures: bpy.props.BoolProperty( name="Pack Textures", default=False )
1605 animations: bpy.props.BoolProperty( name="Export animation", default=True)
1606 #}
1607
1608 def sr_get_mirror_bone( bones ):
1609 #{
1610 side = bones.active.name[-1:]
1611 other_name = bones.active.name[:-1]
1612 if side == 'L': other_name += 'R'
1613 elif side == 'R': other_name += 'L'
1614 else: return None
1615
1616 for b in bones:#{
1617 if b.name == other_name:
1618 return b
1619 #}
1620
1621 return None
1622 #}
1623
1624 class SR_MIRROR_BONE_X(bpy.types.Operator):
1625 #{
1626 bl_idname="skaterift.mirror_bone"
1627 bl_label="Mirror bone attributes - SkateRift"
1628
1629 def execute(_,context):
1630 #{
1631 active_object = context.active_object
1632 bones = active_object.data.bones
1633 a = bones.active
1634 b = sr_get_mirror_bone( bones )
1635
1636 if not b: return {'FINISHED'}
1637
1638 b.SR_data.collider = a.SR_data.collider
1639
1640 def _v3copyflipy( a, b ):#{
1641 b[0] = a[0]
1642 b[1] = -a[1]
1643 b[2] = a[2]
1644 #}
1645
1646 _v3copyflipy( a.SR_data.collider_min, b.SR_data.collider_min )
1647 _v3copyflipy( a.SR_data.collider_max, b.SR_data.collider_max )
1648 b.SR_data.collider_min[1] = -a.SR_data.collider_max[1]
1649 b.SR_data.collider_max[1] = -a.SR_data.collider_min[1]
1650
1651 b.SR_data.cone_constraint = a.SR_data.cone_constraint
1652
1653 _v3copyflipy( a.SR_data.conevx, b.SR_data.conevy )
1654 _v3copyflipy( a.SR_data.conevy, b.SR_data.conevx )
1655 _v3copyflipy( a.SR_data.coneva, b.SR_data.coneva )
1656
1657 b.SR_data.conet = a.SR_data.conet
1658
1659 # redraw
1660 ob = bpy.context.scene.objects[0]
1661 ob.hide_render = ob.hide_render
1662 return {'FINISHED'}
1663 #}
1664 #}
1665
1666 class SR_COMPILE(bpy.types.Operator):
1667 #{
1668 bl_idname="skaterift.compile_all"
1669 bl_label="Compile All"
1670
1671 def execute(_,context):
1672 #{
1673 view_layer = bpy.context.view_layer
1674 for col in view_layer.layer_collection.children["export"].children:
1675 if not col.hide_viewport or bpy.context.scene.SR_data.use_hidden:
1676 sr_compile( bpy.data.collections[col.name] )
1677
1678 return {'FINISHED'}
1679 #}
1680 #}
1681
1682 class SR_COMPILE_THIS(bpy.types.Operator):
1683 #{
1684 bl_idname="skaterift.compile_this"
1685 bl_label="Compile This collection"
1686
1687 def execute(_,context):
1688 #{
1689 col = bpy.context.collection
1690 sr_compile( col )
1691
1692 return {'FINISHED'}
1693 #}
1694 #}
1695
1696 class SR_INTERFACE(bpy.types.Panel):
1697 #{
1698 bl_idname = "VIEW3D_PT_skate_rift"
1699 bl_label = "Skate Rift"
1700 bl_space_type = 'VIEW_3D'
1701 bl_region_type = 'UI'
1702 bl_category = "Skate Rift"
1703
1704 def draw(_, context):
1705 #{
1706 # Compiler section
1707
1708 row = _.layout.row()
1709 row.scale_y = 1.75
1710 row.prop( context.scene.SR_data, 'panel', expand=True )
1711
1712 if context.scene.SR_data.panel == 'SETTINGS': #{
1713 _.layout.prop( context.scene.SR_data, 'gizmos' )
1714 #}
1715 elif context.scene.SR_data.panel == 'EXPORT': #{
1716 _.layout.prop( context.scene.SR_data, "export_dir" )
1717 col = bpy.context.collection
1718
1719 found_in_export = False
1720 export_count = 0
1721 view_layer = bpy.context.view_layer
1722 for c1 in view_layer.layer_collection.children["export"].children: #{
1723 if not c1.hide_viewport or bpy.context.scene.SR_data.use_hidden:
1724 export_count += 1
1725
1726 if c1.name == col.name: #{
1727 found_in_export = True
1728 #}
1729 #}
1730
1731 box = _.layout.box()
1732 row = box.row()
1733 row.alignment = 'CENTER'
1734 row.scale_y = 1.5
1735
1736 if found_in_export: #{
1737 row.label( text=col.name + ".mdl" )
1738 box.prop( col.SR_data, "pack_textures" )
1739 box.prop( col.SR_data, "animations" )
1740 box.operator( "skaterift.compile_this" )
1741 #}
1742 else: #{
1743 row.enabled=False
1744 row.label( text=col.name )
1745
1746 row = box.row()
1747 row.enabled=False
1748 row.alignment = 'CENTER'
1749 row.scale_y = 1.5
1750 row.label( text="This collection is not in the export group" )
1751 #}
1752
1753 box = _.layout.box()
1754 row = box.row()
1755
1756 split = row.split( factor=0.3, align=True )
1757 split.prop( context.scene.SR_data, "use_hidden", text="hidden" )
1758
1759 row1 = split.row()
1760 if export_count == 0:
1761 row1.enabled=False
1762 row1.operator( "skaterift.compile_all", \
1763 text=F"Compile all ({export_count} collections)" )
1764 #}
1765 elif context.scene.SR_data.panel == 'ENTITY': #{
1766 active_object = context.active_object
1767 if not active_object: return
1768
1769 _.layout.operator( 'skaterift.copy_entity_data', \
1770 text=F'Copy entity data to {len(context.selected_objects)-1} '+\
1771 F'other objects' )
1772
1773 box = _.layout.box()
1774 row = box.row()
1775 row.alignment = 'CENTER'
1776 row.label( text=active_object.name )
1777 row.scale_y = 1.5
1778
1779 def _draw_prop_collection( data ): #{
1780 nonlocal box
1781 row = box.row()
1782 row.alignment = 'CENTER'
1783 row.enabled = False
1784 row.scale_y = 1.5
1785 row.label( text=F'{data[0]}' )
1786
1787 if hasattr(type(data[0]),'sr_inspector'):#{
1788 type(data[0]).sr_inspector( box, data )
1789 #}
1790 else:#{
1791 for a in data[0].__annotations__:
1792 box.prop( data[0], a )
1793 #}
1794 #}
1795
1796 if active_object.type == 'ARMATURE': #{
1797 if active_object.mode == 'POSE': #{
1798 bones = active_object.data.bones
1799 mb = sr_get_mirror_bone( bones )
1800 if mb:#{
1801 box.operator( "skaterift.mirror_bone", \
1802 text=F'Mirror attributes to {mb.name}' )
1803 #}
1804
1805 _draw_prop_collection( [bones.active.SR_data ] )
1806 #}
1807 else: #{
1808 row = box.row()
1809 row.alignment='CENTER'
1810 row.scale_y=2.0
1811 row.enabled=False
1812 row.label( text="Enter pose mode to modify bone properties" )
1813 #}
1814 #}
1815 elif active_object.type == 'LIGHT': #{
1816 _draw_prop_collection( [active_object.data.SR_data] )
1817 #}
1818 elif active_object.type in ['EMPTY','CURVE','MESH']:#{
1819 box.prop( active_object.SR_data, "ent_type" )
1820 ent_type = active_object.SR_data.ent_type
1821
1822 col = getattr( active_object.SR_data, ent_type, None )
1823 if col != None and len(col)!=0: _draw_prop_collection( col )
1824
1825 if active_object.type == 'MESH':#{
1826 col = getattr( active_object.data.SR_data, ent_type, None )
1827 if col != None and len(col)!=0: _draw_prop_collection( col )
1828 #}
1829 #}
1830 #}
1831 #}
1832 #}
1833
1834 class SR_MATERIAL_PANEL(bpy.types.Panel):
1835 #{
1836 bl_label="Skate Rift material"
1837 bl_idname="MATERIAL_PT_sr_material"
1838 bl_space_type='PROPERTIES'
1839 bl_region_type='WINDOW'
1840 bl_context="material"
1841
1842 def draw(_,context):
1843 #{
1844 active_object = bpy.context.active_object
1845 if active_object == None: return
1846 active_mat = active_object.active_material
1847 if active_mat == None: return
1848
1849 info = material_info( active_mat )
1850
1851 if 'tex_diffuse' in info:#{
1852 _.layout.label( icon='INFO', \
1853 text=F"{info['tex_diffuse'].name} will be compiled" )
1854 #}
1855
1856 _.layout.prop( active_mat.SR_data, "shader" )
1857 _.layout.prop( active_mat.SR_data, "surface_prop" )
1858 _.layout.prop( active_mat.SR_data, "collision" )
1859
1860 if active_mat.SR_data.collision:#{
1861 _.layout.prop( active_mat.SR_data, "skate_surface" )
1862 _.layout.prop( active_mat.SR_data, "grind_surface" )
1863 _.layout.prop( active_mat.SR_data, "grow_grass" )
1864 #}
1865
1866 if active_mat.SR_data.shader == "terrain_blend":#{
1867 box = _.layout.box()
1868 box.prop( active_mat.SR_data, "blend_offset" )
1869 box.prop( active_mat.SR_data, "sand_colour" )
1870 #}
1871 elif active_mat.SR_data.shader == "vertex_blend":#{
1872 box = _.layout.box()
1873 box.label( icon='INFO', text="Uses vertex colours, the R channel" )
1874 box.prop( active_mat.SR_data, "blend_offset" )
1875 #}
1876 elif active_mat.SR_data.shader == "water":#{
1877 box = _.layout.box()
1878 box.label( icon='INFO', text="Depth scale of 16 meters" )
1879 box.prop( active_mat.SR_data, "shore_colour" )
1880 box.prop( active_mat.SR_data, "ocean_colour" )
1881 #}
1882 #}
1883 #}
1884
1885 def sr_get_type_enum( scene, context ):
1886 #{
1887 items = [('none','None',"")]
1888 mesh_entities=['ent_gate','ent_water']
1889 point_entities=['ent_spawn','ent_route_node','ent_route']
1890
1891 for e in point_entities: items += [(e,e,'')]
1892
1893 if context.scene.SR_data.panel == 'ENTITY': #{
1894 if context.active_object.type == 'MESH': #{
1895 for e in mesh_entities: items += [(e,e,'')]
1896 #}
1897 #}
1898 else: #{
1899 for e in mesh_entities: items += [(e,e,'')]
1900 #}
1901
1902 return items
1903 #}
1904
1905 def sr_on_type_change( _, context ):
1906 #{
1907 obj = context.active_object
1908 ent_type = obj.SR_data.ent_type
1909 if ent_type == 'none': return
1910 if obj.type == 'MESH':#{
1911 col = getattr( obj.data.SR_data, ent_type, None )
1912 if col != None and len(col)==0: col.add()
1913 #}
1914
1915 col = getattr( obj.SR_data, ent_type, None )
1916 if col != None and len(col)==0: col.add()
1917 #}
1918
1919 class SR_OBJECT_ENT_SPAWN(bpy.types.PropertyGroup):
1920 #{
1921 alias: bpy.props.StringProperty( name='alias' )
1922 #}
1923
1924 class SR_OBJECT_ENT_GATE(bpy.types.PropertyGroup):
1925 #{
1926 target: bpy.props.PointerProperty( \
1927 type=bpy.types.Object, name="destination", \
1928 poll=lambda self,obj: sr_filter_ent_type(obj,['ent_gate']))
1929 #}
1930
1931 class SR_MESH_ENT_GATE(bpy.types.PropertyGroup):
1932 #{
1933 dimensions: bpy.props.FloatVectorProperty(name="dimensions",size=3)
1934 #}
1935
1936 class SR_OBJECT_ENT_ROUTE_ENTRY(bpy.types.PropertyGroup):
1937 #{
1938 target: bpy.props.PointerProperty( \
1939 type=bpy.types.Object, name='target', \
1940 poll=lambda self,obj: sr_filter_ent_type(obj,['ent_gate']))
1941 #}
1942
1943 class SR_UL_ROUTE_NODE_LIST(bpy.types.UIList):
1944 #{
1945 bl_idname = 'SR_UL_ROUTE_NODE_LIST'
1946
1947 def draw_item(_,context,layout,data,item,icon,active_data,active_propname):
1948 #{
1949 layout.prop( item, 'target', text='', emboss=False )
1950 #}
1951 #}
1952
1953 def internal_listdel_execute(self,context,ent_name,collection_name):
1954 #{
1955 active_object = context.active_object
1956 data = getattr(active_object.SR_data,ent_name)[0]
1957 lista = getattr(data,collection_name)
1958 index = getattr(data,F'{collection_name}_index')
1959
1960 lista.remove(index)
1961
1962 setattr(data,F'{collection_name}_index', min(max(0,index-1), len(lista)-1))
1963 return{'FINISHED'}
1964 #}
1965
1966 def internal_listadd_execute(self,context,ent_name,collection_name):
1967 #{
1968 active_object = context.active_object
1969 getattr(getattr(active_object.SR_data,ent_name)[0],collection_name).add()
1970 return{'FINISHED'}
1971 #}
1972
1973 def copy_propgroup( de, to ):
1974 #{
1975 for a in de.__annotations__:#{
1976 if isinstance(getattr(de,a), bpy.types.bpy_prop_collection):#{
1977 ca = getattr(de,a)
1978 cb = getattr(to,a)
1979
1980 while len(cb) != len(ca):#{
1981 if len(cb) < len(ca): cb.add()
1982 else: cb.remove(0)
1983 #}
1984 for i in range(len(ca)):#{
1985 copy_propgroup(ca[i],cb[i])
1986 #}
1987 #}
1988 else:#{
1989 setattr(to,a,getattr(de,a))
1990 #}
1991 #}
1992 #}
1993
1994 class SR_OT_COPY_ENTITY_DATA(bpy.types.Operator):
1995 #{
1996 bl_idname = "skaterift.copy_entity_data"
1997 bl_label = "Copy entity data"
1998
1999 def execute(self, context):#{
2000 data = context.active_object.SR_data
2001 new_type = data.ent_type
2002 print( F"Copy entity data from: {context.active_object.name}" )
2003
2004 for obj in context.selected_objects:#{
2005 if obj != context.active_object:#{
2006 print( F" To: {obj.name}" )
2007
2008 obj.SR_data.ent_type = new_type
2009
2010 if active_object.type == 'MESH':#{
2011 col = getattr( obj.data.SR_data, new_type, None )
2012 if col != None and len(col)==0: col.add()
2013 mdata = context.active_object.data.SR_data
2014 copy_propgroup( getattr(mdata,new_type)[0], col[0] )
2015 #}
2016
2017 col = getattr( obj.SR_data, new_type, None )
2018 if col != None and len(col)==0: col.add()
2019 copy_propgroup( getattr(data,new_type)[0], col[0] )
2020 #}
2021 #}
2022 return{'FINISHED'}
2023 #}
2024 #}
2025
2026 class SR_OT_ROUTE_LIST_NEW_ITEM(bpy.types.Operator):
2027 #{
2028 bl_idname = "skaterift.new_entry"
2029 bl_label = "Add gate"
2030
2031 def execute(self, context):#{
2032 return internal_listadd_execute(self,context,'ent_route','gates')
2033 #}
2034 #}
2035
2036 class SR_OT_ROUTE_LIST_DEL_ITEM(bpy.types.Operator):
2037 #{
2038 bl_idname = "skaterift.del_entry"
2039 bl_label = "Remove gate"
2040
2041 @classmethod
2042 def poll(cls, context):#{
2043 active_object = context.active_object
2044 if obj_ent_type(active_object) == 'ent_route':#{
2045 return active_object.SR_data.ent_route[0].gates
2046 #}
2047 else: return False
2048 #}
2049
2050 def execute(self, context):#{
2051 return internal_listdel_execute(self,context,'ent_route','gates')
2052 #}
2053 #}
2054
2055 class SR_OT_AUDIO_LIST_NEW_ITEM(bpy.types.Operator):
2056 #{
2057 bl_idname = "skaterift.al_new_entry"
2058 bl_label = "Add file"
2059
2060 def execute(self, context):#{
2061 return internal_listadd_execute(self,context,'ent_audio','files')
2062 #}
2063 #}
2064
2065 class SR_OT_AUDIO_LIST_DEL_ITEM(bpy.types.Operator):
2066 #{
2067 bl_idname = "skaterift.al_del_entry"
2068 bl_label = "Remove file"
2069
2070 @classmethod
2071 def poll(cls, context):#{
2072 active_object = context.active_object
2073 if obj_ent_type(active_object) == 'ent_audio':#{
2074 return active_object.SR_data.ent_audio[0].files
2075 #}
2076 else: return False
2077 #}
2078
2079 def execute(self, context):#{
2080 return internal_listdel_execute(self,context,'ent_audio','files')
2081 return{'FINISHED'}
2082 #}
2083 #}
2084
2085 class SR_OT_GLYPH_LIST_NEW_ITEM(bpy.types.Operator):
2086 #{
2087 bl_idname = "skaterift.gl_new_entry"
2088 bl_label = "Add glyph"
2089
2090 def execute(self, context):#{
2091 active_object = context.active_object
2092
2093 font = active_object.SR_data.ent_font[0]
2094 font.glyphs.add()
2095
2096 if len(font.glyphs) > 1:#{
2097 prev = font.glyphs[-2]
2098 cur = font.glyphs[-1]
2099
2100 cur.bounds = prev.bounds
2101 cur.utf32 = prev.utf32+1
2102 #}
2103
2104 return{'FINISHED'}
2105 #}
2106 #}
2107
2108 class SR_OT_GLYPH_LIST_DEL_ITEM(bpy.types.Operator):
2109 #{
2110 bl_idname = "skaterift.gl_del_entry"
2111 bl_label = "Remove Glyph"
2112
2113 @classmethod
2114 def poll(cls, context):#{
2115 active_object = context.active_object
2116 if obj_ent_type(active_object) == 'ent_font':#{
2117 return active_object.SR_data.ent_font[0].glyphs
2118 #}
2119 else: return False
2120 #}
2121
2122 def execute(self, context):#{
2123 return internal_listdel_execute(self,context,'ent_font','glyphs')
2124 #}
2125 #}
2126
2127 class SR_OT_GLYPH_LIST_MOVE_ITEM(bpy.types.Operator):
2128 #{
2129 bl_idname = "skaterift.gl_move_item"
2130 bl_label = "aa"
2131 direction: bpy.props.EnumProperty(items=(('UP', 'Up', ""),
2132 ('DOWN', 'Down', ""),))
2133
2134 @classmethod
2135 def poll(cls, context):#{
2136 active_object = context.active_object
2137 if obj_ent_type(active_object) == 'ent_font':#{
2138 return active_object.SR_data.ent_font[0].glyphs
2139 #}
2140 else: return False
2141 #}
2142
2143 def execute(_, context):#{
2144 active_object = context.active_object
2145 data = active_object.SR_data.ent_font[0]
2146
2147 index = data.glyphs_index
2148 neighbor = index + (-1 if _.direction == 'UP' else 1)
2149 data.glyphs.move( neighbor, index )
2150
2151 list_length = len(data.glyphs) - 1
2152 new_index = index + (-1 if _.direction == 'UP' else 1)
2153
2154 data.glyphs_index = max(0, min(new_index, list_length))
2155
2156 return{'FINISHED'}
2157 #}
2158 #}
2159
2160 class SR_OT_FONT_VARIANT_LIST_NEW_ITEM(bpy.types.Operator):
2161 #{
2162 bl_idname = "skaterift.fv_new_entry"
2163 bl_label = "Add variant"
2164
2165 def execute(self, context):#{
2166 return internal_listadd_execute(self,context,'ent_font','variants')
2167 #}
2168 #}
2169
2170 class SR_OT_FONT_VARIANT_LIST_DEL_ITEM(bpy.types.Operator):
2171 #{
2172 bl_idname = "skaterift.fv_del_entry"
2173 bl_label = "Remove variant"
2174
2175 @classmethod
2176 def poll(cls, context):#{
2177 active_object = context.active_object
2178 if obj_ent_type(active_object) == 'ent_font':#{
2179 return active_object.SR_data.ent_font[0].variants
2180 #}
2181 else: return False
2182 #}
2183
2184 def execute(self, context):#{
2185 return internal_listdel_execute(self,context,'ent_font','variants')
2186 #}
2187 #}
2188
2189 class SR_OBJECT_ENT_AUDIO_FILE_ENTRY(bpy.types.PropertyGroup):
2190 #{
2191 path: bpy.props.StringProperty( name="Path" )
2192 probability: bpy.props.FloatProperty( name="Probability",default=100.0 )
2193 #}
2194
2195 class SR_UL_AUDIO_LIST(bpy.types.UIList):
2196 #{
2197 bl_idname = 'SR_UL_AUDIO_LIST'
2198
2199 def draw_item(_,context,layout,data,item,icon,active_data,active_propname):
2200 #{
2201 split = layout.split(factor=0.7)
2202 c = split.column()
2203 c.prop( item, 'path', text='', emboss=False )
2204 c = split.column()
2205 c.prop( item, 'probability', text='%', emboss=True )
2206 #}
2207 #}
2208
2209 class SR_UL_FONT_VARIANT_LIST(bpy.types.UIList):
2210 #{
2211 bl_idname = 'SR_UL_FONT_VARIANT_LIST'
2212
2213 def draw_item(_,context,layout,data,item,icon,active_data,active_propname):
2214 #{
2215 layout.prop( item, 'mesh', emboss=False )
2216 layout.prop( item, 'tipo' )
2217 #}
2218 #}
2219
2220 class SR_UL_FONT_GLYPH_LIST(bpy.types.UIList):
2221 #{
2222 bl_idname = 'SR_UL_FONT_GLYPH_LIST'
2223
2224 def draw_item(_,context,layout,data,item,icon,active_data,active_propname):
2225 #{
2226 s0 = layout.split(factor=0.3)
2227 c = s0.column()
2228 s1 = c.split(factor=0.3)
2229 c = s1.column()
2230 row = c.row()
2231 lbl = chr(item.utf32) if item.utf32 >= 32 and item.utf32 <= 126 else 'ERR'
2232 row.label(text=lbl)
2233 c = s1.column()
2234 c.prop( item, 'utf32', text='', emboss=True )
2235 c = s0.column()
2236 row = c.row()
2237 row.prop( item, 'bounds', text='', emboss=False )
2238 #}
2239 #}
2240
2241 class SR_OBJECT_ENT_ROUTE(bpy.types.PropertyGroup):
2242 #{
2243 gates: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_ROUTE_ENTRY)
2244 gates_index: bpy.props.IntProperty()
2245
2246 colour: bpy.props.FloatVectorProperty( \
2247 name="Colour",\
2248 subtype='COLOR',\
2249 min=0.0,max=1.0,\
2250 default=Vector((0.79,0.63,0.48)),\
2251 description="Route colour"\
2252 )
2253
2254 alias: bpy.props.StringProperty(\
2255 name="Alias",\
2256 default="Untitled Course")
2257
2258 @staticmethod
2259 def sr_inspector( layout, data ):
2260 #{
2261 layout.prop( data[0], 'alias' )
2262 layout.prop( data[0], 'colour' )
2263
2264 layout.label( text='Checkpoints' )
2265 layout.template_list('SR_UL_ROUTE_NODE_LIST', 'Checkpoints', \
2266 data[0], 'gates', data[0], 'gates_index', rows=5)
2267
2268 row = layout.row()
2269 row.operator( 'skaterift.new_entry', text='Add' )
2270 row.operator( 'skaterift.del_entry', text='Remove' )
2271 #}
2272 #}
2273
2274 class SR_OBJECT_ENT_VOLUME(bpy.types.PropertyGroup):
2275 #{
2276 subtype: bpy.props.EnumProperty(
2277 name="Subtype",
2278 items=[('0','Trigger',''),
2279 ('1','Particles (0.1s)','')]
2280 )
2281
2282 target: bpy.props.PointerProperty( \
2283 type=bpy.types.Object, name="Target", \
2284 poll=lambda self,obj: sr_filter_ent_type(obj,['ent_audio']))
2285
2286 @staticmethod
2287 def sr_inspector( layout, data ):
2288 #{
2289 data = data[0]
2290 layout.prop( data, 'subtype' )
2291 layout.prop( data, 'target' )
2292 #}
2293 #}
2294
2295 class SR_OBJECT_ENT_AUDIO(bpy.types.PropertyGroup):
2296 #{
2297 files: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_AUDIO_FILE_ENTRY)
2298 files_index: bpy.props.IntProperty()
2299
2300 flag_3d: bpy.props.BoolProperty( name="3D audio",default=True )
2301 flag_loop: bpy.props.BoolProperty( name="Loop",default=False )
2302 flag_auto: bpy.props.BoolProperty( name="Play at start",default=False )
2303 flag_nodoppler: bpy.props.BoolProperty( name="No Doppler",default=False )
2304
2305 group: bpy.props.IntProperty( name="Group ID", default=0 )
2306 formato: bpy.props.EnumProperty(
2307 name="Format",
2308 items=[('0','Uncompressed Mono',''),
2309 ('1','Compressed Vorbis',''),
2310 ('2','[vg] Bird Synthesis','')]
2311 )
2312 probability_curve: bpy.props.EnumProperty(
2313 name="Probability Curve",
2314 items=[('0','Constant',''),
2315 ('1','Wildlife Daytime',''),
2316 ('2','Wildlife Nighttime','')])
2317 channel_behaviour: bpy.props.EnumProperty(
2318 name="Channel Behaviour",
2319 items=[('0','Unlimited',''),
2320 ('1','Discard if group full', ''),
2321 ('2','Crossfade if group full','')])
2322
2323 transition_duration: bpy.props.FloatProperty(name="Transition Time",\
2324 default=0.2)
2325
2326 max_channels: bpy.props.IntProperty( name="Max Channels", default=1 )
2327 volume: bpy.props.FloatProperty( name="Volume",default=1.0 )
2328
2329 @staticmethod
2330 def sr_inspector( layout, data ):
2331 #{
2332 layout.prop( data[0], 'formato' )
2333 layout.prop( data[0], 'volume' )
2334
2335 box = layout.box()
2336 box.label( text='Channels' )
2337 split = box.split(factor=0.3)
2338 c = split.column()
2339 c.prop( data[0], 'max_channels' )
2340 c = split.column()
2341 c.prop( data[0], 'channel_behaviour', text='Behaviour' )
2342 if data[0].channel_behaviour >= '1':
2343 box.prop( data[0], 'group' )
2344 if data[0].channel_behaviour == '2':
2345 box.prop( data[0], 'transition_duration' )
2346
2347 box = layout.box()
2348 box.label( text='Flags' )
2349 box.prop( data[0], 'flag_3d' )
2350 if data[0].flag_3d: box.prop( data[0], 'flag_nodoppler' )
2351
2352 box.prop( data[0], 'flag_loop' )
2353 box.prop( data[0], 'flag_auto' )
2354
2355 split = layout.split(factor=0.7)
2356 c = split.column()
2357 c.label( text='Filepath' )
2358 c = split.column()
2359 c.label( text='Chance (0.1s)' )
2360
2361 layout.prop( data[0], 'probability_curve' )
2362
2363 layout.template_list('SR_UL_AUDIO_LIST', 'Files', \
2364 data[0], 'files', data[0], 'file_index', rows=5)
2365
2366 row = layout.row()
2367 row.operator( 'skaterift.al_new_entry', text='Add' )
2368 row.operator( 'skaterift.al_del_entry', text='Remove' )
2369 #}
2370 #}
2371
2372 class SR_OBJECT_ENT_MARKER(bpy.types.PropertyGroup):
2373 #{
2374 alias: bpy.props.StringProperty()
2375 #}
2376
2377 class SR_OBJECT_ENT_GLYPH(bpy.types.PropertyGroup):
2378 #{
2379 mini: bpy.props.FloatVectorProperty(size=2)
2380 maxi: bpy.props.FloatVectorProperty(size=2)
2381 utf32: bpy.props.IntProperty()
2382 #}
2383
2384 class SR_OBJECT_ENT_GLYPH_ENTRY(bpy.types.PropertyGroup):
2385 #{
2386 bounds: bpy.props.FloatVectorProperty(size=4,subtype='NONE')
2387 utf32: bpy.props.IntProperty()
2388 #}
2389
2390 class SR_OBJECT_ENT_FONT_VARIANT(bpy.types.PropertyGroup):
2391 #{
2392 mesh: bpy.props.PointerProperty(type=bpy.types.Object)
2393 tipo: bpy.props.StringProperty()
2394 #}
2395
2396 class SR_OBJECT_ENT_FONT(bpy.types.PropertyGroup):
2397 #{
2398 variants: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_FONT_VARIANT)
2399 glyphs: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_GLYPH_ENTRY)
2400 alias: bpy.props.StringProperty()
2401
2402 glyphs_index: bpy.props.IntProperty()
2403 variants_index: bpy.props.IntProperty()
2404
2405 @staticmethod
2406 def sr_inspector( layout, data ):
2407 #{
2408 layout.prop( data[0], 'alias' )
2409
2410 layout.label( text='Variants' )
2411 layout.template_list('SR_UL_FONT_VARIANT_LIST', 'Variants', \
2412 data[0], 'variants', data[0], 'variants_index',\
2413 rows=5 )
2414 row = layout.row()
2415 row.operator( 'skaterift.fv_new_entry', text='Add' )
2416 row.operator( 'skaterift.fv_del_entry', text='Remove' )
2417
2418 layout.label( text='ASCII Glyphs' )
2419 layout.template_list('SR_UL_FONT_GLYPH_LIST', 'Glyphs', \
2420 data[0], 'glyphs', data[0], 'glyphs_index', rows=5)
2421
2422 row = layout.row()
2423 row.operator( 'skaterift.gl_new_entry', text='Add' )
2424 row.operator( 'skaterift.gl_del_entry', text='Remove' )
2425 row.operator( 'skaterift.gl_move_item', text='^' ).direction='UP'
2426 row.operator( 'skaterift.gl_move_item', text='v' ).direction='DOWN'
2427 #}
2428 #}
2429
2430 class SR_OBJECT_PROPERTIES(bpy.types.PropertyGroup):
2431 #{
2432 ent_gate: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_GATE)
2433 ent_spawn: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_SPAWN)
2434 ent_route: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_ROUTE)
2435 ent_volume: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_VOLUME)
2436 ent_audio: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_AUDIO)
2437 ent_marker: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_MARKER)
2438 ent_glyph: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_GLYPH)
2439 ent_font: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_FONT)
2440 ent_type: bpy.props.EnumProperty(
2441 name="Type",
2442 items=[('none', 'None', '', 0),
2443 ('ent_gate','Gate','', 1),
2444 ('ent_spawn','Spawn','', 2),
2445 ('ent_route_node', 'Route Node', '', 3 ),
2446 ('ent_route', 'Route', '', 4),
2447 ('ent_water', 'Water Surface', '', 5),
2448 ('ent_volume', 'Volume', '', 6 ),
2449 ('ent_audio', 'Audio Files', '', 7),
2450 ('ent_marker', 'Marker', '', 8),
2451 ('ent_font', 'Font', '', 9),
2452 ('ent_font_variant','Font variant','',10)],
2453 update=sr_on_type_change
2454 )
2455 #}
2456
2457 class SR_MESH_PROPERTIES(bpy.types.PropertyGroup):
2458 #{
2459 ent_gate: bpy.props.CollectionProperty(type=SR_MESH_ENT_GATE)
2460 #}
2461
2462 class SR_LIGHT_PROPERTIES(bpy.types.PropertyGroup):
2463 #{
2464 daytime: bpy.props.BoolProperty( name='Daytime' )
2465 #}
2466
2467 class SR_BONE_PROPERTIES(bpy.types.PropertyGroup):
2468 #{
2469 collider: bpy.props.EnumProperty( name='Collider Type',
2470 items=[('0','none',''),
2471 ('1','box',''),
2472 ('2','capsule','')])
2473
2474 collider_min: bpy.props.FloatVectorProperty( name='Collider Min', size=3 )
2475 collider_max: bpy.props.FloatVectorProperty( name='Collider Max', size=3 )
2476
2477 cone_constraint: bpy.props.BoolProperty( name='Cone constraint' )
2478
2479 conevx: bpy.props.FloatVectorProperty( name='vx' )
2480 conevy: bpy.props.FloatVectorProperty( name='vy' )
2481 coneva: bpy.props.FloatVectorProperty( name='va' )
2482 conet: bpy.props.FloatProperty( name='t' )
2483
2484 @staticmethod
2485 def sr_inspector( layout, data ):
2486 #{
2487 data = data[0]
2488 box = layout.box()
2489 box.prop( data, 'collider' )
2490
2491 if int(data.collider)>0:#{
2492 row = box.row()
2493 row.prop( data, 'collider_min' )
2494 row = box.row()
2495 row.prop( data, 'collider_max' )
2496 #}
2497
2498 box = layout.box()
2499 box.prop( data, 'cone_constraint' )
2500 if data.cone_constraint:#{
2501 row = box.row()
2502 row.prop( data, 'conevx' )
2503 row = box.row()
2504 row.prop( data, 'conevy' )
2505 row = box.row()
2506 row.prop( data, 'coneva' )
2507 box.prop( data, 'conet' )
2508 #}
2509 #}
2510 #}
2511
2512 class SR_MATERIAL_PROPERTIES(bpy.types.PropertyGroup):
2513 #{
2514 shader: bpy.props.EnumProperty(
2515 name="Format",
2516 items = [
2517 ('standard',"standard",''),
2518 ('standard_cutout', "standard_cutout", ''),
2519 ('terrain_blend', "terrain_blend", ''),
2520 ('vertex_blend', "vertex_blend", ''),
2521 ('water',"water",'')
2522 ])
2523
2524 surface_prop: bpy.props.EnumProperty(
2525 name="Surface Property",
2526 items = [
2527 ('0','concrete',''),
2528 ('1','wood',''),
2529 ('2','grass',''),
2530 ('3','tiles',''),
2531 ('4','metal','')
2532 ])
2533
2534 collision: bpy.props.BoolProperty( \
2535 name="Collisions Enabled",\
2536 default=True,\
2537 description = "Can the player collide with this material"\
2538 )
2539 skate_surface: bpy.props.BoolProperty( \
2540 name="Skate Surface", \
2541 default=True,\
2542 description = "Should the game try to target this surface?" \
2543 )
2544 grind_surface: bpy.props.BoolProperty( \
2545 name="Grind Surface", \
2546 default=False,\
2547 description = "Grind face?" \
2548 )
2549 grow_grass: bpy.props.BoolProperty( \
2550 name="Grow Grass", \
2551 default=False,\
2552 description = "Spawn grass sprites on this surface?" \
2553 )
2554 blend_offset: bpy.props.FloatVectorProperty( \
2555 name="Blend Offset", \
2556 size=2, \
2557 default=Vector((0.5,0.0)),\
2558 description="When surface is more than 45 degrees, add this vector " +\
2559 "to the UVs" \
2560 )
2561 sand_colour: bpy.props.FloatVectorProperty( \
2562 name="Sand Colour",\
2563 subtype='COLOR',\
2564 min=0.0,max=1.0,\
2565 default=Vector((0.79,0.63,0.48)),\
2566 description="Blend to this colour near the 0 coordinate on UP axis"\
2567 )
2568 shore_colour: bpy.props.FloatVectorProperty( \
2569 name="Shore Colour",\
2570 subtype='COLOR',\
2571 min=0.0,max=1.0,\
2572 default=Vector((0.03,0.32,0.61)),\
2573 description="Water colour at the shoreline"\
2574 )
2575 ocean_colour: bpy.props.FloatVectorProperty( \
2576 name="Ocean Colour",\
2577 subtype='COLOR',\
2578 min=0.0,max=1.0,\
2579 default=Vector((0.0,0.006,0.03)),\
2580 description="Water colour in the deep bits"\
2581 )
2582 #}
2583
2584 # ---------------------------------------------------------------------------- #
2585 # #
2586 # GUI section #
2587 # #
2588 # ---------------------------------------------------------------------------- #
2589
2590 cv_view_draw_handler = None
2591 cv_view_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR')
2592 cv_view_verts = []
2593 cv_view_colours = []
2594 cv_view_course_i = 0
2595
2596 # Draw axis alligned sphere at position with radius
2597 #
2598 def cv_draw_sphere( pos, radius, colour ):
2599 #{
2600 global cv_view_verts, cv_view_colours
2601
2602 ly = pos + Vector((0,0,radius))
2603 lx = pos + Vector((0,radius,0))
2604 lz = pos + Vector((0,0,radius))
2605
2606 pi = 3.14159265358979323846264
2607
2608 for i in range(16):#{
2609 t = ((i+1.0) * 1.0/16.0) * pi * 2.0
2610 s = math.sin(t)
2611 c = math.cos(t)
2612
2613 py = pos + Vector((s*radius,0.0,c*radius))
2614 px = pos + Vector((s*radius,c*radius,0.0))
2615 pz = pos + Vector((0.0,s*radius,c*radius))
2616
2617 cv_view_verts += [ px, lx ]
2618 cv_view_verts += [ py, ly ]
2619 cv_view_verts += [ pz, lz ]
2620
2621 cv_view_colours += [ colour, colour, colour, colour, colour, colour ]
2622
2623 ly = py
2624 lx = px
2625 lz = pz
2626 #}
2627 cv_draw_lines()
2628 #}
2629
2630 # Draw axis alligned sphere at position with radius
2631 #
2632 def cv_draw_halfsphere( pos, tx, ty, tz, radius, colour ):
2633 #{
2634 global cv_view_verts, cv_view_colours
2635
2636 ly = pos + tz*radius
2637 lx = pos + ty*radius
2638 lz = pos + tz*radius
2639
2640 pi = 3.14159265358979323846264
2641
2642 for i in range(16):#{
2643 t = ((i+1.0) * 1.0/16.0) * pi
2644 s = math.sin(t)
2645 c = math.cos(t)
2646
2647 s1 = math.sin(t*2.0)
2648 c1 = math.cos(t*2.0)
2649
2650 py = pos + s*tx*radius + c *tz*radius
2651 px = pos + s*tx*radius + c *ty*radius
2652 pz = pos + s1*ty*radius + c1*tz*radius
2653
2654 cv_view_verts += [ px, lx ]
2655 cv_view_verts += [ py, ly ]
2656 cv_view_verts += [ pz, lz ]
2657
2658 cv_view_colours += [ colour, colour, colour, colour, colour, colour ]
2659
2660 ly = py
2661 lx = px
2662 lz = pz
2663 #}
2664 cv_draw_lines()
2665 #}
2666
2667 # Draw transformed -1 -> 1 cube
2668 #
2669 def cv_draw_ucube( transform, colour, s=Vector((1,1,1)), o=Vector((0,0,0)) ):
2670 #{
2671 global cv_view_verts, cv_view_colours
2672
2673 a = o + -1.0 * s
2674 b = o + 1.0 * s
2675
2676 vs = [None]*8
2677 vs[0] = transform @ Vector((a[0], a[1], a[2]))
2678 vs[1] = transform @ Vector((a[0], b[1], a[2]))
2679 vs[2] = transform @ Vector((b[0], b[1], a[2]))
2680 vs[3] = transform @ Vector((b[0], a[1], a[2]))
2681 vs[4] = transform @ Vector((a[0], a[1], b[2]))
2682 vs[5] = transform @ Vector((a[0], b[1], b[2]))
2683 vs[6] = transform @ Vector((b[0], b[1], b[2]))
2684 vs[7] = transform @ Vector((b[0], a[1], b[2]))
2685
2686 indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
2687 (0,4),(1,5),(2,6),(3,7)]
2688
2689 for l in indices:#{
2690 v0 = vs[l[0]]
2691 v1 = vs[l[1]]
2692 cv_view_verts += [(v0[0],v0[1],v0[2])]
2693 cv_view_verts += [(v1[0],v1[1],v1[2])]
2694 cv_view_colours += [colour, colour]
2695 #}
2696 cv_draw_lines()
2697 #}
2698
2699 # Draw line with colour
2700 #
2701 def cv_draw_line( p0, p1, colour ):
2702 #{
2703 global cv_view_verts, cv_view_colours
2704
2705 cv_view_verts += [p0,p1]
2706 cv_view_colours += [colour, colour]
2707 cv_draw_lines()
2708 #}
2709
2710 # Draw line with colour(s)
2711 #
2712 def cv_draw_line2( p0, p1, c0, c1 ):
2713 #{
2714 global cv_view_verts, cv_view_colours
2715
2716 cv_view_verts += [p0,p1]
2717 cv_view_colours += [c0,c1]
2718 cv_draw_lines()
2719 #}
2720
2721 #
2722 #
2723 def cv_tangent_basis( n, tx, ty ):
2724 #{
2725 if abs( n[0] ) >= 0.57735027:#{
2726 tx[0] = n[1]
2727 tx[1] = -n[0]
2728 tx[2] = 0.0
2729 #}
2730 else:#{
2731 tx[0] = 0.0
2732 tx[1] = n[2]
2733 tx[2] = -n[1]
2734 #}
2735
2736 tx.normalize()
2737 _ty = n.cross( tx )
2738
2739 ty[0] = _ty[0]
2740 ty[1] = _ty[1]
2741 ty[2] = _ty[2]
2742 #}
2743
2744 # Draw coloured arrow
2745 #
2746 def cv_draw_arrow( p0, p1, c0, size=0.15 ):
2747 #{
2748 global cv_view_verts, cv_view_colours
2749
2750 n = p1-p0
2751 midpt = p0 + n*0.5
2752 n.normalize()
2753
2754 tx = Vector((1,0,0))
2755 ty = Vector((1,0,0))
2756 cv_tangent_basis( n, tx, ty )
2757
2758 cv_view_verts += [p0,p1, midpt+(tx-n)*size,midpt, midpt+(-tx-n)*size,midpt ]
2759 cv_view_colours += [c0,c0,c0,c0,c0,c0]
2760 #cv_draw_lines()
2761 #}
2762
2763 def cv_draw_line_dotted( p0, p1, c0, dots=10 ):
2764 #{
2765 global cv_view_verts, cv_view_colours
2766
2767 for i in range(dots):#{
2768 t0 = i/dots
2769 t1 = (i+0.25)/dots
2770
2771 p2 = p0*(1.0-t0)+p1*t0
2772 p3 = p0*(1.0-t1)+p1*t1
2773
2774 cv_view_verts += [p2,p3]
2775 cv_view_colours += [c0,c0]
2776 #}
2777 #cv_draw_lines()
2778 #}
2779
2780 # Drawhandles of a bezier control point
2781 #
2782 def cv_draw_bhandle( obj, direction, colour ):
2783 #{
2784 global cv_view_verts, cv_view_colours
2785
2786 p0 = obj.location
2787 h0 = obj.matrix_world @ Vector((0,direction,0))
2788
2789 cv_view_verts += [p0]
2790 cv_view_verts += [h0]
2791 cv_view_colours += [colour,colour]
2792 cv_draw_lines()
2793 #}
2794
2795 # Draw a bezier curve (at fixed resolution 10)
2796 #
2797 def cv_draw_bezier( p0,h0,p1,h1,c0,c1 ):
2798 #{
2799 global cv_view_verts, cv_view_colours
2800
2801 last = p0
2802 for i in range(10):#{
2803 t = (i+1)/10
2804 a0 = 1-t
2805
2806 tt = t*t
2807 ttt = tt*t
2808 p=ttt*p1+(3*tt-3*ttt)*h1+(3*ttt-6*tt+3*t)*h0+(3*tt-ttt-3*t+1)*p0
2809
2810 cv_view_verts += [(last[0],last[1],last[2])]
2811 cv_view_verts += [(p[0],p[1],p[2])]
2812 cv_view_colours += [c0*a0+c1*(1-a0),c0*a0+c1*(1-a0)]
2813
2814 last = p
2815 #}
2816 cv_draw_lines()
2817 #}
2818
2819 # I think this one extends the handles of the bezier otwards......
2820 #
2821 def cv_draw_sbpath( o0,o1,c0,c1,s0,s1 ):
2822 #{
2823 global cv_view_course_i
2824
2825 offs = ((cv_view_course_i % 2)*2-1) * cv_view_course_i * 0.02
2826
2827 p0 = o0.matrix_world @ Vector((offs, 0,0))
2828 h0 = o0.matrix_world @ Vector((offs, s0,0))
2829 p1 = o1.matrix_world @ Vector((offs, 0,0))
2830 h1 = o1.matrix_world @ Vector((offs,-s1,0))
2831
2832 cv_draw_bezier( p0,h0,p1,h1,c0,c1 )
2833 cv_draw_lines()
2834 #}
2835
2836 # Flush the lines buffers. This is called often because god help you if you want
2837 # to do fixed, fast buffers in this catastrophic programming language.
2838 #
2839 def cv_draw_lines():
2840 #{
2841 global cv_view_shader, cv_view_verts, cv_view_colours
2842
2843 if len(cv_view_verts) < 2:
2844 return
2845
2846 lines = batch_for_shader(\
2847 cv_view_shader, 'LINES', \
2848 { "pos":cv_view_verts, "color":cv_view_colours })
2849
2850 lines.draw( cv_view_shader )
2851
2852 cv_view_verts = []
2853 cv_view_colours = []
2854 #}
2855
2856 # I dont remember what this does exactly
2857 #
2858 def cv_draw_bpath( o0,o1,c0,c1 ):
2859 #{
2860 cv_draw_sbpath( o0,o1,c0,c1,1.0,1.0 )
2861 #}
2862
2863 # Semi circle to show the limit. and some lines
2864 #
2865 def draw_limit( obj, center, major, minor, amin, amax, colour ):
2866 #{
2867 global cv_view_verts, cv_view_colours
2868 f = 0.05
2869 ay = major*f
2870 ax = minor*f
2871
2872 for x in range(16):#{
2873 t0 = x/16
2874 t1 = (x+1)/16
2875 a0 = amin*(1.0-t0)+amax*t0
2876 a1 = amin*(1.0-t1)+amax*t1
2877
2878 p0 = center + major*f*math.cos(a0) + minor*f*math.sin(a0)
2879 p1 = center + major*f*math.cos(a1) + minor*f*math.sin(a1)
2880
2881 p0=obj.matrix_world @ p0
2882 p1=obj.matrix_world @ p1
2883 cv_view_verts += [p0,p1]
2884 cv_view_colours += [colour,colour]
2885
2886 if x == 0:#{
2887 cv_view_verts += [p0,center]
2888 cv_view_colours += [colour,colour]
2889 #}
2890 if x == 15:#{
2891 cv_view_verts += [p1,center]
2892 cv_view_colours += [colour,colour]
2893 #}
2894 #}
2895
2896 cv_view_verts += [center+major*1.2*f,center+major*f*0.8]
2897 cv_view_colours += [colour,colour]
2898
2899 cv_draw_lines()
2900 #}
2901
2902 # Cone and twist limit
2903 #
2904 def draw_cone_twist( center, vx, vy, va ):
2905 #{
2906 global cv_view_verts, cv_view_colours
2907 axis = vy.cross( vx )
2908 axis.normalize()
2909
2910 size = 0.12
2911
2912 cv_view_verts += [center, center+va*size]
2913 cv_view_colours += [ (1,1,1), (1,1,1) ]
2914
2915 for x in range(32):#{
2916 t0 = (x/32) * math.tau
2917 t1 = ((x+1)/32) * math.tau
2918
2919 c0 = math.cos(t0)
2920 s0 = math.sin(t0)
2921 c1 = math.cos(t1)
2922 s1 = math.sin(t1)
2923
2924 p0 = center + (axis + vx*c0 + vy*s0).normalized() * size
2925 p1 = center + (axis + vx*c1 + vy*s1).normalized() * size
2926
2927 col0 = ( abs(c0), abs(s0), 0.0, 1.0 )
2928 col1 = ( abs(c1), abs(s1), 0.0, 1.0 )
2929
2930 cv_view_verts += [center, p0, p0, p1]
2931 cv_view_colours += [ (0,0,0), col0, col0, col1 ]
2932 #}
2933
2934 cv_draw_lines()
2935 #}
2936
2937 # Draws constraints and stuff for the skeleton. This isnt documented and wont be
2938 #
2939 def draw_skeleton_helpers( obj ):
2940 #{
2941 global cv_view_verts, cv_view_colours
2942
2943 if obj.data.pose_position != 'REST':#{
2944 return
2945 #}
2946
2947 for bone in obj.data.bones:#{
2948 c = bone.head_local
2949 a = Vector((bone.SR_data.collider_min[0],
2950 bone.SR_data.collider_min[1],
2951 bone.SR_data.collider_min[2]))
2952 b = Vector((bone.SR_data.collider_max[0],
2953 bone.SR_data.collider_max[1],
2954 bone.SR_data.collider_max[2]))
2955
2956 if bone.SR_data.collider == '1':#{
2957 vs = [None]*8
2958 vs[0]=obj.matrix_world@Vector((c[0]+a[0],c[1]+a[1],c[2]+a[2]))
2959 vs[1]=obj.matrix_world@Vector((c[0]+a[0],c[1]+b[1],c[2]+a[2]))
2960 vs[2]=obj.matrix_world@Vector((c[0]+b[0],c[1]+b[1],c[2]+a[2]))
2961 vs[3]=obj.matrix_world@Vector((c[0]+b[0],c[1]+a[1],c[2]+a[2]))
2962 vs[4]=obj.matrix_world@Vector((c[0]+a[0],c[1]+a[1],c[2]+b[2]))
2963 vs[5]=obj.matrix_world@Vector((c[0]+a[0],c[1]+b[1],c[2]+b[2]))
2964 vs[6]=obj.matrix_world@Vector((c[0]+b[0],c[1]+b[1],c[2]+b[2]))
2965 vs[7]=obj.matrix_world@Vector((c[0]+b[0],c[1]+a[1],c[2]+b[2]))
2966
2967 indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
2968 (0,4),(1,5),(2,6),(3,7)]
2969
2970 for l in indices:#{
2971 v0 = vs[l[0]]
2972 v1 = vs[l[1]]
2973
2974 cv_view_verts += [(v0[0],v0[1],v0[2])]
2975 cv_view_verts += [(v1[0],v1[1],v1[2])]
2976 cv_view_colours += [(0.5,0.5,0.5),(0.5,0.5,0.5)]
2977 #}
2978 #}
2979 elif bone.SR_data.collider == '2':#{
2980 v0 = b-a
2981 major_axis = 0
2982 largest = -1.0
2983
2984 for i in range(3):#{
2985 if abs(v0[i]) > largest:#{
2986 largest = abs(v0[i])
2987 major_axis = i
2988 #}
2989 #}
2990
2991 v1 = Vector((0,0,0))
2992 v1[major_axis] = 1.0
2993
2994 tx = Vector((0,0,0))
2995 ty = Vector((0,0,0))
2996
2997 cv_tangent_basis( v1, tx, ty )
2998 r = (abs(tx.dot( v0 )) + abs(ty.dot( v0 ))) * 0.25
2999 l = v0[ major_axis ] - r*2
3000
3001 p0 = obj.matrix_world@Vector( c + (a+b)*0.5 + v1*l*-0.5 )
3002 p1 = obj.matrix_world@Vector( c + (a+b)*0.5 + v1*l* 0.5 )
3003
3004 colour = [0.2,0.2,0.2]
3005 colour[major_axis] = 0.5
3006
3007 cv_draw_halfsphere( p0, -v1, ty, tx, r, colour )
3008 cv_draw_halfsphere( p1, v1, ty, tx, r, colour )
3009 cv_draw_line( p0+tx* r, p1+tx* r, colour )
3010 cv_draw_line( p0+tx*-r, p1+tx*-r, colour )
3011 cv_draw_line( p0+ty* r, p1+ty* r, colour )
3012 cv_draw_line( p0+ty*-r, p1+ty*-r, colour )
3013 #}
3014 else:#{
3015 continue
3016 #}
3017
3018 center = obj.matrix_world @ c
3019 if bone.SR_data.cone_constraint:#{
3020 vx = Vector([bone.SR_data.conevx[_] for _ in range(3)])
3021 vy = Vector([bone.SR_data.conevy[_] for _ in range(3)])
3022 va = Vector([bone.SR_data.coneva[_] for _ in range(3)])
3023 draw_cone_twist( center, vx, vy, va )
3024 #}
3025 #}
3026 #}
3027
3028 def cv_ent_gate( obj ):
3029 #{
3030 global cv_view_verts, cv_view_colours
3031
3032 if obj.type != 'MESH': return
3033
3034 mesh_data = obj.data.SR_data.ent_gate[0]
3035 data = obj.SR_data.ent_gate[0]
3036 dims = mesh_data.dimensions
3037
3038 vs = [None]*9
3039 c = Vector((0,0,dims[2]))
3040
3041 vs[0] = obj.matrix_world @ Vector((-dims[0],0.0,-dims[1]+dims[2]))
3042 vs[1] = obj.matrix_world @ Vector((-dims[0],0.0, dims[1]+dims[2]))
3043 vs[2] = obj.matrix_world @ Vector(( dims[0],0.0, dims[1]+dims[2]))
3044 vs[3] = obj.matrix_world @ Vector(( dims[0],0.0,-dims[1]+dims[2]))
3045 vs[4] = obj.matrix_world @ (c+Vector((-1,0,-2)))
3046 vs[5] = obj.matrix_world @ (c+Vector((-1,0, 2)))
3047 vs[6] = obj.matrix_world @ (c+Vector(( 1,0, 2)))
3048 vs[7] = obj.matrix_world @ (c+Vector((-1,0, 0)))
3049 vs[8] = obj.matrix_world @ (c+Vector(( 1,0, 0)))
3050
3051 indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(7,8)]
3052
3053 r3d = bpy.context.area.spaces.active.region_3d
3054
3055 p0 = r3d.view_matrix.inverted().translation
3056 v0 = (obj.matrix_world@Vector((0,0,0))) - p0
3057 v1 = obj.matrix_world.to_3x3() @ Vector((0,1,0))
3058
3059 if v0.dot(v1) > 0.0: cc = (0,1,0)
3060 else: cc = (1,0,0)
3061
3062 for l in indices:#{
3063 v0 = vs[l[0]]
3064 v1 = vs[l[1]]
3065 cv_view_verts += [(v0[0],v0[1],v0[2])]
3066 cv_view_verts += [(v1[0],v1[1],v1[2])]
3067 cv_view_colours += [cc,cc]
3068 #}
3069
3070 sw = (0.4,0.4,0.4)
3071 if data.target != None:
3072 cv_draw_arrow( obj.location, data.target.location, sw )
3073 #}
3074
3075 def cv_ent_volume( obj ):
3076 #{
3077 global cv_view_verts, cv_view_colours
3078
3079 data = obj.SR_data.ent_volume[0]
3080
3081 if data.subtype == '0':#{
3082 cv_draw_ucube( obj.matrix_world, (0,1,0) )
3083
3084 if data.target:#{
3085 cv_draw_line( obj.location, data.target.location, (0,1,0) )
3086 #}
3087 #}
3088 elif data.subtype == '1':#{
3089 cv_draw_ucube( obj.matrix_world, (1,1,0) )
3090
3091 if data.target:#{
3092 cv_draw_line( obj.location, data.target.location, (1,1,0) )
3093 #}
3094 #}
3095 #}
3096
3097 def dijkstra( graph, start_node, target_node ):
3098 #{
3099 unvisited = [_ for _ in graph]
3100 shortest_path = {}
3101 previous_nodes = {}
3102
3103 for n in unvisited:
3104 shortest_path[n] = 9999999.999999
3105 shortest_path[start_node] = 0
3106
3107 while unvisited:#{
3108 current_min_node = None
3109 for n in unvisited:#{
3110 if current_min_node == None:
3111 current_min_node = n
3112 elif shortest_path[n] < shortest_path[current_min_node]:
3113 current_min_node = n
3114 #}
3115
3116 for branch in graph[current_min_node]:#{
3117 tentative_value = shortest_path[current_min_node]
3118 tentative_value += graph[current_min_node][branch]
3119 if tentative_value < shortest_path[branch]:#{
3120 shortest_path[branch] = tentative_value
3121 previous_nodes[branch] = current_min_node
3122 #}
3123 #}
3124
3125 unvisited.remove(current_min_node)
3126 #}
3127
3128 path = []
3129 node = target_node
3130 while node != start_node:#{
3131 path.append(node)
3132
3133 if node not in previous_nodes: return None
3134 node = previous_nodes[node]
3135 #}
3136
3137 # Add the start node manually
3138 path.append(start_node)
3139 return path
3140 #}
3141
3142 class dij_graph():
3143 #{
3144 def __init__(_,points,graph,subsections):#{
3145 _.points = points
3146 _.graph = graph
3147 _.subsections = subsections
3148 #}
3149 #}
3150
3151 def create_node_graph( curves, gates ):
3152 #{
3153 # add endpoints of curves
3154 graph = {}
3155 route_points = []
3156 subsections = []
3157 point_count = 0
3158 spline_count = 0
3159
3160 for c in range(len(curves)):#{
3161 for s in range(len(curves[c].data.splines)):#{
3162 spline = curves[c].data.splines[s]
3163 l = len(spline.points)
3164 if l < 2: continue
3165
3166 dist = round(spline.calc_length(),2)
3167
3168 ia = point_count
3169 ib = point_count+l-1
3170
3171 graph[ia] = { ib: dist }
3172 graph[ib] = { ia: dist }
3173
3174 for i in range(len(spline.points)):#{
3175 wco = curves[c].matrix_world @ spline.points[i].co
3176 route_points.append(Vector((wco[0],wco[1],wco[2]+0.5)))
3177
3178 previous = ia+i-1
3179 proxima = ia+i+1
3180
3181 if i == 0: previous = -1
3182 if i == len(spline.points)-1: proxima = -1
3183
3184 subsections.append((spline_count,previous,proxima))
3185 point_count += 1
3186 #}
3187
3188 spline_count += 1
3189 #}
3190 #}
3191
3192 # link endpoints
3193 graph_keys = list(graph)
3194 for i in range(len(graph_keys)-1):#{
3195 for j in range(i+1, len(graph_keys)):#{
3196 if i%2==0 and i+1==j: continue
3197
3198 ni = graph_keys[i]
3199 nj = graph_keys[j]
3200 pi = route_points[ni]
3201 pj = route_points[nj]
3202
3203 dist = round((pj-pi).magnitude,2)
3204
3205 if dist < 10.0:#{
3206 graph[ni][nj] = dist
3207 graph[nj][ni] = dist
3208 #}
3209 #}
3210 #}
3211
3212 # add and link gates( by name )
3213 for gate in gates:#{
3214 v1 = gate.matrix_world.to_3x3() @ Vector((0,1,0))
3215 if gate.SR_data.ent_gate[0].target:
3216 v1 = v1 * -1.0
3217
3218 graph[ gate.name ] = {}
3219
3220 for i in range(len(graph_keys)):#{
3221 ni = graph_keys[i]
3222 pi = route_points[ni]
3223
3224 v0 = pi-gate.location
3225 if v0.dot(v1) < 0.0: continue
3226
3227 dist = round(v0.magnitude,2)
3228
3229 if dist < 10.0:#{
3230 graph[ gate.name ][ ni ] = dist
3231 graph[ ni ][ gate.name ] = dist
3232 #}
3233 #}
3234 #}
3235
3236 return dij_graph(route_points,graph,subsections)
3237 #}
3238
3239 def solve_graph( dij, start, end ):
3240 #{
3241 path = dijkstra( dij.graph, end, start )
3242 full = []
3243
3244 if path:#{
3245 for sj in range(1,len(path)-2):#{
3246 i0 = path[sj]
3247 i1 = path[sj+1]
3248 map0 = dij.subsections[i0]
3249 map1 = dij.subsections[i1]
3250
3251 if map0[0] == map1[0]:#{
3252 if map0[1] == -1: direction = 2
3253 else: direction = 1
3254 sent = 0
3255
3256 while True:#{
3257 map0 = dij.subsections[i0]
3258 i1 = map0[direction]
3259 if i1 == -1: break
3260
3261 full.append( i0 )
3262 sent += 1
3263 i0 = i1
3264 if sent > 50: break
3265 #}
3266 #}
3267 else:#{
3268 full.append( i0 )
3269 #}
3270 #}
3271
3272 full.append( path[-2] )
3273 #}
3274 return full
3275 #}
3276
3277 def cv_draw_route( route, dij ):
3278 #{
3279 pole = Vector((0.2,0.2,10))
3280 hat = Vector((1,8,0.2))
3281 cc = (route.SR_data.ent_route[0].colour[0],
3282 route.SR_data.ent_route[0].colour[1],
3283 route.SR_data.ent_route[0].colour[2])
3284
3285 cv_draw_ucube(route.matrix_world,cc,Vector((0.5,-7.5,6)),\
3286 Vector((0,-6.5,5.5)))
3287 cv_draw_ucube(route.matrix_world,cc,pole, Vector(( 0.5, 0.5,0)) )
3288 cv_draw_ucube(route.matrix_world,cc,pole, Vector(( 0.5,-13.5,0)) )
3289 cv_draw_ucube(route.matrix_world,cc,hat, Vector((-0.5,-6.5, 12)) )
3290 cv_draw_ucube(route.matrix_world,cc,hat, Vector((-0.5,-6.5,-1)) )
3291
3292 checkpoints = route.SR_data.ent_route[0].gates
3293
3294 for i in range(len(checkpoints)):#{
3295 gi = checkpoints[i].target
3296 gj = checkpoints[(i+1)%len(checkpoints)].target
3297
3298 if gi:#{
3299 dest = gi.SR_data.ent_gate[0].target
3300 if dest:
3301 cv_draw_line_dotted( gi.location, dest.location, cc )
3302 gi = dest
3303 #}
3304
3305 if gi==gj: continue # error?
3306 if not gi or not gj: continue
3307
3308 path = solve_graph( dij, gi.name, gj.name )
3309
3310 if path:#{
3311 cv_draw_arrow(gi.location,dij.points[path[0]],cc,1.5)
3312 cv_draw_arrow(dij.points[path[len(path)-1]],gj.location,cc,1.5)
3313 for j in range(len(path)-1):#{
3314 i0 = path[j]
3315 i1 = path[j+1]
3316 o0 = dij.points[ i0 ]
3317 o1 = dij.points[ i1 ]
3318 cv_draw_arrow(o0,o1,cc,1.5)
3319 #}
3320 #}
3321 else:#{
3322 cv_draw_line_dotted( gi.location, gj.location, cc )
3323 #}
3324 #}
3325 #}
3326
3327 def cv_draw():
3328 #{
3329 global cv_view_shader
3330 global cv_view_verts
3331 global cv_view_colours
3332 global cv_view_course_i
3333
3334 cv_view_course_i = 0
3335 cv_view_verts = []
3336 cv_view_colours = []
3337
3338 cv_view_shader.bind()
3339 gpu.state.depth_mask_set(False)
3340 gpu.state.line_width_set(2.0)
3341 gpu.state.face_culling_set('BACK')
3342 gpu.state.depth_test_set('LESS')
3343 gpu.state.blend_set('NONE')
3344
3345 route_gates = []
3346 route_curves = []
3347 routes = []
3348
3349 for obj in bpy.context.collection.objects:#{
3350 if obj.type == 'ARMATURE':#{
3351 if obj.data.pose_position == 'REST':
3352 draw_skeleton_helpers( obj )
3353 #}
3354 else:#{
3355 ent_type = obj_ent_type( obj )
3356
3357 if ent_type == 'ent_gate':#{
3358 cv_ent_gate( obj )
3359 route_gates += [obj]
3360 #}
3361 elif ent_type == 'ent_route_node':#{
3362 if obj.type == 'CURVE':#{
3363 route_curves += [obj]
3364 #}
3365 #}
3366 elif ent_type == 'ent_route':
3367 routes += [obj]
3368 elif ent_type == 'ent_volume':#{
3369 cv_ent_volume( obj )
3370 #}
3371 elif ent_type == 'ent_audio':#{
3372 if obj.SR_data.ent_audio[0].flag_3d:
3373 cv_draw_sphere( obj.location, obj.scale[0], (1,1,0) )
3374 #}
3375 elif ent_type == 'ent_font':#{
3376 data = obj.SR_data.ent_font[0]
3377
3378 for i in range(len(data.variants)):#{
3379 sub = data.variants[i].mesh
3380 if not sub: continue
3381
3382 for ch in data.glyphs:#{
3383 mini = (ch.bounds[0],ch.bounds[1])
3384 maxi = (ch.bounds[2]+mini[0],ch.bounds[3]+mini[1])
3385 p0 = sub.matrix_world @ Vector((mini[0],0.0,mini[1]))
3386 p1 = sub.matrix_world @ Vector((maxi[0],0.0,mini[1]))
3387 p2 = sub.matrix_world @ Vector((maxi[0],0.0,maxi[1]))
3388 p3 = sub.matrix_world @ Vector((mini[0],0.0,maxi[1]))
3389
3390 if i == data.variants_index: cc = (0.5,0.5,0.5)
3391 else: cc = (0,0,0)
3392
3393 cv_view_verts += [p0,p1,p1,p2,p2,p3,p3,p0]
3394 cv_view_colours += [cc,cc,cc,cc,cc,cc,cc,cc]
3395 #}
3396 #}
3397 #}
3398 #}
3399 #}
3400
3401 dij = create_node_graph( route_curves, route_gates )
3402
3403 #cv_draw_route_map( route_nodes )
3404 for route in routes:#{
3405 cv_draw_route( route, dij )
3406 #}
3407
3408 cv_draw_lines()
3409 return
3410 #}
3411
3412 classes = [ SR_INTERFACE, SR_MATERIAL_PANEL,\
3413 SR_COLLECTION_SETTINGS, SR_SCENE_SETTINGS, \
3414 SR_COMPILE, SR_COMPILE_THIS, SR_MIRROR_BONE_X,\
3415 \
3416 SR_OBJECT_ENT_GATE, SR_MESH_ENT_GATE, SR_OBJECT_ENT_SPAWN, \
3417 SR_OBJECT_ENT_ROUTE_ENTRY, SR_UL_ROUTE_NODE_LIST, \
3418 SR_OBJECT_ENT_ROUTE, SR_OT_ROUTE_LIST_NEW_ITEM,\
3419 SR_OT_GLYPH_LIST_NEW_ITEM, SR_OT_GLYPH_LIST_DEL_ITEM,\
3420 SR_OT_GLYPH_LIST_MOVE_ITEM,\
3421 SR_OT_AUDIO_LIST_NEW_ITEM,SR_OT_AUDIO_LIST_DEL_ITEM,\
3422 SR_OT_FONT_VARIANT_LIST_NEW_ITEM,SR_OT_FONT_VARIANT_LIST_DEL_ITEM,\
3423 SR_OT_COPY_ENTITY_DATA, \
3424 SR_OBJECT_ENT_VOLUME, \
3425 SR_UL_AUDIO_LIST, SR_OBJECT_ENT_AUDIO_FILE_ENTRY,\
3426 SR_OT_ROUTE_LIST_DEL_ITEM,\
3427 SR_OBJECT_ENT_AUDIO,SR_OBJECT_ENT_MARKER,SR_OBJECT_ENT_GLYPH,\
3428 SR_OBJECT_ENT_FONT_VARIANT,
3429 SR_OBJECT_ENT_GLYPH_ENTRY,\
3430 SR_UL_FONT_VARIANT_LIST,SR_UL_FONT_GLYPH_LIST,\
3431 SR_OBJECT_ENT_FONT,\
3432 \
3433 SR_OBJECT_PROPERTIES, SR_LIGHT_PROPERTIES, SR_BONE_PROPERTIES,
3434 SR_MESH_PROPERTIES, SR_MATERIAL_PROPERTIES \
3435 ]
3436
3437 def register():
3438 #{
3439 for c in classes:
3440 bpy.utils.register_class(c)
3441
3442 bpy.types.Scene.SR_data = \
3443 bpy.props.PointerProperty(type=SR_SCENE_SETTINGS)
3444 bpy.types.Collection.SR_data = \
3445 bpy.props.PointerProperty(type=SR_COLLECTION_SETTINGS)
3446
3447 bpy.types.Object.SR_data = \
3448 bpy.props.PointerProperty(type=SR_OBJECT_PROPERTIES)
3449 bpy.types.Light.SR_data = \
3450 bpy.props.PointerProperty(type=SR_LIGHT_PROPERTIES)
3451 bpy.types.Bone.SR_data = \
3452 bpy.props.PointerProperty(type=SR_BONE_PROPERTIES)
3453 bpy.types.Mesh.SR_data = \
3454 bpy.props.PointerProperty(type=SR_MESH_PROPERTIES)
3455 bpy.types.Material.SR_data = \
3456 bpy.props.PointerProperty(type=SR_MATERIAL_PROPERTIES)
3457
3458 global cv_view_draw_handler
3459 cv_view_draw_handler = bpy.types.SpaceView3D.draw_handler_add(\
3460 cv_draw,(),'WINDOW','POST_VIEW')
3461 #}
3462
3463 def unregister():
3464 #{
3465 for c in classes:
3466 bpy.utils.unregister_class(c)
3467
3468 global cv_view_draw_handler
3469 bpy.types.SpaceView3D.draw_handler_remove(cv_view_draw_handler,'WINDOW')
3470 #}
3471
3472 # ---------------------------------------------------------------------------- #
3473 # #
3474 # QOI encoder #
3475 # #
3476 # ---------------------------------------------------------------------------- #
3477 # #
3478 # Transliteration of: #
3479 # https://github.com/phoboslab/qoi/blob/master/qoi.h #
3480 # #
3481 # Copyright (c) 2021, Dominic Szablewski - https://phoboslab.org #
3482 # SPDX-License-Identifier: MIT #
3483 # QOI - The "Quite OK Image" format for fast, lossless image compression #
3484 # #
3485 # ---------------------------------------------------------------------------- #
3486
3487 class qoi_rgba_t(Structure):
3488 #{
3489 _pack_ = 1
3490 _fields_ = [("r",c_uint8),
3491 ("g",c_uint8),
3492 ("b",c_uint8),
3493 ("a",c_uint8)]
3494 #}
3495
3496 QOI_OP_INDEX = 0x00 # 00xxxxxx
3497 QOI_OP_DIFF = 0x40 # 01xxxxxx
3498 QOI_OP_LUMA = 0x80 # 10xxxxxx
3499 QOI_OP_RUN = 0xc0 # 11xxxxxx
3500 QOI_OP_RGB = 0xfe # 11111110
3501 QOI_OP_RGBA = 0xff # 11111111
3502
3503 QOI_MASK_2 = 0xc0 # 11000000
3504
3505 def qoi_colour_hash( c ):
3506 #{
3507 return c.r*3 + c.g*5 + c.b*7 + c.a*11
3508 #}
3509
3510 def qoi_eq( a, b ):
3511 #{
3512 return (a.r==b.r) and (a.g==b.g) and (a.b==b.b) and (a.a==b.a)
3513 #}
3514
3515 def qoi_32bit( v ):
3516 #{
3517 return bytearray([ (0xff000000 & v) >> 24, \
3518 (0x00ff0000 & v) >> 16, \
3519 (0x0000ff00 & v) >> 8, \
3520 (0x000000ff & v) ])
3521 #}
3522
3523 def qoi_encode( img ):
3524 #{
3525 data = bytearray()
3526
3527 print(F"{' ':<30}",end='\r')
3528 print(F"[QOI] Encoding {img.name}.qoi[{img.size[0]},{img.size[1]}]",end='\r')
3529
3530 index = [ qoi_rgba_t() for _ in range(64) ]
3531
3532 # Header
3533 #
3534 data.extend( bytearray(c_uint32(0x66696f71)) )
3535 data.extend( qoi_32bit( img.size[0] ) )
3536 data.extend( qoi_32bit( img.size[1] ) )
3537 data.extend( bytearray(c_uint8(4)) )
3538 data.extend( bytearray(c_uint8(0)) )
3539
3540 run = 0
3541 px_prev = qoi_rgba_t()
3542 px_prev.r = c_uint8(0)
3543 px_prev.g = c_uint8(0)
3544 px_prev.b = c_uint8(0)
3545 px_prev.a = c_uint8(255)
3546
3547 px = qoi_rgba_t()
3548 px.r = c_uint8(0)
3549 px.g = c_uint8(0)
3550 px.b = c_uint8(0)
3551 px.a = c_uint8(255)
3552
3553 px_len = img.size[0] * img.size[1]
3554 paxels = [ int(min(max(_,0),1)*255) for _ in img.pixels ]
3555
3556 for px_pos in range( px_len ): #{
3557 idx = px_pos * img.channels
3558 nc = img.channels-1
3559
3560 px.r = paxels[idx+min(0,nc)]
3561 px.g = paxels[idx+min(1,nc)]
3562 px.b = paxels[idx+min(2,nc)]
3563 px.a = paxels[idx+min(3,nc)]
3564
3565 if qoi_eq( px, px_prev ): #{
3566 run += 1
3567
3568 if (run == 62) or (px_pos == px_len-1): #{
3569 data.extend( bytearray( c_uint8(QOI_OP_RUN | (run-1))) )
3570 run = 0
3571 #}
3572 #}
3573 else: #{
3574 if run > 0: #{
3575 data.extend( bytearray( c_uint8(QOI_OP_RUN | (run-1))) )
3576 run = 0
3577 #}
3578
3579 index_pos = qoi_colour_hash(px) % 64
3580
3581 if qoi_eq( index[index_pos], px ): #{
3582 data.extend( bytearray( c_uint8(QOI_OP_INDEX | index_pos)) )
3583 #}
3584 else: #{
3585 index[ index_pos ].r = px.r
3586 index[ index_pos ].g = px.g
3587 index[ index_pos ].b = px.b
3588 index[ index_pos ].a = px.a
3589
3590 if px.a == px_prev.a: #{
3591 vr = int(px.r) - int(px_prev.r)
3592 vg = int(px.g) - int(px_prev.g)
3593 vb = int(px.b) - int(px_prev.b)
3594
3595 vg_r = vr - vg
3596 vg_b = vb - vg
3597
3598 if (vr > -3) and (vr < 2) and\
3599 (vg > -3) and (vg < 2) and\
3600 (vb > -3) and (vb < 2):
3601 #{
3602 op = QOI_OP_DIFF | (vr+2) << 4 | (vg+2) << 2 | (vb+2)
3603 data.extend( bytearray( c_uint8(op) ))
3604 #}
3605 elif (vg_r > -9) and (vg_r < 8) and\
3606 (vg > -33) and (vg < 32 ) and\
3607 (vg_b > -9) and (vg_b < 8):
3608 #{
3609 op = QOI_OP_LUMA | (vg+32)
3610 delta = (vg_r+8) << 4 | (vg_b + 8)
3611 data.extend( bytearray( c_uint8(op) ) )
3612 data.extend( bytearray( c_uint8(delta) ))
3613 #}
3614 else: #{
3615 data.extend( bytearray( c_uint8(QOI_OP_RGB) ) )
3616 data.extend( bytearray( c_uint8(px.r) ))
3617 data.extend( bytearray( c_uint8(px.g) ))
3618 data.extend( bytearray( c_uint8(px.b) ))
3619 #}
3620 #}
3621 else: #{
3622 data.extend( bytearray( c_uint8(QOI_OP_RGBA) ) )
3623 data.extend( bytearray( c_uint8(px.r) ))
3624 data.extend( bytearray( c_uint8(px.g) ))
3625 data.extend( bytearray( c_uint8(px.b) ))
3626 data.extend( bytearray( c_uint8(px.a) ))
3627 #}
3628 #}
3629 #}
3630
3631 px_prev.r = px.r
3632 px_prev.g = px.g
3633 px_prev.b = px.b
3634 px_prev.a = px.a
3635 #}
3636
3637 # Padding
3638 for i in range(7):
3639 data.extend( bytearray( c_uint8(0) ))
3640 data.extend( bytearray( c_uint8(1) ))
3641 bytearray_align_to( data, 16, b'\x00' )
3642
3643 return data
3644 #}