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