nonlocal stuff again
[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 obj_data = obj.SR_data.ent_gate[0]
1294 mesh_data = obj.data.SR_data.ent_gate[0]
1295
1296 if obj_data.tipo == 'default':#{
1297 if obj_data.target:#{
1298 gate.target = sr_compile.entity_ids[obj_data.target.name]
1299 gate.type = 1
1300 #}
1301 #}
1302 elif obj_data.tipo == 'nonlocal':#{
1303 gate.target = sr_compile_string(obj_data.key)
1304 gate.type = 2
1305 #}
1306 else: gate.type = 0
1307
1308 gate.dimensions[0] = mesh_data.dimensions[0]
1309 gate.dimensions[1] = mesh_data.dimensions[1]
1310 gate.dimensions[2] = mesh_data.dimensions[2]
1311
1312 q = [obj.matrix_local.to_quaternion(), (0,0,0,1)]
1313 co = [obj.matrix_world @ Vector((0,0,0)), (0,0,0)]
1314
1315 if obj_data.target:#{
1316 q[1] = obj_data.target.matrix_local.to_quaternion()
1317 co[1]= obj_data.target.matrix_world @ Vector((0,0,0))
1318 #}
1319
1320 # Setup transform
1321 #
1322 for x in range(2):#{
1323 gate.co[x][0] = co[x][0]
1324 gate.co[x][1] = co[x][2]
1325 gate.co[x][2] = -co[x][1]
1326 gate.q[x][0] = q[x][1]
1327 gate.q[x][1] = q[x][3]
1328 gate.q[x][2] = -q[x][2]
1329 gate.q[x][3] = q[x][0]
1330 #}
1331
1332 sr_ent_push( gate )
1333 #}
1334 elif ent_type == 'ent_spawn': #{
1335 spawn = ent_spawn()
1336 compile_obj_transform( obj, spawn.transform )
1337 obj_data = obj.SR_data.ent_spawn[0]
1338 spawn.pstr_name = sr_compile_string( obj_data.alias )
1339 sr_ent_push( spawn )
1340 #}
1341 elif ent_type == 'ent_water':#{
1342 water = ent_water()
1343 compile_obj_transform( obj, water.transform )
1344 water.max_dist = 0.0
1345 sr_ent_push( water )
1346 #}
1347 elif ent_type == 'ent_audio':#{
1348 obj_data = obj.SR_data.ent_audio[0]
1349 audio = ent_audio()
1350 compile_obj_transform( obj, audio.transform )
1351 audio.clip_start = audio_clip_count
1352 audio.clip_count = len(obj_data.files)
1353 audio_clip_count += audio.clip_count
1354 audio.max_channels = obj_data.max_channels
1355 audio.volume = obj_data.volume
1356
1357 # TODO flags:
1358 # - allow/disable doppler
1359 # - channel group tags with random colours
1360 # - transition properties
1361
1362 if obj_data.flag_loop: audio.flags |= 0x1
1363 if obj_data.flag_nodoppler: audio.flags |= 0x2
1364 if obj_data.flag_3d: audio.flags |= 0x4
1365 if obj_data.flag_auto: audio.flags |= 0x8
1366 if obj_data.formato == '0': audio.flags |= 0x000
1367 elif obj_data.formato == '1': audio.flags |= 0x400
1368 elif obj_data.formato == '2': audio.flags |= 0x1000
1369
1370 audio.channel_behaviour = int(obj_data.channel_behaviour)
1371 if audio.channel_behaviour >= 1:#{
1372 audio.group = obj_data.group
1373 #}
1374 if audio.channel_behaviour == 2:#{
1375 audio.crossfade = obj_data.transition_duration
1376 #}
1377 audio.probability_curve = int(obj_data.probability_curve)
1378
1379 for ci in range(audio.clip_count):#{
1380 entry = obj_data.files[ci]
1381 clip = ent_audio_clip()
1382 clip.probability = entry.probability
1383 if obj_data.formato == '2':#{
1384 sr_pack_file( clip._anon.file, '', vg_str_bin(entry.path) )
1385 #}
1386 else:#{
1387 clip._anon.file.path = sr_compile_string( entry.path )
1388 clip._anon.file.pack_offset = 0
1389 clip._anon.file.pack_size = 0
1390 #}
1391 sr_ent_push( clip )
1392 #}
1393 sr_ent_push( audio )
1394 #}
1395 elif ent_type == 'ent_volume':#{
1396 obj_data = obj.SR_data.ent_volume[0]
1397 volume = ent_volume()
1398 volume.type = int(obj_data.subtype)
1399 compile_obj_transform( obj, volume.transform )
1400
1401 if obj_data.target:#{
1402 target = obj_data.target
1403 volume.target.type = sr_entity_alias[obj_ent_type(target)]
1404 volume.target.index = sr_compile.entity_ids[ target.name ]
1405 #}
1406
1407 sr_ent_push(volume)
1408 #}
1409 elif ent_type == 'ent_marker':#{
1410 marker = ent_marker()
1411 marker.name = sr_compile_string( obj.SR_data.ent_marker[0].alias )
1412 compile_obj_transform( obj, marker.transform )
1413 sr_ent_push(marker)
1414 #}
1415 #}
1416 #}
1417
1418 sr_compile_fonts(collection)
1419
1420 def _children( col ):#{
1421 yield col
1422 for c in col.children:#{
1423 yield from _children(c)
1424 #}
1425 #}
1426
1427 checkpoint_count = 0
1428 pathindice_count = 0
1429 routenode_count = 0
1430
1431 for col in _children(collection):#{
1432 print( F"Adding routes for subcollection: {col.name}" )
1433 route_gates = []
1434 route_curves = []
1435 routes = []
1436
1437 for obj in col.objects:#{
1438 if obj.type == 'ARMATURE': pass
1439 else:#{
1440 ent_type = obj_ent_type( obj )
1441
1442 if ent_type == 'ent_gate':
1443 route_gates += [obj]
1444 elif ent_type == 'ent_route_node':#{
1445 if obj.type == 'CURVE':#{
1446 route_curves += [obj]
1447 #}
1448 #}
1449 elif ent_type == 'ent_route':
1450 routes += [obj]
1451 #}
1452 #}
1453
1454 dij = create_node_graph( route_curves, route_gates )
1455
1456 for obj in routes:#{
1457 obj_data = obj.SR_data.ent_route[0]
1458 route = ent_route()
1459 route.pstr_name = sr_compile_string( obj_data.alias )
1460 route.checkpoints_start = checkpoint_count
1461 route.checkpoints_count = 0
1462
1463 for ci in range(3):
1464 route.colour[ci] = obj_data.colour[ci]
1465 route.colour[3] = 1.0
1466
1467 compile_obj_transform( obj, route.transform )
1468 checkpoints = obj_data.gates
1469
1470 for i in range(len(checkpoints)):#{
1471 gi = checkpoints[i].target
1472 gj = checkpoints[(i+1)%len(checkpoints)].target
1473 gate = gi
1474
1475 if gi:#{
1476 dest = gi.SR_data.ent_gate[0].target
1477 gi = dest
1478 #}
1479
1480 if gi==gj: continue # error?
1481 if not gi or not gj: continue
1482
1483 checkpoint = ent_checkpoint()
1484 checkpoint.gate_index = sr_compile.entity_ids[gate.name]
1485 checkpoint.path_start = pathindice_count
1486 checkpoint.path_count = 0
1487
1488 path = solve_graph( dij, gi.name, gj.name )
1489
1490 if path:#{
1491 for pi in range(len(path)):#{
1492 pathindice = ent_path_index()
1493 pathindice.index = routenode_count + path[pi]
1494 sr_ent_push( pathindice )
1495
1496 checkpoint.path_count += 1
1497 pathindice_count += 1
1498 #}
1499 #}
1500
1501 sr_ent_push( checkpoint )
1502 route.checkpoints_count += 1
1503 checkpoint_count += 1
1504 #}
1505
1506 sr_ent_push( route )
1507 #}
1508
1509 for point in dij.points:#{
1510 rn = ent_route_node()
1511 rn.co[0] = point[0]
1512 rn.co[1] = point[2]
1513 rn.co[2] = -point[1]
1514 sr_ent_push( rn )
1515 #}
1516
1517 routenode_count += len(dij.points)
1518 #}
1519
1520
1521 print( F"[SR] Writing file" )
1522
1523 file_array_instructions = {}
1524 file_offset = 0
1525
1526 def _write_array( name, item_size, data ):#{
1527 nonlocal file_array_instructions, file_offset
1528
1529 count = len(data)//item_size
1530 file_array_instructions[name] = {'count':count, 'size':item_size,\
1531 'data':data, 'offset': file_offset}
1532 file_offset += len(data)
1533 file_offset = int_align_to( file_offset, 8 )
1534 #}
1535
1536 _write_array( 'strings', 1, sr_compile.string_data )
1537 _write_array( 'mdl_mesh', sizeof(mdl_mesh), sr_compile.mesh_data )
1538 _write_array( 'mdl_submesh', sizeof(mdl_submesh), sr_compile.submesh_data )
1539 _write_array( 'mdl_material', sizeof(mdl_material), sr_compile.material_data)
1540 _write_array( 'mdl_texture', sizeof(mdl_texture), sr_compile.texture_data)
1541 _write_array( 'mdl_armature', sizeof(mdl_armature), sr_compile.armature_data)
1542 _write_array( 'mdl_bone', sizeof(mdl_bone), sr_compile.bone_data )
1543
1544 for name, buffer in sr_compile.entity_data.items():#{
1545 _write_array( name, sr_compile.entity_info[name]['size'], buffer )
1546 #}
1547
1548 _write_array( 'mdl_animation', sizeof(mdl_animation), sr_compile.anim_data)
1549 _write_array( 'mdl_keyframe', sizeof(mdl_transform),sr_compile.keyframe_data)
1550 _write_array( 'mdl_vert', sizeof(mdl_vert), sr_compile.vertex_data )
1551 _write_array( 'mdl_indice', sizeof(c_uint32), sr_compile.indice_data )
1552 _write_array( 'pack', 1, sr_compile.pack_data )
1553
1554 header_size = int_align_to( sizeof(mdl_header), 8 )
1555 index_size = int_align_to( sizeof(mdl_array)*len(file_array_instructions),8 )
1556
1557 folder = bpy.path.abspath(bpy.context.scene.SR_data.export_dir)
1558 path = F"{folder}{collection.name}.mdl"
1559 print( path )
1560
1561 fp = open( path, "wb" )
1562 header = mdl_header()
1563 header.version = 40
1564 sr_array_title( header.arrays, \
1565 'index', len(file_array_instructions), \
1566 sizeof(mdl_array), header_size )
1567
1568 fp.write( bytearray_align_to( bytearray(header), 8 ) )
1569
1570 print( F'[SR] {"name":>16}| count | offset' )
1571 index = bytearray()
1572 for name,info in file_array_instructions.items():#{
1573 arr = mdl_array()
1574 offset = info['offset'] + header_size + index_size
1575 sr_array_title( arr, name, info['count'], info['size'], offset )
1576 index.extend( bytearray(arr) )
1577
1578 print( F'[SR] {name:>16}| {info["count"]: 8} '+\
1579 F' 0x{info["offset"]:02x}' )
1580 #}
1581 fp.write( bytearray_align_to( index, 8 ) )
1582 #bytearray_print_hex( index )
1583
1584 for name,info in file_array_instructions.items():#{
1585 fp.write( bytearray_align_to( info['data'], 8 ) )
1586 #}
1587
1588 fp.close()
1589
1590 print( '[SR] done' )
1591 #}
1592
1593 class SR_SCENE_SETTINGS(bpy.types.PropertyGroup):
1594 #{
1595 use_hidden: bpy.props.BoolProperty( name="use hidden", default=False )
1596 export_dir: bpy.props.StringProperty( name="Export Dir", subtype='DIR_PATH' )
1597 gizmos: bpy.props.BoolProperty( name="Draw Gizmos", default=True )
1598
1599 panel: bpy.props.EnumProperty(
1600 name='Panel',
1601 description='',
1602 items=[
1603 ('EXPORT', 'Export', '', 'MOD_BUILD',0),
1604 ('ENTITY', 'Entity', '', 'MONKEY',1),
1605 ('SETTINGS', 'Settings', 'Settings', 'PREFERENCES',2),
1606 ],
1607 )
1608 #}
1609
1610 class SR_COLLECTION_SETTINGS(bpy.types.PropertyGroup):
1611 #{
1612 pack_textures: bpy.props.BoolProperty( name="Pack Textures", default=False )
1613 animations: bpy.props.BoolProperty( name="Export animation", default=True)
1614 #}
1615
1616 def sr_get_mirror_bone( bones ):
1617 #{
1618 side = bones.active.name[-1:]
1619 other_name = bones.active.name[:-1]
1620 if side == 'L': other_name += 'R'
1621 elif side == 'R': other_name += 'L'
1622 else: return None
1623
1624 for b in bones:#{
1625 if b.name == other_name:
1626 return b
1627 #}
1628
1629 return None
1630 #}
1631
1632 class SR_MIRROR_BONE_X(bpy.types.Operator):
1633 #{
1634 bl_idname="skaterift.mirror_bone"
1635 bl_label="Mirror bone attributes - SkateRift"
1636
1637 def execute(_,context):
1638 #{
1639 active_object = context.active_object
1640 bones = active_object.data.bones
1641 a = bones.active
1642 b = sr_get_mirror_bone( bones )
1643
1644 if not b: return {'FINISHED'}
1645
1646 b.SR_data.collider = a.SR_data.collider
1647
1648 def _v3copyflipy( a, b ):#{
1649 b[0] = a[0]
1650 b[1] = -a[1]
1651 b[2] = a[2]
1652 #}
1653
1654 _v3copyflipy( a.SR_data.collider_min, b.SR_data.collider_min )
1655 _v3copyflipy( a.SR_data.collider_max, b.SR_data.collider_max )
1656 b.SR_data.collider_min[1] = -a.SR_data.collider_max[1]
1657 b.SR_data.collider_max[1] = -a.SR_data.collider_min[1]
1658
1659 b.SR_data.cone_constraint = a.SR_data.cone_constraint
1660
1661 _v3copyflipy( a.SR_data.conevx, b.SR_data.conevy )
1662 _v3copyflipy( a.SR_data.conevy, b.SR_data.conevx )
1663 _v3copyflipy( a.SR_data.coneva, b.SR_data.coneva )
1664
1665 b.SR_data.conet = a.SR_data.conet
1666
1667 # redraw
1668 ob = bpy.context.scene.objects[0]
1669 ob.hide_render = ob.hide_render
1670 return {'FINISHED'}
1671 #}
1672 #}
1673
1674 class SR_COMPILE(bpy.types.Operator):
1675 #{
1676 bl_idname="skaterift.compile_all"
1677 bl_label="Compile All"
1678
1679 def execute(_,context):
1680 #{
1681 view_layer = bpy.context.view_layer
1682 for col in view_layer.layer_collection.children["export"].children:
1683 if not col.hide_viewport or bpy.context.scene.SR_data.use_hidden:
1684 sr_compile( bpy.data.collections[col.name] )
1685
1686 return {'FINISHED'}
1687 #}
1688 #}
1689
1690 class SR_COMPILE_THIS(bpy.types.Operator):
1691 #{
1692 bl_idname="skaterift.compile_this"
1693 bl_label="Compile This collection"
1694
1695 def execute(_,context):
1696 #{
1697 col = bpy.context.collection
1698 sr_compile( col )
1699
1700 return {'FINISHED'}
1701 #}
1702 #}
1703
1704 class SR_INTERFACE(bpy.types.Panel):
1705 #{
1706 bl_idname = "VIEW3D_PT_skate_rift"
1707 bl_label = "Skate Rift"
1708 bl_space_type = 'VIEW_3D'
1709 bl_region_type = 'UI'
1710 bl_category = "Skate Rift"
1711
1712 def draw(_, context):
1713 #{
1714 # Compiler section
1715
1716 row = _.layout.row()
1717 row.scale_y = 1.75
1718 row.prop( context.scene.SR_data, 'panel', expand=True )
1719
1720 if context.scene.SR_data.panel == 'SETTINGS': #{
1721 _.layout.prop( context.scene.SR_data, 'gizmos' )
1722 #}
1723 elif context.scene.SR_data.panel == 'EXPORT': #{
1724 _.layout.prop( context.scene.SR_data, "export_dir" )
1725 col = bpy.context.collection
1726
1727 found_in_export = False
1728 export_count = 0
1729 view_layer = bpy.context.view_layer
1730 for c1 in view_layer.layer_collection.children["export"].children: #{
1731 if not c1.hide_viewport or bpy.context.scene.SR_data.use_hidden:
1732 export_count += 1
1733
1734 if c1.name == col.name: #{
1735 found_in_export = True
1736 #}
1737 #}
1738
1739 box = _.layout.box()
1740 row = box.row()
1741 row.alignment = 'CENTER'
1742 row.scale_y = 1.5
1743
1744 if found_in_export: #{
1745 row.label( text=col.name + ".mdl" )
1746 box.prop( col.SR_data, "pack_textures" )
1747 box.prop( col.SR_data, "animations" )
1748 box.operator( "skaterift.compile_this" )
1749 #}
1750 else: #{
1751 row.enabled=False
1752 row.label( text=col.name )
1753
1754 row = box.row()
1755 row.enabled=False
1756 row.alignment = 'CENTER'
1757 row.scale_y = 1.5
1758 row.label( text="This collection is not in the export group" )
1759 #}
1760
1761 box = _.layout.box()
1762 row = box.row()
1763
1764 split = row.split( factor=0.3, align=True )
1765 split.prop( context.scene.SR_data, "use_hidden", text="hidden" )
1766
1767 row1 = split.row()
1768 if export_count == 0:
1769 row1.enabled=False
1770 row1.operator( "skaterift.compile_all", \
1771 text=F"Compile all ({export_count} collections)" )
1772 #}
1773 elif context.scene.SR_data.panel == 'ENTITY': #{
1774 active_object = context.active_object
1775 if not active_object: return
1776
1777 _.layout.operator( 'skaterift.copy_entity_data', \
1778 text=F'Copy entity data to {len(context.selected_objects)-1} '+\
1779 F'other objects' )
1780
1781 box = _.layout.box()
1782 row = box.row()
1783 row.alignment = 'CENTER'
1784 row.label( text=active_object.name )
1785 row.scale_y = 1.5
1786
1787 def _draw_prop_collection( data ): #{
1788 nonlocal box
1789 row = box.row()
1790 row.alignment = 'CENTER'
1791 row.enabled = False
1792 row.scale_y = 1.5
1793 row.label( text=F'{data[0]}' )
1794
1795 if hasattr(type(data[0]),'sr_inspector'):#{
1796 type(data[0]).sr_inspector( box, data )
1797 #}
1798 else:#{
1799 for a in data[0].__annotations__:
1800 box.prop( data[0], a )
1801 #}
1802 #}
1803
1804 if active_object.type == 'ARMATURE': #{
1805 if active_object.mode == 'POSE': #{
1806 bones = active_object.data.bones
1807 mb = sr_get_mirror_bone( bones )
1808 if mb:#{
1809 box.operator( "skaterift.mirror_bone", \
1810 text=F'Mirror attributes to {mb.name}' )
1811 #}
1812
1813 _draw_prop_collection( [bones.active.SR_data ] )
1814 #}
1815 else: #{
1816 row = box.row()
1817 row.alignment='CENTER'
1818 row.scale_y=2.0
1819 row.enabled=False
1820 row.label( text="Enter pose mode to modify bone properties" )
1821 #}
1822 #}
1823 elif active_object.type == 'LIGHT': #{
1824 _draw_prop_collection( [active_object.data.SR_data] )
1825 #}
1826 elif active_object.type in ['EMPTY','CURVE','MESH']:#{
1827 box.prop( active_object.SR_data, "ent_type" )
1828 ent_type = active_object.SR_data.ent_type
1829
1830 col = getattr( active_object.SR_data, ent_type, None )
1831 if col != None and len(col)!=0: _draw_prop_collection( col )
1832
1833 if active_object.type == 'MESH':#{
1834 col = getattr( active_object.data.SR_data, ent_type, None )
1835 if col != None and len(col)!=0: _draw_prop_collection( col )
1836 #}
1837 #}
1838 #}
1839 #}
1840 #}
1841
1842 class SR_MATERIAL_PANEL(bpy.types.Panel):
1843 #{
1844 bl_label="Skate Rift material"
1845 bl_idname="MATERIAL_PT_sr_material"
1846 bl_space_type='PROPERTIES'
1847 bl_region_type='WINDOW'
1848 bl_context="material"
1849
1850 def draw(_,context):
1851 #{
1852 active_object = bpy.context.active_object
1853 if active_object == None: return
1854 active_mat = active_object.active_material
1855 if active_mat == None: return
1856
1857 info = material_info( active_mat )
1858
1859 if 'tex_diffuse' in info:#{
1860 _.layout.label( icon='INFO', \
1861 text=F"{info['tex_diffuse'].name} will be compiled" )
1862 #}
1863
1864 _.layout.prop( active_mat.SR_data, "shader" )
1865 _.layout.prop( active_mat.SR_data, "surface_prop" )
1866 _.layout.prop( active_mat.SR_data, "collision" )
1867
1868 if active_mat.SR_data.collision:#{
1869 _.layout.prop( active_mat.SR_data, "skate_surface" )
1870 _.layout.prop( active_mat.SR_data, "grind_surface" )
1871 _.layout.prop( active_mat.SR_data, "grow_grass" )
1872 #}
1873
1874 if active_mat.SR_data.shader == "terrain_blend":#{
1875 box = _.layout.box()
1876 box.prop( active_mat.SR_data, "blend_offset" )
1877 box.prop( active_mat.SR_data, "sand_colour" )
1878 #}
1879 elif active_mat.SR_data.shader == "vertex_blend":#{
1880 box = _.layout.box()
1881 box.label( icon='INFO', text="Uses vertex colours, the R channel" )
1882 box.prop( active_mat.SR_data, "blend_offset" )
1883 #}
1884 elif active_mat.SR_data.shader == "water":#{
1885 box = _.layout.box()
1886 box.label( icon='INFO', text="Depth scale of 16 meters" )
1887 box.prop( active_mat.SR_data, "shore_colour" )
1888 box.prop( active_mat.SR_data, "ocean_colour" )
1889 #}
1890 #}
1891 #}
1892
1893 def sr_get_type_enum( scene, context ):
1894 #{
1895 items = [('none','None',"")]
1896 mesh_entities=['ent_gate','ent_water']
1897 point_entities=['ent_spawn','ent_route_node','ent_route']
1898
1899 for e in point_entities: items += [(e,e,'')]
1900
1901 if context.scene.SR_data.panel == 'ENTITY': #{
1902 if context.active_object.type == 'MESH': #{
1903 for e in mesh_entities: items += [(e,e,'')]
1904 #}
1905 #}
1906 else: #{
1907 for e in mesh_entities: items += [(e,e,'')]
1908 #}
1909
1910 return items
1911 #}
1912
1913 def sr_on_type_change( _, context ):
1914 #{
1915 obj = context.active_object
1916 ent_type = obj.SR_data.ent_type
1917 if ent_type == 'none': return
1918 if obj.type == 'MESH':#{
1919 col = getattr( obj.data.SR_data, ent_type, None )
1920 if col != None and len(col)==0: col.add()
1921 #}
1922
1923 col = getattr( obj.SR_data, ent_type, None )
1924 if col != None and len(col)==0: col.add()
1925 #}
1926
1927 class SR_OBJECT_ENT_SPAWN(bpy.types.PropertyGroup):
1928 #{
1929 alias: bpy.props.StringProperty( name='alias' )
1930 #}
1931
1932 class SR_OBJECT_ENT_GATE(bpy.types.PropertyGroup):
1933 #{
1934 target: bpy.props.PointerProperty( \
1935 type=bpy.types.Object, name="destination", \
1936 poll=lambda self,obj: sr_filter_ent_type(obj,['ent_gate']))
1937
1938 key: bpy.props.StringProperty()
1939 tipo: bpy.props.EnumProperty(items=(('default', 'Default', ""),
1940 ('nonlocal', 'Non-Local', ""),))
1941
1942 @staticmethod
1943 def sr_inspector( layout, data ):
1944 #{
1945 box = layout.box()
1946 box.prop( data[0], 'tipo', text="subtype" )
1947
1948 if data[0].tipo == 'default': box.prop( data[0], 'target' )
1949 elif data[0].tipo == 'nonlocal': box.prop( data[0], 'key' )
1950 #}
1951 #}
1952
1953 class SR_MESH_ENT_GATE(bpy.types.PropertyGroup):
1954 #{
1955 dimensions: bpy.props.FloatVectorProperty(name="dimensions",size=3)
1956 #}
1957
1958 class SR_OBJECT_ENT_ROUTE_ENTRY(bpy.types.PropertyGroup):
1959 #{
1960 target: bpy.props.PointerProperty( \
1961 type=bpy.types.Object, name='target', \
1962 poll=lambda self,obj: sr_filter_ent_type(obj,['ent_gate']))
1963 #}
1964
1965 class SR_UL_ROUTE_NODE_LIST(bpy.types.UIList):
1966 #{
1967 bl_idname = 'SR_UL_ROUTE_NODE_LIST'
1968
1969 def draw_item(_,context,layout,data,item,icon,active_data,active_propname):
1970 #{
1971 layout.prop( item, 'target', text='', emboss=False )
1972 #}
1973 #}
1974
1975 def internal_listdel_execute(self,context,ent_name,collection_name):
1976 #{
1977 active_object = context.active_object
1978 data = getattr(active_object.SR_data,ent_name)[0]
1979 lista = getattr(data,collection_name)
1980 index = getattr(data,F'{collection_name}_index')
1981
1982 lista.remove(index)
1983
1984 setattr(data,F'{collection_name}_index', min(max(0,index-1), len(lista)-1))
1985 return{'FINISHED'}
1986 #}
1987
1988 def internal_listadd_execute(self,context,ent_name,collection_name):
1989 #{
1990 active_object = context.active_object
1991 getattr(getattr(active_object.SR_data,ent_name)[0],collection_name).add()
1992 return{'FINISHED'}
1993 #}
1994
1995 def copy_propgroup( de, to ):
1996 #{
1997 for a in de.__annotations__:#{
1998 if isinstance(getattr(de,a), bpy.types.bpy_prop_collection):#{
1999 ca = getattr(de,a)
2000 cb = getattr(to,a)
2001
2002 while len(cb) != len(ca):#{
2003 if len(cb) < len(ca): cb.add()
2004 else: cb.remove(0)
2005 #}
2006 for i in range(len(ca)):#{
2007 copy_propgroup(ca[i],cb[i])
2008 #}
2009 #}
2010 else:#{
2011 setattr(to,a,getattr(de,a))
2012 #}
2013 #}
2014 #}
2015
2016 class SR_OT_COPY_ENTITY_DATA(bpy.types.Operator):
2017 #{
2018 bl_idname = "skaterift.copy_entity_data"
2019 bl_label = "Copy entity data"
2020
2021 def execute(self, context):#{
2022 data = context.active_object.SR_data
2023 new_type = data.ent_type
2024 print( F"Copy entity data from: {context.active_object.name}" )
2025
2026 for obj in context.selected_objects:#{
2027 if obj != context.active_object:#{
2028 print( F" To: {obj.name}" )
2029
2030 obj.SR_data.ent_type = new_type
2031
2032 if active_object.type == 'MESH':#{
2033 col = getattr( obj.data.SR_data, new_type, None )
2034 if col != None and len(col)==0: col.add()
2035 mdata = context.active_object.data.SR_data
2036 copy_propgroup( getattr(mdata,new_type)[0], col[0] )
2037 #}
2038
2039 col = getattr( obj.SR_data, new_type, None )
2040 if col != None and len(col)==0: col.add()
2041 copy_propgroup( getattr(data,new_type)[0], col[0] )
2042 #}
2043 #}
2044 return{'FINISHED'}
2045 #}
2046 #}
2047
2048 class SR_OT_ROUTE_LIST_NEW_ITEM(bpy.types.Operator):
2049 #{
2050 bl_idname = "skaterift.new_entry"
2051 bl_label = "Add gate"
2052
2053 def execute(self, context):#{
2054 return internal_listadd_execute(self,context,'ent_route','gates')
2055 #}
2056 #}
2057
2058 class SR_OT_ROUTE_LIST_DEL_ITEM(bpy.types.Operator):
2059 #{
2060 bl_idname = "skaterift.del_entry"
2061 bl_label = "Remove gate"
2062
2063 @classmethod
2064 def poll(cls, context):#{
2065 active_object = context.active_object
2066 if obj_ent_type(active_object) == 'ent_route':#{
2067 return active_object.SR_data.ent_route[0].gates
2068 #}
2069 else: return False
2070 #}
2071
2072 def execute(self, context):#{
2073 return internal_listdel_execute(self,context,'ent_route','gates')
2074 #}
2075 #}
2076
2077 class SR_OT_AUDIO_LIST_NEW_ITEM(bpy.types.Operator):
2078 #{
2079 bl_idname = "skaterift.al_new_entry"
2080 bl_label = "Add file"
2081
2082 def execute(self, context):#{
2083 return internal_listadd_execute(self,context,'ent_audio','files')
2084 #}
2085 #}
2086
2087 class SR_OT_AUDIO_LIST_DEL_ITEM(bpy.types.Operator):
2088 #{
2089 bl_idname = "skaterift.al_del_entry"
2090 bl_label = "Remove file"
2091
2092 @classmethod
2093 def poll(cls, context):#{
2094 active_object = context.active_object
2095 if obj_ent_type(active_object) == 'ent_audio':#{
2096 return active_object.SR_data.ent_audio[0].files
2097 #}
2098 else: return False
2099 #}
2100
2101 def execute(self, context):#{
2102 return internal_listdel_execute(self,context,'ent_audio','files')
2103 return{'FINISHED'}
2104 #}
2105 #}
2106
2107 class SR_OT_GLYPH_LIST_NEW_ITEM(bpy.types.Operator):
2108 #{
2109 bl_idname = "skaterift.gl_new_entry"
2110 bl_label = "Add glyph"
2111
2112 def execute(self, context):#{
2113 active_object = context.active_object
2114
2115 font = active_object.SR_data.ent_font[0]
2116 font.glyphs.add()
2117
2118 if len(font.glyphs) > 1:#{
2119 prev = font.glyphs[-2]
2120 cur = font.glyphs[-1]
2121
2122 cur.bounds = prev.bounds
2123 cur.utf32 = prev.utf32+1
2124 #}
2125
2126 return{'FINISHED'}
2127 #}
2128 #}
2129
2130 class SR_OT_GLYPH_LIST_DEL_ITEM(bpy.types.Operator):
2131 #{
2132 bl_idname = "skaterift.gl_del_entry"
2133 bl_label = "Remove Glyph"
2134
2135 @classmethod
2136 def poll(cls, context):#{
2137 active_object = context.active_object
2138 if obj_ent_type(active_object) == 'ent_font':#{
2139 return active_object.SR_data.ent_font[0].glyphs
2140 #}
2141 else: return False
2142 #}
2143
2144 def execute(self, context):#{
2145 return internal_listdel_execute(self,context,'ent_font','glyphs')
2146 #}
2147 #}
2148
2149 class SR_OT_GLYPH_LIST_MOVE_ITEM(bpy.types.Operator):
2150 #{
2151 bl_idname = "skaterift.gl_move_item"
2152 bl_label = "aa"
2153 direction: bpy.props.EnumProperty(items=(('UP', 'Up', ""),
2154 ('DOWN', 'Down', ""),))
2155
2156 @classmethod
2157 def poll(cls, context):#{
2158 active_object = context.active_object
2159 if obj_ent_type(active_object) == 'ent_font':#{
2160 return active_object.SR_data.ent_font[0].glyphs
2161 #}
2162 else: return False
2163 #}
2164
2165 def execute(_, context):#{
2166 active_object = context.active_object
2167 data = active_object.SR_data.ent_font[0]
2168
2169 index = data.glyphs_index
2170 neighbor = index + (-1 if _.direction == 'UP' else 1)
2171 data.glyphs.move( neighbor, index )
2172
2173 list_length = len(data.glyphs) - 1
2174 new_index = index + (-1 if _.direction == 'UP' else 1)
2175
2176 data.glyphs_index = max(0, min(new_index, list_length))
2177
2178 return{'FINISHED'}
2179 #}
2180 #}
2181
2182 class SR_OT_FONT_VARIANT_LIST_NEW_ITEM(bpy.types.Operator):
2183 #{
2184 bl_idname = "skaterift.fv_new_entry"
2185 bl_label = "Add variant"
2186
2187 def execute(self, context):#{
2188 return internal_listadd_execute(self,context,'ent_font','variants')
2189 #}
2190 #}
2191
2192 class SR_OT_FONT_VARIANT_LIST_DEL_ITEM(bpy.types.Operator):
2193 #{
2194 bl_idname = "skaterift.fv_del_entry"
2195 bl_label = "Remove variant"
2196
2197 @classmethod
2198 def poll(cls, context):#{
2199 active_object = context.active_object
2200 if obj_ent_type(active_object) == 'ent_font':#{
2201 return active_object.SR_data.ent_font[0].variants
2202 #}
2203 else: return False
2204 #}
2205
2206 def execute(self, context):#{
2207 return internal_listdel_execute(self,context,'ent_font','variants')
2208 #}
2209 #}
2210
2211 class SR_OBJECT_ENT_AUDIO_FILE_ENTRY(bpy.types.PropertyGroup):
2212 #{
2213 path: bpy.props.StringProperty( name="Path" )
2214 probability: bpy.props.FloatProperty( name="Probability",default=100.0 )
2215 #}
2216
2217 class SR_UL_AUDIO_LIST(bpy.types.UIList):
2218 #{
2219 bl_idname = 'SR_UL_AUDIO_LIST'
2220
2221 def draw_item(_,context,layout,data,item,icon,active_data,active_propname):
2222 #{
2223 split = layout.split(factor=0.7)
2224 c = split.column()
2225 c.prop( item, 'path', text='', emboss=False )
2226 c = split.column()
2227 c.prop( item, 'probability', text='%', emboss=True )
2228 #}
2229 #}
2230
2231 class SR_UL_FONT_VARIANT_LIST(bpy.types.UIList):
2232 #{
2233 bl_idname = 'SR_UL_FONT_VARIANT_LIST'
2234
2235 def draw_item(_,context,layout,data,item,icon,active_data,active_propname):
2236 #{
2237 layout.prop( item, 'mesh', emboss=False )
2238 layout.prop( item, 'tipo' )
2239 #}
2240 #}
2241
2242 class SR_UL_FONT_GLYPH_LIST(bpy.types.UIList):
2243 #{
2244 bl_idname = 'SR_UL_FONT_GLYPH_LIST'
2245
2246 def draw_item(_,context,layout,data,item,icon,active_data,active_propname):
2247 #{
2248 s0 = layout.split(factor=0.3)
2249 c = s0.column()
2250 s1 = c.split(factor=0.3)
2251 c = s1.column()
2252 row = c.row()
2253 lbl = chr(item.utf32) if item.utf32 >= 32 and item.utf32 <= 126 else 'ERR'
2254 row.label(text=lbl)
2255 c = s1.column()
2256 c.prop( item, 'utf32', text='', emboss=True )
2257 c = s0.column()
2258 row = c.row()
2259 row.prop( item, 'bounds', text='', emboss=False )
2260 #}
2261 #}
2262
2263 class SR_OBJECT_ENT_ROUTE(bpy.types.PropertyGroup):
2264 #{
2265 gates: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_ROUTE_ENTRY)
2266 gates_index: bpy.props.IntProperty()
2267
2268 colour: bpy.props.FloatVectorProperty( \
2269 name="Colour",\
2270 subtype='COLOR',\
2271 min=0.0,max=1.0,\
2272 default=Vector((0.79,0.63,0.48)),\
2273 description="Route colour"\
2274 )
2275
2276 alias: bpy.props.StringProperty(\
2277 name="Alias",\
2278 default="Untitled Course")
2279
2280 @staticmethod
2281 def sr_inspector( layout, data ):
2282 #{
2283 layout.prop( data[0], 'alias' )
2284 layout.prop( data[0], 'colour' )
2285
2286 layout.label( text='Checkpoints' )
2287 layout.template_list('SR_UL_ROUTE_NODE_LIST', 'Checkpoints', \
2288 data[0], 'gates', data[0], 'gates_index', rows=5)
2289
2290 row = layout.row()
2291 row.operator( 'skaterift.new_entry', text='Add' )
2292 row.operator( 'skaterift.del_entry', text='Remove' )
2293 #}
2294 #}
2295
2296 class SR_OBJECT_ENT_VOLUME(bpy.types.PropertyGroup):
2297 #{
2298 subtype: bpy.props.EnumProperty(
2299 name="Subtype",
2300 items=[('0','Trigger',''),
2301 ('1','Particles (0.1s)','')]
2302 )
2303
2304 target: bpy.props.PointerProperty( \
2305 type=bpy.types.Object, name="Target", \
2306 poll=lambda self,obj: sr_filter_ent_type(obj,['ent_audio']))
2307
2308 @staticmethod
2309 def sr_inspector( layout, data ):
2310 #{
2311 data = data[0]
2312 layout.prop( data, 'subtype' )
2313 layout.prop( data, 'target' )
2314 #}
2315 #}
2316
2317 class SR_OBJECT_ENT_AUDIO(bpy.types.PropertyGroup):
2318 #{
2319 files: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_AUDIO_FILE_ENTRY)
2320 files_index: bpy.props.IntProperty()
2321
2322 flag_3d: bpy.props.BoolProperty( name="3D audio",default=True )
2323 flag_loop: bpy.props.BoolProperty( name="Loop",default=False )
2324 flag_auto: bpy.props.BoolProperty( name="Play at start",default=False )
2325 flag_nodoppler: bpy.props.BoolProperty( name="No Doppler",default=False )
2326
2327 group: bpy.props.IntProperty( name="Group ID", default=0 )
2328 formato: bpy.props.EnumProperty(
2329 name="Format",
2330 items=[('0','Uncompressed Mono',''),
2331 ('1','Compressed Vorbis',''),
2332 ('2','[vg] Bird Synthesis','')]
2333 )
2334 probability_curve: bpy.props.EnumProperty(
2335 name="Probability Curve",
2336 items=[('0','Constant',''),
2337 ('1','Wildlife Daytime',''),
2338 ('2','Wildlife Nighttime','')])
2339 channel_behaviour: bpy.props.EnumProperty(
2340 name="Channel Behaviour",
2341 items=[('0','Unlimited',''),
2342 ('1','Discard if group full', ''),
2343 ('2','Crossfade if group full','')])
2344
2345 transition_duration: bpy.props.FloatProperty(name="Transition Time",\
2346 default=0.2)
2347
2348 max_channels: bpy.props.IntProperty( name="Max Channels", default=1 )
2349 volume: bpy.props.FloatProperty( name="Volume",default=1.0 )
2350
2351 @staticmethod
2352 def sr_inspector( layout, data ):
2353 #{
2354 layout.prop( data[0], 'formato' )
2355 layout.prop( data[0], 'volume' )
2356
2357 box = layout.box()
2358 box.label( text='Channels' )
2359 split = box.split(factor=0.3)
2360 c = split.column()
2361 c.prop( data[0], 'max_channels' )
2362 c = split.column()
2363 c.prop( data[0], 'channel_behaviour', text='Behaviour' )
2364 if data[0].channel_behaviour >= '1':
2365 box.prop( data[0], 'group' )
2366 if data[0].channel_behaviour == '2':
2367 box.prop( data[0], 'transition_duration' )
2368
2369 box = layout.box()
2370 box.label( text='Flags' )
2371 box.prop( data[0], 'flag_3d' )
2372 if data[0].flag_3d: box.prop( data[0], 'flag_nodoppler' )
2373
2374 box.prop( data[0], 'flag_loop' )
2375 box.prop( data[0], 'flag_auto' )
2376
2377 split = layout.split(factor=0.7)
2378 c = split.column()
2379 c.label( text='Filepath' )
2380 c = split.column()
2381 c.label( text='Chance (0.1s)' )
2382
2383 layout.prop( data[0], 'probability_curve' )
2384
2385 layout.template_list('SR_UL_AUDIO_LIST', 'Files', \
2386 data[0], 'files', data[0], 'file_index', rows=5)
2387
2388 row = layout.row()
2389 row.operator( 'skaterift.al_new_entry', text='Add' )
2390 row.operator( 'skaterift.al_del_entry', text='Remove' )
2391 #}
2392 #}
2393
2394 class SR_OBJECT_ENT_MARKER(bpy.types.PropertyGroup):
2395 #{
2396 alias: bpy.props.StringProperty()
2397 #}
2398
2399 class SR_OBJECT_ENT_GLYPH(bpy.types.PropertyGroup):
2400 #{
2401 mini: bpy.props.FloatVectorProperty(size=2)
2402 maxi: bpy.props.FloatVectorProperty(size=2)
2403 utf32: bpy.props.IntProperty()
2404 #}
2405
2406 class SR_OBJECT_ENT_GLYPH_ENTRY(bpy.types.PropertyGroup):
2407 #{
2408 bounds: bpy.props.FloatVectorProperty(size=4,subtype='NONE')
2409 utf32: bpy.props.IntProperty()
2410 #}
2411
2412 class SR_OBJECT_ENT_FONT_VARIANT(bpy.types.PropertyGroup):
2413 #{
2414 mesh: bpy.props.PointerProperty(type=bpy.types.Object)
2415 tipo: bpy.props.StringProperty()
2416 #}
2417
2418 class SR_OBJECT_ENT_FONT(bpy.types.PropertyGroup):
2419 #{
2420 variants: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_FONT_VARIANT)
2421 glyphs: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_GLYPH_ENTRY)
2422 alias: bpy.props.StringProperty()
2423
2424 glyphs_index: bpy.props.IntProperty()
2425 variants_index: bpy.props.IntProperty()
2426
2427 @staticmethod
2428 def sr_inspector( layout, data ):
2429 #{
2430 layout.prop( data[0], 'alias' )
2431
2432 layout.label( text='Variants' )
2433 layout.template_list('SR_UL_FONT_VARIANT_LIST', 'Variants', \
2434 data[0], 'variants', data[0], 'variants_index',\
2435 rows=5 )
2436 row = layout.row()
2437 row.operator( 'skaterift.fv_new_entry', text='Add' )
2438 row.operator( 'skaterift.fv_del_entry', text='Remove' )
2439
2440 layout.label( text='ASCII Glyphs' )
2441 layout.template_list('SR_UL_FONT_GLYPH_LIST', 'Glyphs', \
2442 data[0], 'glyphs', data[0], 'glyphs_index', rows=5)
2443
2444 row = layout.row()
2445 row.operator( 'skaterift.gl_new_entry', text='Add' )
2446 row.operator( 'skaterift.gl_del_entry', text='Remove' )
2447 row.operator( 'skaterift.gl_move_item', text='^' ).direction='UP'
2448 row.operator( 'skaterift.gl_move_item', text='v' ).direction='DOWN'
2449 #}
2450 #}
2451
2452 class SR_OBJECT_PROPERTIES(bpy.types.PropertyGroup):
2453 #{
2454 ent_gate: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_GATE)
2455 ent_spawn: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_SPAWN)
2456 ent_route: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_ROUTE)
2457 ent_volume: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_VOLUME)
2458 ent_audio: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_AUDIO)
2459 ent_marker: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_MARKER)
2460 ent_glyph: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_GLYPH)
2461 ent_font: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_FONT)
2462 ent_type: bpy.props.EnumProperty(
2463 name="Type",
2464 items=[('none', 'None', '', 0),
2465 ('ent_gate','Gate','', 1),
2466 ('ent_spawn','Spawn','', 2),
2467 ('ent_route_node', 'Route Node', '', 3 ),
2468 ('ent_route', 'Route', '', 4),
2469 ('ent_water', 'Water Surface', '', 5),
2470 ('ent_volume', 'Volume', '', 6 ),
2471 ('ent_audio', 'Audio Files', '', 7),
2472 ('ent_marker', 'Marker', '', 8),
2473 ('ent_font', 'Font', '', 9),
2474 ('ent_font_variant','Font variant','',10)],
2475 update=sr_on_type_change
2476 )
2477 #}
2478
2479 class SR_MESH_PROPERTIES(bpy.types.PropertyGroup):
2480 #{
2481 ent_gate: bpy.props.CollectionProperty(type=SR_MESH_ENT_GATE)
2482 #}
2483
2484 class SR_LIGHT_PROPERTIES(bpy.types.PropertyGroup):
2485 #{
2486 daytime: bpy.props.BoolProperty( name='Daytime' )
2487 #}
2488
2489 class SR_BONE_PROPERTIES(bpy.types.PropertyGroup):
2490 #{
2491 collider: bpy.props.EnumProperty( name='Collider Type',
2492 items=[('0','none',''),
2493 ('1','box',''),
2494 ('2','capsule','')])
2495
2496 collider_min: bpy.props.FloatVectorProperty( name='Collider Min', size=3 )
2497 collider_max: bpy.props.FloatVectorProperty( name='Collider Max', size=3 )
2498
2499 cone_constraint: bpy.props.BoolProperty( name='Cone constraint' )
2500
2501 conevx: bpy.props.FloatVectorProperty( name='vx' )
2502 conevy: bpy.props.FloatVectorProperty( name='vy' )
2503 coneva: bpy.props.FloatVectorProperty( name='va' )
2504 conet: bpy.props.FloatProperty( name='t' )
2505
2506 @staticmethod
2507 def sr_inspector( layout, data ):
2508 #{
2509 data = data[0]
2510 box = layout.box()
2511 box.prop( data, 'collider' )
2512
2513 if int(data.collider)>0:#{
2514 row = box.row()
2515 row.prop( data, 'collider_min' )
2516 row = box.row()
2517 row.prop( data, 'collider_max' )
2518 #}
2519
2520 box = layout.box()
2521 box.prop( data, 'cone_constraint' )
2522 if data.cone_constraint:#{
2523 row = box.row()
2524 row.prop( data, 'conevx' )
2525 row = box.row()
2526 row.prop( data, 'conevy' )
2527 row = box.row()
2528 row.prop( data, 'coneva' )
2529 box.prop( data, 'conet' )
2530 #}
2531 #}
2532 #}
2533
2534 class SR_MATERIAL_PROPERTIES(bpy.types.PropertyGroup):
2535 #{
2536 shader: bpy.props.EnumProperty(
2537 name="Format",
2538 items = [
2539 ('standard',"standard",''),
2540 ('standard_cutout', "standard_cutout", ''),
2541 ('terrain_blend', "terrain_blend", ''),
2542 ('vertex_blend', "vertex_blend", ''),
2543 ('water',"water",'')
2544 ])
2545
2546 surface_prop: bpy.props.EnumProperty(
2547 name="Surface Property",
2548 items = [
2549 ('0','concrete',''),
2550 ('1','wood',''),
2551 ('2','grass',''),
2552 ('3','tiles',''),
2553 ('4','metal','')
2554 ])
2555
2556 collision: bpy.props.BoolProperty( \
2557 name="Collisions Enabled",\
2558 default=True,\
2559 description = "Can the player collide with this material"\
2560 )
2561 skate_surface: bpy.props.BoolProperty( \
2562 name="Skate Surface", \
2563 default=True,\
2564 description = "Should the game try to target this surface?" \
2565 )
2566 grind_surface: bpy.props.BoolProperty( \
2567 name="Grind Surface", \
2568 default=False,\
2569 description = "Grind face?" \
2570 )
2571 grow_grass: bpy.props.BoolProperty( \
2572 name="Grow Grass", \
2573 default=False,\
2574 description = "Spawn grass sprites on this surface?" \
2575 )
2576 blend_offset: bpy.props.FloatVectorProperty( \
2577 name="Blend Offset", \
2578 size=2, \
2579 default=Vector((0.5,0.0)),\
2580 description="When surface is more than 45 degrees, add this vector " +\
2581 "to the UVs" \
2582 )
2583 sand_colour: bpy.props.FloatVectorProperty( \
2584 name="Sand Colour",\
2585 subtype='COLOR',\
2586 min=0.0,max=1.0,\
2587 default=Vector((0.79,0.63,0.48)),\
2588 description="Blend to this colour near the 0 coordinate on UP axis"\
2589 )
2590 shore_colour: bpy.props.FloatVectorProperty( \
2591 name="Shore Colour",\
2592 subtype='COLOR',\
2593 min=0.0,max=1.0,\
2594 default=Vector((0.03,0.32,0.61)),\
2595 description="Water colour at the shoreline"\
2596 )
2597 ocean_colour: bpy.props.FloatVectorProperty( \
2598 name="Ocean Colour",\
2599 subtype='COLOR',\
2600 min=0.0,max=1.0,\
2601 default=Vector((0.0,0.006,0.03)),\
2602 description="Water colour in the deep bits"\
2603 )
2604 #}
2605
2606 # ---------------------------------------------------------------------------- #
2607 # #
2608 # GUI section #
2609 # #
2610 # ---------------------------------------------------------------------------- #
2611
2612 cv_view_draw_handler = None
2613 cv_view_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR')
2614 cv_view_verts = []
2615 cv_view_colours = []
2616 cv_view_course_i = 0
2617
2618 # Draw axis alligned sphere at position with radius
2619 #
2620 def cv_draw_sphere( pos, radius, colour ):
2621 #{
2622 global cv_view_verts, cv_view_colours
2623
2624 ly = pos + Vector((0,0,radius))
2625 lx = pos + Vector((0,radius,0))
2626 lz = pos + Vector((0,0,radius))
2627
2628 pi = 3.14159265358979323846264
2629
2630 for i in range(16):#{
2631 t = ((i+1.0) * 1.0/16.0) * pi * 2.0
2632 s = math.sin(t)
2633 c = math.cos(t)
2634
2635 py = pos + Vector((s*radius,0.0,c*radius))
2636 px = pos + Vector((s*radius,c*radius,0.0))
2637 pz = pos + Vector((0.0,s*radius,c*radius))
2638
2639 cv_view_verts += [ px, lx ]
2640 cv_view_verts += [ py, ly ]
2641 cv_view_verts += [ pz, lz ]
2642
2643 cv_view_colours += [ colour, colour, colour, colour, colour, colour ]
2644
2645 ly = py
2646 lx = px
2647 lz = pz
2648 #}
2649 cv_draw_lines()
2650 #}
2651
2652 # Draw axis alligned sphere at position with radius
2653 #
2654 def cv_draw_halfsphere( pos, tx, ty, tz, radius, colour ):
2655 #{
2656 global cv_view_verts, cv_view_colours
2657
2658 ly = pos + tz*radius
2659 lx = pos + ty*radius
2660 lz = pos + tz*radius
2661
2662 pi = 3.14159265358979323846264
2663
2664 for i in range(16):#{
2665 t = ((i+1.0) * 1.0/16.0) * pi
2666 s = math.sin(t)
2667 c = math.cos(t)
2668
2669 s1 = math.sin(t*2.0)
2670 c1 = math.cos(t*2.0)
2671
2672 py = pos + s*tx*radius + c *tz*radius
2673 px = pos + s*tx*radius + c *ty*radius
2674 pz = pos + s1*ty*radius + c1*tz*radius
2675
2676 cv_view_verts += [ px, lx ]
2677 cv_view_verts += [ py, ly ]
2678 cv_view_verts += [ pz, lz ]
2679
2680 cv_view_colours += [ colour, colour, colour, colour, colour, colour ]
2681
2682 ly = py
2683 lx = px
2684 lz = pz
2685 #}
2686 cv_draw_lines()
2687 #}
2688
2689 # Draw transformed -1 -> 1 cube
2690 #
2691 def cv_draw_ucube( transform, colour, s=Vector((1,1,1)), o=Vector((0,0,0)) ):
2692 #{
2693 global cv_view_verts, cv_view_colours
2694
2695 a = o + -1.0 * s
2696 b = o + 1.0 * s
2697
2698 vs = [None]*8
2699 vs[0] = transform @ Vector((a[0], a[1], a[2]))
2700 vs[1] = transform @ Vector((a[0], b[1], a[2]))
2701 vs[2] = transform @ Vector((b[0], b[1], a[2]))
2702 vs[3] = transform @ Vector((b[0], a[1], a[2]))
2703 vs[4] = transform @ Vector((a[0], a[1], b[2]))
2704 vs[5] = transform @ Vector((a[0], b[1], b[2]))
2705 vs[6] = transform @ Vector((b[0], b[1], b[2]))
2706 vs[7] = transform @ Vector((b[0], a[1], b[2]))
2707
2708 indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
2709 (0,4),(1,5),(2,6),(3,7)]
2710
2711 for l in indices:#{
2712 v0 = vs[l[0]]
2713 v1 = vs[l[1]]
2714 cv_view_verts += [(v0[0],v0[1],v0[2])]
2715 cv_view_verts += [(v1[0],v1[1],v1[2])]
2716 cv_view_colours += [colour, colour]
2717 #}
2718 cv_draw_lines()
2719 #}
2720
2721 # Draw line with colour
2722 #
2723 def cv_draw_line( p0, p1, colour ):
2724 #{
2725 global cv_view_verts, cv_view_colours
2726
2727 cv_view_verts += [p0,p1]
2728 cv_view_colours += [colour, colour]
2729 cv_draw_lines()
2730 #}
2731
2732 # Draw line with colour(s)
2733 #
2734 def cv_draw_line2( p0, p1, c0, c1 ):
2735 #{
2736 global cv_view_verts, cv_view_colours
2737
2738 cv_view_verts += [p0,p1]
2739 cv_view_colours += [c0,c1]
2740 cv_draw_lines()
2741 #}
2742
2743 #
2744 #
2745 def cv_tangent_basis( n, tx, ty ):
2746 #{
2747 if abs( n[0] ) >= 0.57735027:#{
2748 tx[0] = n[1]
2749 tx[1] = -n[0]
2750 tx[2] = 0.0
2751 #}
2752 else:#{
2753 tx[0] = 0.0
2754 tx[1] = n[2]
2755 tx[2] = -n[1]
2756 #}
2757
2758 tx.normalize()
2759 _ty = n.cross( tx )
2760
2761 ty[0] = _ty[0]
2762 ty[1] = _ty[1]
2763 ty[2] = _ty[2]
2764 #}
2765
2766 # Draw coloured arrow
2767 #
2768 def cv_draw_arrow( p0, p1, c0, size=0.15 ):
2769 #{
2770 global cv_view_verts, cv_view_colours
2771
2772 n = p1-p0
2773 midpt = p0 + n*0.5
2774 n.normalize()
2775
2776 tx = Vector((1,0,0))
2777 ty = Vector((1,0,0))
2778 cv_tangent_basis( n, tx, ty )
2779
2780 cv_view_verts += [p0,p1, midpt+(tx-n)*size,midpt, midpt+(-tx-n)*size,midpt ]
2781 cv_view_colours += [c0,c0,c0,c0,c0,c0]
2782 #cv_draw_lines()
2783 #}
2784
2785 def cv_draw_line_dotted( p0, p1, c0, dots=10 ):
2786 #{
2787 global cv_view_verts, cv_view_colours
2788
2789 for i in range(dots):#{
2790 t0 = i/dots
2791 t1 = (i+0.25)/dots
2792
2793 p2 = p0*(1.0-t0)+p1*t0
2794 p3 = p0*(1.0-t1)+p1*t1
2795
2796 cv_view_verts += [p2,p3]
2797 cv_view_colours += [c0,c0]
2798 #}
2799 #cv_draw_lines()
2800 #}
2801
2802 # Drawhandles of a bezier control point
2803 #
2804 def cv_draw_bhandle( obj, direction, colour ):
2805 #{
2806 global cv_view_verts, cv_view_colours
2807
2808 p0 = obj.location
2809 h0 = obj.matrix_world @ Vector((0,direction,0))
2810
2811 cv_view_verts += [p0]
2812 cv_view_verts += [h0]
2813 cv_view_colours += [colour,colour]
2814 cv_draw_lines()
2815 #}
2816
2817 # Draw a bezier curve (at fixed resolution 10)
2818 #
2819 def cv_draw_bezier( p0,h0,p1,h1,c0,c1 ):
2820 #{
2821 global cv_view_verts, cv_view_colours
2822
2823 last = p0
2824 for i in range(10):#{
2825 t = (i+1)/10
2826 a0 = 1-t
2827
2828 tt = t*t
2829 ttt = tt*t
2830 p=ttt*p1+(3*tt-3*ttt)*h1+(3*ttt-6*tt+3*t)*h0+(3*tt-ttt-3*t+1)*p0
2831
2832 cv_view_verts += [(last[0],last[1],last[2])]
2833 cv_view_verts += [(p[0],p[1],p[2])]
2834 cv_view_colours += [c0*a0+c1*(1-a0),c0*a0+c1*(1-a0)]
2835
2836 last = p
2837 #}
2838 cv_draw_lines()
2839 #}
2840
2841 # I think this one extends the handles of the bezier otwards......
2842 #
2843 def cv_draw_sbpath( o0,o1,c0,c1,s0,s1 ):
2844 #{
2845 global cv_view_course_i
2846
2847 offs = ((cv_view_course_i % 2)*2-1) * cv_view_course_i * 0.02
2848
2849 p0 = o0.matrix_world @ Vector((offs, 0,0))
2850 h0 = o0.matrix_world @ Vector((offs, s0,0))
2851 p1 = o1.matrix_world @ Vector((offs, 0,0))
2852 h1 = o1.matrix_world @ Vector((offs,-s1,0))
2853
2854 cv_draw_bezier( p0,h0,p1,h1,c0,c1 )
2855 cv_draw_lines()
2856 #}
2857
2858 # Flush the lines buffers. This is called often because god help you if you want
2859 # to do fixed, fast buffers in this catastrophic programming language.
2860 #
2861 def cv_draw_lines():
2862 #{
2863 global cv_view_shader, cv_view_verts, cv_view_colours
2864
2865 if len(cv_view_verts) < 2:
2866 return
2867
2868 lines = batch_for_shader(\
2869 cv_view_shader, 'LINES', \
2870 { "pos":cv_view_verts, "color":cv_view_colours })
2871
2872 lines.draw( cv_view_shader )
2873
2874 cv_view_verts = []
2875 cv_view_colours = []
2876 #}
2877
2878 # I dont remember what this does exactly
2879 #
2880 def cv_draw_bpath( o0,o1,c0,c1 ):
2881 #{
2882 cv_draw_sbpath( o0,o1,c0,c1,1.0,1.0 )
2883 #}
2884
2885 # Semi circle to show the limit. and some lines
2886 #
2887 def draw_limit( obj, center, major, minor, amin, amax, colour ):
2888 #{
2889 global cv_view_verts, cv_view_colours
2890 f = 0.05
2891 ay = major*f
2892 ax = minor*f
2893
2894 for x in range(16):#{
2895 t0 = x/16
2896 t1 = (x+1)/16
2897 a0 = amin*(1.0-t0)+amax*t0
2898 a1 = amin*(1.0-t1)+amax*t1
2899
2900 p0 = center + major*f*math.cos(a0) + minor*f*math.sin(a0)
2901 p1 = center + major*f*math.cos(a1) + minor*f*math.sin(a1)
2902
2903 p0=obj.matrix_world @ p0
2904 p1=obj.matrix_world @ p1
2905 cv_view_verts += [p0,p1]
2906 cv_view_colours += [colour,colour]
2907
2908 if x == 0:#{
2909 cv_view_verts += [p0,center]
2910 cv_view_colours += [colour,colour]
2911 #}
2912 if x == 15:#{
2913 cv_view_verts += [p1,center]
2914 cv_view_colours += [colour,colour]
2915 #}
2916 #}
2917
2918 cv_view_verts += [center+major*1.2*f,center+major*f*0.8]
2919 cv_view_colours += [colour,colour]
2920
2921 cv_draw_lines()
2922 #}
2923
2924 # Cone and twist limit
2925 #
2926 def draw_cone_twist( center, vx, vy, va ):
2927 #{
2928 global cv_view_verts, cv_view_colours
2929 axis = vy.cross( vx )
2930 axis.normalize()
2931
2932 size = 0.12
2933
2934 cv_view_verts += [center, center+va*size]
2935 cv_view_colours += [ (1,1,1), (1,1,1) ]
2936
2937 for x in range(32):#{
2938 t0 = (x/32) * math.tau
2939 t1 = ((x+1)/32) * math.tau
2940
2941 c0 = math.cos(t0)
2942 s0 = math.sin(t0)
2943 c1 = math.cos(t1)
2944 s1 = math.sin(t1)
2945
2946 p0 = center + (axis + vx*c0 + vy*s0).normalized() * size
2947 p1 = center + (axis + vx*c1 + vy*s1).normalized() * size
2948
2949 col0 = ( abs(c0), abs(s0), 0.0, 1.0 )
2950 col1 = ( abs(c1), abs(s1), 0.0, 1.0 )
2951
2952 cv_view_verts += [center, p0, p0, p1]
2953 cv_view_colours += [ (0,0,0), col0, col0, col1 ]
2954 #}
2955
2956 cv_draw_lines()
2957 #}
2958
2959 # Draws constraints and stuff for the skeleton. This isnt documented and wont be
2960 #
2961 def draw_skeleton_helpers( obj ):
2962 #{
2963 global cv_view_verts, cv_view_colours
2964
2965 if obj.data.pose_position != 'REST':#{
2966 return
2967 #}
2968
2969 for bone in obj.data.bones:#{
2970 c = bone.head_local
2971 a = Vector((bone.SR_data.collider_min[0],
2972 bone.SR_data.collider_min[1],
2973 bone.SR_data.collider_min[2]))
2974 b = Vector((bone.SR_data.collider_max[0],
2975 bone.SR_data.collider_max[1],
2976 bone.SR_data.collider_max[2]))
2977
2978 if bone.SR_data.collider == '1':#{
2979 vs = [None]*8
2980 vs[0]=obj.matrix_world@Vector((c[0]+a[0],c[1]+a[1],c[2]+a[2]))
2981 vs[1]=obj.matrix_world@Vector((c[0]+a[0],c[1]+b[1],c[2]+a[2]))
2982 vs[2]=obj.matrix_world@Vector((c[0]+b[0],c[1]+b[1],c[2]+a[2]))
2983 vs[3]=obj.matrix_world@Vector((c[0]+b[0],c[1]+a[1],c[2]+a[2]))
2984 vs[4]=obj.matrix_world@Vector((c[0]+a[0],c[1]+a[1],c[2]+b[2]))
2985 vs[5]=obj.matrix_world@Vector((c[0]+a[0],c[1]+b[1],c[2]+b[2]))
2986 vs[6]=obj.matrix_world@Vector((c[0]+b[0],c[1]+b[1],c[2]+b[2]))
2987 vs[7]=obj.matrix_world@Vector((c[0]+b[0],c[1]+a[1],c[2]+b[2]))
2988
2989 indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
2990 (0,4),(1,5),(2,6),(3,7)]
2991
2992 for l in indices:#{
2993 v0 = vs[l[0]]
2994 v1 = vs[l[1]]
2995
2996 cv_view_verts += [(v0[0],v0[1],v0[2])]
2997 cv_view_verts += [(v1[0],v1[1],v1[2])]
2998 cv_view_colours += [(0.5,0.5,0.5),(0.5,0.5,0.5)]
2999 #}
3000 #}
3001 elif bone.SR_data.collider == '2':#{
3002 v0 = b-a
3003 major_axis = 0
3004 largest = -1.0
3005
3006 for i in range(3):#{
3007 if abs(v0[i]) > largest:#{
3008 largest = abs(v0[i])
3009 major_axis = i
3010 #}
3011 #}
3012
3013 v1 = Vector((0,0,0))
3014 v1[major_axis] = 1.0
3015
3016 tx = Vector((0,0,0))
3017 ty = Vector((0,0,0))
3018
3019 cv_tangent_basis( v1, tx, ty )
3020 r = (abs(tx.dot( v0 )) + abs(ty.dot( v0 ))) * 0.25
3021 l = v0[ major_axis ] - r*2
3022
3023 p0 = obj.matrix_world@Vector( c + (a+b)*0.5 + v1*l*-0.5 )
3024 p1 = obj.matrix_world@Vector( c + (a+b)*0.5 + v1*l* 0.5 )
3025
3026 colour = [0.2,0.2,0.2]
3027 colour[major_axis] = 0.5
3028
3029 cv_draw_halfsphere( p0, -v1, ty, tx, r, colour )
3030 cv_draw_halfsphere( p1, v1, ty, tx, r, colour )
3031 cv_draw_line( p0+tx* r, p1+tx* r, colour )
3032 cv_draw_line( p0+tx*-r, p1+tx*-r, colour )
3033 cv_draw_line( p0+ty* r, p1+ty* r, colour )
3034 cv_draw_line( p0+ty*-r, p1+ty*-r, colour )
3035 #}
3036 else:#{
3037 continue
3038 #}
3039
3040 center = obj.matrix_world @ c
3041 if bone.SR_data.cone_constraint:#{
3042 vx = Vector([bone.SR_data.conevx[_] for _ in range(3)])
3043 vy = Vector([bone.SR_data.conevy[_] for _ in range(3)])
3044 va = Vector([bone.SR_data.coneva[_] for _ in range(3)])
3045 draw_cone_twist( center, vx, vy, va )
3046 #}
3047 #}
3048 #}
3049
3050 def cv_ent_gate( obj ):
3051 #{
3052 global cv_view_verts, cv_view_colours
3053
3054 if obj.type != 'MESH': return
3055
3056 mesh_data = obj.data.SR_data.ent_gate[0]
3057 data = obj.SR_data.ent_gate[0]
3058 dims = mesh_data.dimensions
3059
3060 vs = [None]*9
3061 c = Vector((0,0,dims[2]))
3062
3063 vs[0] = obj.matrix_world @ Vector((-dims[0],0.0,-dims[1]+dims[2]))
3064 vs[1] = obj.matrix_world @ Vector((-dims[0],0.0, dims[1]+dims[2]))
3065 vs[2] = obj.matrix_world @ Vector(( dims[0],0.0, dims[1]+dims[2]))
3066 vs[3] = obj.matrix_world @ Vector(( dims[0],0.0,-dims[1]+dims[2]))
3067 vs[4] = obj.matrix_world @ (c+Vector((-1,0,-2)))
3068 vs[5] = obj.matrix_world @ (c+Vector((-1,0, 2)))
3069 vs[6] = obj.matrix_world @ (c+Vector(( 1,0, 2)))
3070 vs[7] = obj.matrix_world @ (c+Vector((-1,0, 0)))
3071 vs[8] = obj.matrix_world @ (c+Vector(( 1,0, 0)))
3072
3073 indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(7,8)]
3074
3075 r3d = bpy.context.area.spaces.active.region_3d
3076
3077 p0 = r3d.view_matrix.inverted().translation
3078 v0 = (obj.matrix_world@Vector((0,0,0))) - p0
3079 v1 = obj.matrix_world.to_3x3() @ Vector((0,1,0))
3080
3081 if v0.dot(v1) > 0.0: cc = (0,1,0)
3082 else: cc = (1,0,0)
3083
3084 for l in indices:#{
3085 v0 = vs[l[0]]
3086 v1 = vs[l[1]]
3087 cv_view_verts += [(v0[0],v0[1],v0[2])]
3088 cv_view_verts += [(v1[0],v1[1],v1[2])]
3089 cv_view_colours += [cc,cc]
3090 #}
3091
3092 sw = (0.4,0.4,0.4)
3093 if data.target != None:
3094 cv_draw_arrow( obj.location, data.target.location, sw )
3095 #}
3096
3097 def cv_ent_volume( obj ):
3098 #{
3099 global cv_view_verts, cv_view_colours
3100
3101 data = obj.SR_data.ent_volume[0]
3102
3103 if data.subtype == '0':#{
3104 cv_draw_ucube( obj.matrix_world, (0,1,0) )
3105
3106 if data.target:#{
3107 cv_draw_line( obj.location, data.target.location, (0,1,0) )
3108 #}
3109 #}
3110 elif data.subtype == '1':#{
3111 cv_draw_ucube( obj.matrix_world, (1,1,0) )
3112
3113 if data.target:#{
3114 cv_draw_line( obj.location, data.target.location, (1,1,0) )
3115 #}
3116 #}
3117 #}
3118
3119 def dijkstra( graph, start_node, target_node ):
3120 #{
3121 unvisited = [_ for _ in graph]
3122 shortest_path = {}
3123 previous_nodes = {}
3124
3125 for n in unvisited:
3126 shortest_path[n] = 9999999.999999
3127 shortest_path[start_node] = 0
3128
3129 while unvisited:#{
3130 current_min_node = None
3131 for n in unvisited:#{
3132 if current_min_node == None:
3133 current_min_node = n
3134 elif shortest_path[n] < shortest_path[current_min_node]:
3135 current_min_node = n
3136 #}
3137
3138 for branch in graph[current_min_node]:#{
3139 tentative_value = shortest_path[current_min_node]
3140 tentative_value += graph[current_min_node][branch]
3141 if tentative_value < shortest_path[branch]:#{
3142 shortest_path[branch] = tentative_value
3143 previous_nodes[branch] = current_min_node
3144 #}
3145 #}
3146
3147 unvisited.remove(current_min_node)
3148 #}
3149
3150 path = []
3151 node = target_node
3152 while node != start_node:#{
3153 path.append(node)
3154
3155 if node not in previous_nodes: return None
3156 node = previous_nodes[node]
3157 #}
3158
3159 # Add the start node manually
3160 path.append(start_node)
3161 return path
3162 #}
3163
3164 class dij_graph():
3165 #{
3166 def __init__(_,points,graph,subsections):#{
3167 _.points = points
3168 _.graph = graph
3169 _.subsections = subsections
3170 #}
3171 #}
3172
3173 def create_node_graph( curves, gates ):
3174 #{
3175 # add endpoints of curves
3176 graph = {}
3177 route_points = []
3178 subsections = []
3179 point_count = 0
3180 spline_count = 0
3181
3182 for c in range(len(curves)):#{
3183 for s in range(len(curves[c].data.splines)):#{
3184 spline = curves[c].data.splines[s]
3185 l = len(spline.points)
3186 if l < 2: continue
3187
3188 dist = round(spline.calc_length(),2)
3189
3190 ia = point_count
3191 ib = point_count+l-1
3192
3193 graph[ia] = { ib: dist }
3194 graph[ib] = { ia: dist }
3195
3196 for i in range(len(spline.points)):#{
3197 wco = curves[c].matrix_world @ spline.points[i].co
3198 route_points.append(Vector((wco[0],wco[1],wco[2]+0.5)))
3199
3200 previous = ia+i-1
3201 proxima = ia+i+1
3202
3203 if i == 0: previous = -1
3204 if i == len(spline.points)-1: proxima = -1
3205
3206 subsections.append((spline_count,previous,proxima))
3207 point_count += 1
3208 #}
3209
3210 spline_count += 1
3211 #}
3212 #}
3213
3214 # link endpoints
3215 graph_keys = list(graph)
3216 for i in range(len(graph_keys)-1):#{
3217 for j in range(i+1, len(graph_keys)):#{
3218 if i%2==0 and i+1==j: continue
3219
3220 ni = graph_keys[i]
3221 nj = graph_keys[j]
3222 pi = route_points[ni]
3223 pj = route_points[nj]
3224
3225 dist = round((pj-pi).magnitude,2)
3226
3227 if dist < 10.0:#{
3228 graph[ni][nj] = dist
3229 graph[nj][ni] = dist
3230 #}
3231 #}
3232 #}
3233
3234 # add and link gates( by name )
3235 for gate in gates:#{
3236 v1 = gate.matrix_world.to_3x3() @ Vector((0,1,0))
3237 if gate.SR_data.ent_gate[0].target:
3238 v1 = v1 * -1.0
3239
3240 graph[ gate.name ] = {}
3241
3242 for i in range(len(graph_keys)):#{
3243 ni = graph_keys[i]
3244 pi = route_points[ni]
3245
3246 v0 = pi-gate.location
3247 if v0.dot(v1) < 0.0: continue
3248
3249 dist = round(v0.magnitude,2)
3250
3251 if dist < 10.0:#{
3252 graph[ gate.name ][ ni ] = dist
3253 graph[ ni ][ gate.name ] = dist
3254 #}
3255 #}
3256 #}
3257
3258 return dij_graph(route_points,graph,subsections)
3259 #}
3260
3261 def solve_graph( dij, start, end ):
3262 #{
3263 path = dijkstra( dij.graph, end, start )
3264 full = []
3265
3266 if path:#{
3267 for sj in range(1,len(path)-2):#{
3268 i0 = path[sj]
3269 i1 = path[sj+1]
3270 map0 = dij.subsections[i0]
3271 map1 = dij.subsections[i1]
3272
3273 if map0[0] == map1[0]:#{
3274 if map0[1] == -1: direction = 2
3275 else: direction = 1
3276 sent = 0
3277
3278 while True:#{
3279 map0 = dij.subsections[i0]
3280 i1 = map0[direction]
3281 if i1 == -1: break
3282
3283 full.append( i0 )
3284 sent += 1
3285 i0 = i1
3286 if sent > 50: break
3287 #}
3288 #}
3289 else:#{
3290 full.append( i0 )
3291 #}
3292 #}
3293
3294 full.append( path[-2] )
3295 #}
3296 return full
3297 #}
3298
3299 def cv_draw_route( route, dij ):
3300 #{
3301 pole = Vector((0.2,0.2,10))
3302 hat = Vector((1,8,0.2))
3303 cc = (route.SR_data.ent_route[0].colour[0],
3304 route.SR_data.ent_route[0].colour[1],
3305 route.SR_data.ent_route[0].colour[2])
3306
3307 cv_draw_ucube(route.matrix_world,cc,Vector((0.5,-7.5,6)),\
3308 Vector((0,-6.5,5.5)))
3309 cv_draw_ucube(route.matrix_world,cc,pole, Vector(( 0.5, 0.5,0)) )
3310 cv_draw_ucube(route.matrix_world,cc,pole, Vector(( 0.5,-13.5,0)) )
3311 cv_draw_ucube(route.matrix_world,cc,hat, Vector((-0.5,-6.5, 12)) )
3312 cv_draw_ucube(route.matrix_world,cc,hat, Vector((-0.5,-6.5,-1)) )
3313
3314 checkpoints = route.SR_data.ent_route[0].gates
3315
3316 for i in range(len(checkpoints)):#{
3317 gi = checkpoints[i].target
3318 gj = checkpoints[(i+1)%len(checkpoints)].target
3319
3320 if gi:#{
3321 dest = gi.SR_data.ent_gate[0].target
3322 if dest:
3323 cv_draw_line_dotted( gi.location, dest.location, cc )
3324 gi = dest
3325 #}
3326
3327 if gi==gj: continue # error?
3328 if not gi or not gj: continue
3329
3330 path = solve_graph( dij, gi.name, gj.name )
3331
3332 if path:#{
3333 cv_draw_arrow(gi.location,dij.points[path[0]],cc,1.5)
3334 cv_draw_arrow(dij.points[path[len(path)-1]],gj.location,cc,1.5)
3335 for j in range(len(path)-1):#{
3336 i0 = path[j]
3337 i1 = path[j+1]
3338 o0 = dij.points[ i0 ]
3339 o1 = dij.points[ i1 ]
3340 cv_draw_arrow(o0,o1,cc,1.5)
3341 #}
3342 #}
3343 else:#{
3344 cv_draw_line_dotted( gi.location, gj.location, cc )
3345 #}
3346 #}
3347 #}
3348
3349 def cv_draw():
3350 #{
3351 global cv_view_shader
3352 global cv_view_verts
3353 global cv_view_colours
3354 global cv_view_course_i
3355
3356 cv_view_course_i = 0
3357 cv_view_verts = []
3358 cv_view_colours = []
3359
3360 cv_view_shader.bind()
3361 gpu.state.depth_mask_set(False)
3362 gpu.state.line_width_set(2.0)
3363 gpu.state.face_culling_set('BACK')
3364 gpu.state.depth_test_set('LESS')
3365 gpu.state.blend_set('NONE')
3366
3367 route_gates = []
3368 route_curves = []
3369 routes = []
3370
3371 for obj in bpy.context.collection.objects:#{
3372 if obj.type == 'ARMATURE':#{
3373 if obj.data.pose_position == 'REST':
3374 draw_skeleton_helpers( obj )
3375 #}
3376 else:#{
3377 ent_type = obj_ent_type( obj )
3378
3379 if ent_type == 'ent_gate':#{
3380 cv_ent_gate( obj )
3381 route_gates += [obj]
3382 #}
3383 elif ent_type == 'ent_route_node':#{
3384 if obj.type == 'CURVE':#{
3385 route_curves += [obj]
3386 #}
3387 #}
3388 elif ent_type == 'ent_route':
3389 routes += [obj]
3390 elif ent_type == 'ent_volume':#{
3391 cv_ent_volume( obj )
3392 #}
3393 elif ent_type == 'ent_audio':#{
3394 if obj.SR_data.ent_audio[0].flag_3d:
3395 cv_draw_sphere( obj.location, obj.scale[0], (1,1,0) )
3396 #}
3397 elif ent_type == 'ent_font':#{
3398 data = obj.SR_data.ent_font[0]
3399
3400 for i in range(len(data.variants)):#{
3401 sub = data.variants[i].mesh
3402 if not sub: continue
3403
3404 for ch in data.glyphs:#{
3405 mini = (ch.bounds[0],ch.bounds[1])
3406 maxi = (ch.bounds[2]+mini[0],ch.bounds[3]+mini[1])
3407 p0 = sub.matrix_world @ Vector((mini[0],0.0,mini[1]))
3408 p1 = sub.matrix_world @ Vector((maxi[0],0.0,mini[1]))
3409 p2 = sub.matrix_world @ Vector((maxi[0],0.0,maxi[1]))
3410 p3 = sub.matrix_world @ Vector((mini[0],0.0,maxi[1]))
3411
3412 if i == data.variants_index: cc = (0.5,0.5,0.5)
3413 else: cc = (0,0,0)
3414
3415 cv_view_verts += [p0,p1,p1,p2,p2,p3,p3,p0]
3416 cv_view_colours += [cc,cc,cc,cc,cc,cc,cc,cc]
3417 #}
3418 #}
3419 #}
3420 #}
3421 #}
3422
3423 dij = create_node_graph( route_curves, route_gates )
3424
3425 #cv_draw_route_map( route_nodes )
3426 for route in routes:#{
3427 cv_draw_route( route, dij )
3428 #}
3429
3430 cv_draw_lines()
3431 return
3432 #}
3433
3434 classes = [ SR_INTERFACE, SR_MATERIAL_PANEL,\
3435 SR_COLLECTION_SETTINGS, SR_SCENE_SETTINGS, \
3436 SR_COMPILE, SR_COMPILE_THIS, SR_MIRROR_BONE_X,\
3437 \
3438 SR_OBJECT_ENT_GATE, SR_MESH_ENT_GATE, SR_OBJECT_ENT_SPAWN, \
3439 SR_OBJECT_ENT_ROUTE_ENTRY, SR_UL_ROUTE_NODE_LIST, \
3440 SR_OBJECT_ENT_ROUTE, SR_OT_ROUTE_LIST_NEW_ITEM,\
3441 SR_OT_GLYPH_LIST_NEW_ITEM, SR_OT_GLYPH_LIST_DEL_ITEM,\
3442 SR_OT_GLYPH_LIST_MOVE_ITEM,\
3443 SR_OT_AUDIO_LIST_NEW_ITEM,SR_OT_AUDIO_LIST_DEL_ITEM,\
3444 SR_OT_FONT_VARIANT_LIST_NEW_ITEM,SR_OT_FONT_VARIANT_LIST_DEL_ITEM,\
3445 SR_OT_COPY_ENTITY_DATA, \
3446 SR_OBJECT_ENT_VOLUME, \
3447 SR_UL_AUDIO_LIST, SR_OBJECT_ENT_AUDIO_FILE_ENTRY,\
3448 SR_OT_ROUTE_LIST_DEL_ITEM,\
3449 SR_OBJECT_ENT_AUDIO,SR_OBJECT_ENT_MARKER,SR_OBJECT_ENT_GLYPH,\
3450 SR_OBJECT_ENT_FONT_VARIANT,
3451 SR_OBJECT_ENT_GLYPH_ENTRY,\
3452 SR_UL_FONT_VARIANT_LIST,SR_UL_FONT_GLYPH_LIST,\
3453 SR_OBJECT_ENT_FONT,\
3454 \
3455 SR_OBJECT_PROPERTIES, SR_LIGHT_PROPERTIES, SR_BONE_PROPERTIES,
3456 SR_MESH_PROPERTIES, SR_MATERIAL_PROPERTIES \
3457 ]
3458
3459 def register():
3460 #{
3461 for c in classes:
3462 bpy.utils.register_class(c)
3463
3464 bpy.types.Scene.SR_data = \
3465 bpy.props.PointerProperty(type=SR_SCENE_SETTINGS)
3466 bpy.types.Collection.SR_data = \
3467 bpy.props.PointerProperty(type=SR_COLLECTION_SETTINGS)
3468
3469 bpy.types.Object.SR_data = \
3470 bpy.props.PointerProperty(type=SR_OBJECT_PROPERTIES)
3471 bpy.types.Light.SR_data = \
3472 bpy.props.PointerProperty(type=SR_LIGHT_PROPERTIES)
3473 bpy.types.Bone.SR_data = \
3474 bpy.props.PointerProperty(type=SR_BONE_PROPERTIES)
3475 bpy.types.Mesh.SR_data = \
3476 bpy.props.PointerProperty(type=SR_MESH_PROPERTIES)
3477 bpy.types.Material.SR_data = \
3478 bpy.props.PointerProperty(type=SR_MATERIAL_PROPERTIES)
3479
3480 global cv_view_draw_handler
3481 cv_view_draw_handler = bpy.types.SpaceView3D.draw_handler_add(\
3482 cv_draw,(),'WINDOW','POST_VIEW')
3483 #}
3484
3485 def unregister():
3486 #{
3487 for c in classes:
3488 bpy.utils.unregister_class(c)
3489
3490 global cv_view_draw_handler
3491 bpy.types.SpaceView3D.draw_handler_remove(cv_view_draw_handler,'WINDOW')
3492 #}
3493
3494 # ---------------------------------------------------------------------------- #
3495 # #
3496 # QOI encoder #
3497 # #
3498 # ---------------------------------------------------------------------------- #
3499 # #
3500 # Transliteration of: #
3501 # https://github.com/phoboslab/qoi/blob/master/qoi.h #
3502 # #
3503 # Copyright (c) 2021, Dominic Szablewski - https://phoboslab.org #
3504 # SPDX-License-Identifier: MIT #
3505 # QOI - The "Quite OK Image" format for fast, lossless image compression #
3506 # #
3507 # ---------------------------------------------------------------------------- #
3508
3509 class qoi_rgba_t(Structure):
3510 #{
3511 _pack_ = 1
3512 _fields_ = [("r",c_uint8),
3513 ("g",c_uint8),
3514 ("b",c_uint8),
3515 ("a",c_uint8)]
3516 #}
3517
3518 QOI_OP_INDEX = 0x00 # 00xxxxxx
3519 QOI_OP_DIFF = 0x40 # 01xxxxxx
3520 QOI_OP_LUMA = 0x80 # 10xxxxxx
3521 QOI_OP_RUN = 0xc0 # 11xxxxxx
3522 QOI_OP_RGB = 0xfe # 11111110
3523 QOI_OP_RGBA = 0xff # 11111111
3524
3525 QOI_MASK_2 = 0xc0 # 11000000
3526
3527 def qoi_colour_hash( c ):
3528 #{
3529 return c.r*3 + c.g*5 + c.b*7 + c.a*11
3530 #}
3531
3532 def qoi_eq( a, b ):
3533 #{
3534 return (a.r==b.r) and (a.g==b.g) and (a.b==b.b) and (a.a==b.a)
3535 #}
3536
3537 def qoi_32bit( v ):
3538 #{
3539 return bytearray([ (0xff000000 & v) >> 24, \
3540 (0x00ff0000 & v) >> 16, \
3541 (0x0000ff00 & v) >> 8, \
3542 (0x000000ff & v) ])
3543 #}
3544
3545 def qoi_encode( img ):
3546 #{
3547 data = bytearray()
3548
3549 print(F"{' ':<30}",end='\r')
3550 print(F"[QOI] Encoding {img.name}.qoi[{img.size[0]},{img.size[1]}]",end='\r')
3551
3552 index = [ qoi_rgba_t() for _ in range(64) ]
3553
3554 # Header
3555 #
3556 data.extend( bytearray(c_uint32(0x66696f71)) )
3557 data.extend( qoi_32bit( img.size[0] ) )
3558 data.extend( qoi_32bit( img.size[1] ) )
3559 data.extend( bytearray(c_uint8(4)) )
3560 data.extend( bytearray(c_uint8(0)) )
3561
3562 run = 0
3563 px_prev = qoi_rgba_t()
3564 px_prev.r = c_uint8(0)
3565 px_prev.g = c_uint8(0)
3566 px_prev.b = c_uint8(0)
3567 px_prev.a = c_uint8(255)
3568
3569 px = qoi_rgba_t()
3570 px.r = c_uint8(0)
3571 px.g = c_uint8(0)
3572 px.b = c_uint8(0)
3573 px.a = c_uint8(255)
3574
3575 px_len = img.size[0] * img.size[1]
3576 paxels = [ int(min(max(_,0),1)*255) for _ in img.pixels ]
3577
3578 for px_pos in range( px_len ): #{
3579 idx = px_pos * img.channels
3580 nc = img.channels-1
3581
3582 px.r = paxels[idx+min(0,nc)]
3583 px.g = paxels[idx+min(1,nc)]
3584 px.b = paxels[idx+min(2,nc)]
3585 px.a = paxels[idx+min(3,nc)]
3586
3587 if qoi_eq( px, px_prev ): #{
3588 run += 1
3589
3590 if (run == 62) or (px_pos == px_len-1): #{
3591 data.extend( bytearray( c_uint8(QOI_OP_RUN | (run-1))) )
3592 run = 0
3593 #}
3594 #}
3595 else: #{
3596 if run > 0: #{
3597 data.extend( bytearray( c_uint8(QOI_OP_RUN | (run-1))) )
3598 run = 0
3599 #}
3600
3601 index_pos = qoi_colour_hash(px) % 64
3602
3603 if qoi_eq( index[index_pos], px ): #{
3604 data.extend( bytearray( c_uint8(QOI_OP_INDEX | index_pos)) )
3605 #}
3606 else: #{
3607 index[ index_pos ].r = px.r
3608 index[ index_pos ].g = px.g
3609 index[ index_pos ].b = px.b
3610 index[ index_pos ].a = px.a
3611
3612 if px.a == px_prev.a: #{
3613 vr = int(px.r) - int(px_prev.r)
3614 vg = int(px.g) - int(px_prev.g)
3615 vb = int(px.b) - int(px_prev.b)
3616
3617 vg_r = vr - vg
3618 vg_b = vb - vg
3619
3620 if (vr > -3) and (vr < 2) and\
3621 (vg > -3) and (vg < 2) and\
3622 (vb > -3) and (vb < 2):
3623 #{
3624 op = QOI_OP_DIFF | (vr+2) << 4 | (vg+2) << 2 | (vb+2)
3625 data.extend( bytearray( c_uint8(op) ))
3626 #}
3627 elif (vg_r > -9) and (vg_r < 8) and\
3628 (vg > -33) and (vg < 32 ) and\
3629 (vg_b > -9) and (vg_b < 8):
3630 #{
3631 op = QOI_OP_LUMA | (vg+32)
3632 delta = (vg_r+8) << 4 | (vg_b + 8)
3633 data.extend( bytearray( c_uint8(op) ) )
3634 data.extend( bytearray( c_uint8(delta) ))
3635 #}
3636 else: #{
3637 data.extend( bytearray( c_uint8(QOI_OP_RGB) ) )
3638 data.extend( bytearray( c_uint8(px.r) ))
3639 data.extend( bytearray( c_uint8(px.g) ))
3640 data.extend( bytearray( c_uint8(px.b) ))
3641 #}
3642 #}
3643 else: #{
3644 data.extend( bytearray( c_uint8(QOI_OP_RGBA) ) )
3645 data.extend( bytearray( c_uint8(px.r) ))
3646 data.extend( bytearray( c_uint8(px.g) ))
3647 data.extend( bytearray( c_uint8(px.b) ))
3648 data.extend( bytearray( c_uint8(px.a) ))
3649 #}
3650 #}
3651 #}
3652
3653 px_prev.r = px.r
3654 px_prev.g = px.g
3655 px_prev.b = px.b
3656 px_prev.a = px.a
3657 #}
3658
3659 # Padding
3660 for i in range(7):
3661 data.extend( bytearray( c_uint8(0) ))
3662 data.extend( bytearray( c_uint8(1) ))
3663 bytearray_align_to( data, 16, b'\x00' )
3664
3665 return data
3666 #}