-#
-# =============================================================================
-#
-# Copyright . . . -----, ,----- ,---. .---.
-# 2021-2022 |\ /| | / | | | | /|
-# | \ / | +-- / +----- +---' | / |
-# | \ / | | / | | \ | / |
-# | \/ | | / | | \ | / |
-# ' ' '--' [] '----- '----- ' ' '---' SOFTWARE
-#
-# =============================================================================
-#
-# Python exporter for Blender, compiles .mdl format for Skate Rift.
-#
-# Its really slow, sorry, I don't know how to speed it up.
-# Also not sure why you need to put # before {} in code blocks, there is errors
-# otherwise
-#
-
import bpy, math, gpu, os
import cProfile
from ctypes import *
from mathutils import *
from gpu_extras.batch import batch_for_shader
+from bpy_extras import mesh_utils
bl_info = {
- "name":"Skate Rift model compiler",
+ "name":"Skaterift .mdl exporter",
"author": "Harry Godden (hgn)",
"version": (0,2),
"blender":(3,1,0),
"location":"Export",
- "descriptin":"",
+ "description":"",
"warning":"",
"wiki_url":"",
"category":"Import/Export",
}
+sr_entity_list = [
+ ('none', 'None', '', 0 ),
+ ('ent_gate', 'Gate', '', 1 ),
+ ('ent_spawn', 'Spawn Point', '', 2 ),
+ ('ent_route_node', 'Routing Path', '', 3 ),
+ ('ent_route', 'Skate Course', '', 4 ),
+ ('ent_water', 'Water Surface', '', 5 ),
+ ('ent_volume', 'Volume/Trigger', '', 6 ),
+ ('ent_audio', 'Audio', '', 7 ),
+ ('ent_marker', 'Marker', '', 8 ),
+ ('ent_font', 'Font', '', 9 ),
+ ('ent_font_variant', 'Font:Variant', '', 10 ),
+ ('ent_traffic', 'Traffic Model', '', 11 ),
+ ('ent_skateshop', 'Skate Shop', '', 12 ),
+ ('ent_camera', 'Camera', '', 13 )
+]
+
+def get_entity_enum_id( alias ):
+#{
+ for et in sr_entity_list:#{
+ if et[0] == alias:#{
+ return et[3]
+ #}
+ #}
+
+ return 0
+#}
+
class mdl_vert(Structure): # 48 bytes. Quite large. Could compress
#{ # the normals and uvs to i16s. Not an
_pack_ = 1 # real issue, yet.
("groups",c_uint8*4)]
#}
+class mdl_transform(Structure):
+#{
+ _fields_ = [("co",c_float*3),
+ ( "s",c_float*3),
+ ( "q",c_float*4)]
+#}
+
class mdl_submesh(Structure):
#{
- _pack_ = 1
_fields_ = [("indice_start",c_uint32),
("indice_count",c_uint32),
("vertex_start",c_uint32),
("vertex_count",c_uint32),
("bbx",(c_float*3)*2),
- ("material_id",c_uint32)] # index into the material array
-#}
-
-class mdl_texture(Structure):
-#{
- _pack_ = 1
- _fields_ = [("pstr_name",c_uint32),
- ("pack_offset",c_uint32),
- ("pack_length",c_uint32)]
+ ("material_id",c_uint16), # index into the material array
+ ("flags",c_uint16)]
#}
class mdl_material(Structure):
#{
- _pack_ = 1
_fields_ = [("pstr_name",c_uint32),
("shader",c_uint32),
("flags",c_uint32),
("colour",c_float*4),
("colour1",c_float*4),
("tex_diffuse",c_uint32),
- ("tex_decal",c_uint32),
- ("tex_normal",c_uint32)]
+ ("tex_none0",c_uint32),
+ ("tex_none1",c_uint32)]
#}
-class mdl_node(Structure):
+class mdl_bone(Structure):
#{
- _pack_ = 1
- _fields_ = [("co",c_float*3),
- ( "q",c_float*4),
- ( "s",c_float*3),
- ("sub_uid",c_uint32), # dont use
- ("submesh_start",c_uint32),
- ("submesh_count",c_uint32),
- ("classtype",c_uint32),
- ("offset",c_uint32),
+ _fields_ = [("co",c_float*3),("end",c_float*3),
("parent",c_uint32),
- ("pstr_name",c_uint32)]
+ ("collider",c_uint32),
+ ("ik_target",c_uint32),
+ ("ik_pole",c_uint32),
+ ("flags",c_uint32),
+ ("pstr_name",c_uint32),
+ ("hitbox",(c_float*3)*2),
+ ("conevx",c_float*3),("conevy",c_float*3),("coneva",c_float*3),
+ ("conet",c_float)]
#}
-class mdl_header(Structure):
+class mdl_armature(Structure):
#{
- _pack_ = 1
- _fields_ = [("identifier",c_uint32),
- ("version",c_uint32),
- ("file_length",c_uint32),
- ("pad0",c_uint32),
-
- ("node_count",c_uint32),
- ("node_offset",c_uint32),
-
- ("submesh_count",c_uint32),
- ("submesh_offset",c_uint32),
-
- ("material_count",c_uint32),
- ("material_offset",c_uint32),
-
- ("texture_count",c_uint32),
- ("texture_offset",c_uint32),
-
- ("anim_count",c_uint32),
- ("anim_offset",c_uint32),
-
- ("entdata_size",c_uint32),
- ("entdata_offset",c_uint32),
-
- ("strings_size",c_uint32),
- ("strings_offset",c_uint32),
-
- ("keyframe_count",c_uint32),
- ("keyframe_offset",c_uint32),
-
- ("vertex_count",c_uint32),
- ("vertex_offset",c_uint32),
-
- ("indice_count",c_uint32),
- ("indice_offset",c_uint32),
-
- ("pack_size",c_uint32),
- ("pack_offset",c_uint32)]
+ _fields_ = [("transform",mdl_transform),
+ ("bone_start",c_uint32),
+ ("bone_count",c_uint32),
+ ("anim_start",c_uint32),
+ ("anim_count",c_uint32)]
#}
class mdl_animation(Structure):
#{
- _pack_ = 1
_fields_ = [("pstr_name",c_uint32),
("length",c_uint32),
("rate",c_float),
- ("offset",c_uint32)]
+ ("keyframe_start",c_uint32)]
#}
-class mdl_keyframe(Structure):
+class mdl_mesh(Structure):
#{
- _pack_ = 1
- _fields_ = [("co",c_float*3),
- ("q",c_float*4),
- ("s",c_float*3)]
+ _fields_ = [("transform",mdl_transform),
+ ("submesh_start",c_uint32),
+ ("submesh_count",c_uint32),
+ ("pstr_name",c_uint32),
+ ("entity_id",c_uint32),
+ ("armature_id",c_uint32)]
#}
-# ---------------------------------------------------------------------------- #
-# #
-# Entity definitions #
-# #
-# ---------------------------------------------------------------------------- #
-#
-# ctypes _fields_ defines the data which is filled in by:
-# def encode_obj( _, node, node_def ):
-#
-# gizmos get drawn into the viewport via:
-# @staticmethod
-# def draw_scene_helpers( obj ):
-#
-# editor enterface, simiraliy:
-# @staticmethod
-# def editor_interface( layout, obj ):
-#
-
-# Classtype 1
-#
-# Purpose: A rift. must target another gate, the target gate can not have more
-# than one target nodes of its own.
-#
-class classtype_gate(Structure):
+class mdl_file(Structure):
#{
- _pack_ = 1
- _fields_ = [("target",c_uint32),
- ("dims",c_float*3)]
-
- def encode_obj(_, node,node_def):
- #{
- node.classtype = 1
-
- obj = node_def['obj']
-
- if obj.cv_data.target != None:
- _.target = obj.cv_data.target.cv_data.uid
-
- if obj.type == 'MESH':
- #{
- _.dims[0] = obj.data.cv_data.v0[0]
- _.dims[1] = obj.data.cv_data.v0[1]
- _.dims[2] = obj.data.cv_data.v0[2]
- #}
- else:
- #{
- _.dims[0] = obj.cv_data.v0[0]
- _.dims[1] = obj.cv_data.v0[1]
- _.dims[2] = obj.cv_data.v0[2]
- #}
- #}
-
- @staticmethod
- def draw_scene_helpers( obj ):
- #{
- global cv_view_verts, cv_view_colours
-
- if obj.type == 'MESH':
- dims = obj.data.cv_data.v0
- else:
- dims = obj.cv_data.v0
-
- vs = [None]*9
- c = Vector((0,0,dims[2]))
-
- vs[0] = obj.matrix_world @ Vector((-dims[0],0.0,-dims[1]+dims[2]))
- vs[1] = obj.matrix_world @ Vector((-dims[0],0.0, dims[1]+dims[2]))
- vs[2] = obj.matrix_world @ Vector(( dims[0],0.0, dims[1]+dims[2]))
- vs[3] = obj.matrix_world @ Vector(( dims[0],0.0,-dims[1]+dims[2]))
- vs[4] = obj.matrix_world @ (c+Vector((-1,0,-2)))
- vs[5] = obj.matrix_world @ (c+Vector((-1,0, 2)))
- vs[6] = obj.matrix_world @ (c+Vector(( 1,0, 2)))
- vs[7] = obj.matrix_world @ (c+Vector((-1,0, 0)))
- vs[8] = obj.matrix_world @ (c+Vector(( 1,0, 0)))
-
- indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(7,8)]
-
- for l in indices:
- #{
- v0 = vs[l[0]]
- v1 = vs[l[1]]
- cv_view_verts += [(v0[0],v0[1],v0[2])]
- cv_view_verts += [(v1[0],v1[1],v1[2])]
- cv_view_colours += [(1,1,0,1),(1,1,0,1)]
- #}
-
- sw = (0.4,0.4,0.4,0.2)
- if obj.cv_data.target != None:
- cv_draw_arrow( obj.location, obj.cv_data.target.location, sw )
- #}
-
- @staticmethod
- def editor_interface( layout, obj ):
- #{
- layout.prop( obj.cv_data, "target" )
-
- mesh = obj.data
- layout.label( text=F"(i) Data is stored in {mesh.name}" )
- layout.prop( mesh.cv_data, "v0", text="Gate dimensions" )
- #}
+ _fields_ = [("path",c_uint32),
+ ("pack_offset",c_uint32),
+ ("pack_size",c_uint32)]
#}
-# Classtype 3
-#
-# Purpose: player can reset here, its a safe place
-# spawns can share the same name, the closest one will be picked
-#
-# when the world loads it will pick the one named 'start' first.
-#
-class classtype_spawn(Structure):
+class mdl_texture(Structure):
#{
- _pack_ = 1
- _fields_ = [("pstr_alias",c_uint32)]
-
- def encode_obj(_, node,node_def):
- #{
- node.classtype = 3
- _.pstr_alias = encoder_process_pstr( node_def['obj'].cv_data.strp )
- #}
+ _fields_ = [("file",mdl_file),
+ ("glname",c_uint32)]
+#}
- @staticmethod
- def draw_scene_helpers( obj ):
- #{
- global cv_view_verts, cv_view_colours
+class mdl_array(Structure):
+#{
+ _fields_ = [("file_offset",c_uint32),
+ ("item_count",c_uint32),
+ ("item_size",c_uint32),
+ ("name",c_byte*16)]
+#}
- vs = [None]*4
- vs[0] = obj.matrix_world @ Vector((0,0,0))
- vs[1] = obj.matrix_world @ Vector((0,2,0))
- vs[2] = obj.matrix_world @ Vector((0.5,1,0))
- vs[3] = obj.matrix_world @ Vector((-0.5,1,0))
- indices = [(0,1),(1,2),(1,3)]
+class mdl_header(Structure):
+#{
+ _fields_ = [("version",c_uint32),
+ ("arrays",mdl_array)]
+#}
- for l in indices:
- #{
- v0 = vs[l[0]]
- v1 = vs[l[1]]
-
- cv_view_verts += [(v0[0],v0[1],v0[2])]
- cv_view_verts += [(v1[0],v1[1],v1[2])]
- cv_view_colours += [(0,1,1,1),(0,1,1,1)]
- #}
+class ent_spawn(Structure):
+#{
+ _fields_ = [("transform",mdl_transform),
+ ("pstr_name",c_uint32)]
+#}
- cv_draw_sphere( obj.location, 20.0, [0.1,0,0.9,0.4] )
- #}
+class ent_light(Structure):
+#{
+ _fields_ = [("transform",mdl_transform),
+ ("daytime",c_uint32),
+ ("type",c_uint32),
+ ("colour",c_float*4),
+ ("angle",c_float),
+ ("range",c_float),
+ ("inverse_world",(c_float*3)*4), # Runtime
+ ("angle_sin_cos",(c_float*2))] # Runtime
+#}
- @staticmethod
- def editor_interface( layout, obj ):
- #{
- layout.prop( obj.cv_data, "strp", text="Alias" )
- #}
+class version_refcount_union(Union):
+#{
+ _fields_ = [("timing_version",c_uint32),
+ ("ref_count",c_uint8)]
#}
-# Classtype 4
-#
-# Purpose: Tells the game to draw water HERE, at this entity.
-#
-class classtype_water(Structure):
+class ent_gate(Structure):
#{
- _pack_ = 1
- _fields_ = [("temp",c_uint32)]
+ _fields_ = [("type",c_uint32),
+ ("target", c_uint32),
+ ("dimensions", c_float*3),
+ ("co", (c_float*3)*2),
+ ("q", (c_float*4)*2),
+ ("to_world",(c_float*3)*4),
+ ("transport",(c_float*3)*4),
+ ("_anonymous_union",version_refcount_union),
+ ("timing_time",c_double),
+ ("routes",c_uint16*4),
+ ("route_count",c_uint8)]
+#}
- def encode_obj(_, node,node_def):
- #{
- node.classtype = 4
- # no data, spooky
- #}
+class ent_route_node(Structure):
+#{
+ _fields_ = [("co",c_float*3),
+ ("ref_count",c_uint8),
+ ("ref_total",c_uint8)]
#}
-# Classtype 8
-#
-# Purpose: Defines a route node and links to up to two more nodes
-#
-class classtype_route_node(Structure):
+class ent_path_index(Structure):
#{
- _pack_ = 1
- _fields_ = [("target",c_uint32),
- ("target1",c_uint32)]
+ _fields_ = [("index",c_uint16)]
+#}
- def encode_obj(_, node,node_def):
- #{
- node.classtype = 8
- obj = node_def['obj']
+class vg_audio_clip(Structure):
+#{
+ _fields_ = [("path",c_uint64),
+ ("flags",c_uint32),
+ ("size",c_uint32),
+ ("data",c_uint64)]
+#}
- if obj.cv_data.target != None:
- _.target = obj.cv_data.target.cv_data.uid
- if obj.cv_data.target1 != None:
- _.target1 = obj.cv_data.target1.cv_data.uid
- #}
+class union_file_audio_clip(Union):
+#{
+ _fields_ = [("file",mdl_file),
+ ("reserved",vg_audio_clip)]
+#}
- @staticmethod
- def draw_scene_helpers( obj ):
- #{
- global cv_view_verts, cv_view_colours
+class ent_audio_clip(Structure):
+#{
+ _fields_ = [("_anon",union_file_audio_clip),
+ ("probability",c_float)]
+#}
- sw = Vector((0.4,0.4,0.4,0.2))
- sw2 = Vector((1.5,0.2,0.2,0.0))
- if obj.cv_data.target != None:
- cv_draw_bpath( obj, obj.cv_data.target, sw, sw )
- if obj.cv_data.target1 != None:
- cv_draw_bpath( obj, obj.cv_data.target1, sw, sw )
+class ent_checkpoint(Structure):
+#{
+ _fields_ = [("gate_index",c_uint16),
+ ("path_start",c_uint16),
+ ("path_count",c_uint16)]
+#}
- cv_draw_bhandle( obj, 1.0, (0.8,0.8,0.8,1.0) )
- cv_draw_bhandle( obj, -1.0, (0.4,0.4,0.4,1.0) )
+class ent_route(Structure):
+#{
+ _fields_ = [("transform",mdl_transform),
+ ("pstr_name",c_uint32),
+ ("checkpoints_start",c_uint16),
+ ("checkpoints_count",c_uint16),
+ ("colour",c_float*4),
+ ("active",c_uint32), #runtime
+ ("factive",c_float),
+ ("board_transform",(c_float*3)*4),
+ ("sm",mdl_submesh),
+ ("latest_pass",c_double)]
+#}
- p1 = obj.location+ \
- obj.matrix_world.to_quaternion() @ Vector((0,0,-6+1.5))
- cv_draw_arrow( obj.location, p1, sw )
- #}
+class ent_water(Structure):
+#{
+ _fields_ = [("transform",mdl_transform),
+ ("max_dist",c_float),
+ ("reserved0",c_uint32),
+ ("reserved1",c_uint32)]
+#}
- @staticmethod
- def editor_interface( layout, obj ):
- #{
- layout.prop( obj.cv_data, "target", text="Left" )
- layout.prop( obj.cv_data, "target1", text="Right" )
- #}
+class volume_trigger(Structure):
+#{
+ _fields_ = [("event",c_uint32),
+ ("blank",c_uint32)]
#}
-# Classtype 9
-#
-# Purpose: Defines a route, its 'starting' point, and the colour to use for it
-#
-class classtype_route(Structure):
+class volume_particles(Structure):
#{
- _pack_ = 1
- _fields_ = [("id_start",c_uint32),
- ("pstr_name",c_uint32),
- ("colour",c_float*3)]
+ _fields_ = [("blank",c_uint32),
+ ("blank2",c_uint32)]
+#}
- def encode_obj(_, node,node_def):
- #{
- node.classtype = 9
- obj = node_def['obj']
+class volume_union(Union):
+#{
+ _fields_ = [("trigger",volume_trigger),
+ ("particles",volume_particles)]
+#}
- _.colour[0] = obj.cv_data.colour[0]
- _.colour[1] = obj.cv_data.colour[1]
- _.colour[2] = obj.cv_data.colour[2]
- _.pstr_name = encoder_process_pstr( obj.cv_data.strp )
+class ent_volume(Structure):
+#{
+ _fields_ = [("transform",mdl_transform),
+ ("to_world",(c_float*3)*4),
+ ("to_local",(c_float*3)*4),
+ ("type",c_uint32),
+ ("target",c_uint32),
+ ("_anon",volume_union)]
+#}
- if obj.cv_data.target != None:
- _.id_start = obj.cv_data.target.cv_data.uid
- #}
+class ent_audio(Structure):
+#{
+ _fields_ = [("transform",mdl_transform),
+ ("flags",c_uint32),
+ ("clip_start",c_uint32),
+ ("clip_count",c_uint32),
+ ("volume",c_float),
+ ("crossfade",c_float),
+ ("channel_behaviour",c_uint32),
+ ("group",c_uint32),
+ ("probability_curve",c_uint32),
+ ("max_channels",c_uint32)]
+#}
- @staticmethod
- def draw_scene_helpers( obj ):
- #{
- global cv_view_verts, cv_view_colours, cv_view_course_i
+class ent_marker(Structure):
+#{
+ _fields_ = [("transform",mdl_transform),
+ ("name",c_uint32)]
+#}
- if obj.cv_data.target:
- cv_draw_arrow( obj.location, obj.cv_data.target.location, [1,1,1,1] )
-
- # Tries to simulate how we do it in the game
- #
- stack = [None]*64
- stack_i = [0]*64
- stack[0] = obj.cv_data.target
- si = 1
- loop_complete = False
+class ent_glyph(Structure):
+#{
+ _fields_ = [("size",c_float*2),
+ ("indice_start",c_uint32),
+ ("indice_count",c_uint32)]
+#}
- while si > 0:
- #{
- if stack_i[si-1] == 2:
- #{
- si -= 1
- continue
+class ent_font_variant(Structure):
+#{
+ _fields_ = [("name",c_uint32),
+ ("material_id",c_uint32)]
+#}
- if si == 0: # Loop failed to complete
- break
- #}
+class ent_font(Structure):
+#{
+ _fields_ = [("alias",c_uint32),
+ ("variant_start",c_uint32),
+ ("variant_count",c_uint32),
+ ("glyph_start",c_uint32),
+ ("glyph_count",c_uint32),
+ ("glyph_utf32_base",c_uint32)]
+#}
- node = stack[si-1]
+class ent_traffic(Structure):
+#{
+ _fields_ = [("transform",mdl_transform),
+ ("submesh_start",c_uint32),
+ ("submesh_count",c_uint32),
+ ("start_node",c_uint32),
+ ("node_count",c_uint32),
+ ("speed",c_float),
+ ("t",c_float),
+ ("index",c_uint32)]
+#}
- targets = [None,None]
- targets[0] = node.cv_data.target
+class ent_skateshop(Structure):
+#{
+ _fields_ = [("transform",mdl_transform),
+ ("id_display",c_uint32),
+ ("id_info",c_uint32),
+ ("id_rack",c_uint32),
+ ("id_camera",c_uint32)]
+#}
- if node.cv_data.classtype == 'classtype_route_node':
- #{
- targets[1] = node.cv_data.target1
- #}
-
- nextnode = targets[stack_i[si-1]]
- stack_i[si-1] += 1
-
- if nextnode != None: # branch
- #{
- if nextnode == stack[0]: # Loop completed
- #{
- loop_complete = True
- break
- #}
+class ent_camera(Structure):
+#{
+ _fields_ = [("transform",mdl_transform),
+ ("fov",c_float)]
+#}
- valid=True
- for sj in range(si):
- #{
- if stack[sj] == nextnode: # invalidated path
- #{
- valid=False
- break
- #}
- #}
+def obj_ent_type( obj ):
+#{
+ if obj.type == 'ARMATURE': return 'mdl_armature'
+ elif obj.type == 'LIGHT': return 'ent_light'
+ elif obj.type == 'CAMERA': return 'ent_camera'
+ else: return obj.SR_data.ent_type
+#}
- if valid:
- #{
- stack_i[si] = 0
- stack[si] = nextnode
- si += 1
- continue
- #}
- #}
- #}
+def sr_filter_ent_type( obj, ent_types ):
+#{
+ if obj == bpy.context.active_object: return False
- if loop_complete:
- #{
- cc = Vector((obj.cv_data.colour[0],\
- obj.cv_data.colour[1],\
- obj.cv_data.colour[2],\
- 1.0))
-
- for sj in range(si):
- #{
- sk = (sj+1)%si
-
- if stack[sj].cv_data.classtype == 'classtype_gate' and \
- stack[sk].cv_data.classtype == 'classtype_gate':
- #{
- dist = (stack[sj].location-stack[sk].location).magnitude
- cv_draw_sbpath( stack[sj], stack[sk], cc*0.4, cc, dist, dist )
- #}
- else:
- cv_draw_bpath( stack[sj], stack[sk], cc, cc )
+ for c0 in obj.users_collection:#{
+ for c1 in bpy.context.active_object.users_collection:#{
+ if c0 == c1:#{
+ return obj_ent_type( obj ) in ent_types
#}
-
- cv_view_course_i += 1
#}
#}
- @staticmethod
- def editor_interface( layout, obj ):
- #{
- layout.prop( obj.cv_data, "target", text="'Start' from" )
- layout.prop( obj.cv_data, "colour" )
- layout.prop( obj.cv_data, "strp", text="Name" )
- #}
+ return False
#}
-# Classtype 12
-#
-# Purpose: links an mesh node to a type 11
-#
-class classtype_skin(Structure):
+def compile_obj_transform( obj, transform ):
#{
- _pack_ = 1
- _fields_ = [("skeleton",c_uint32)]
+ co = obj.matrix_world @ Vector((0,0,0))
+ q = obj.matrix_local.to_quaternion()
+ s = obj.scale
+
+ # Setup transform
+ #
+ transform.co[0] = co[0]
+ transform.co[1] = co[2]
+ transform.co[2] = -co[1]
+ transform.q[0] = q[1]
+ transform.q[1] = q[3]
+ transform.q[2] = -q[2]
+ transform.q[3] = q[0]
+ transform.s[0] = s[0]
+ transform.s[1] = s[2]
+ transform.s[2] = s[1]
+#}
- def encode_obj(_, node,node_def):
- #{
- node.classtype = 12
-
- armature_def = node_def['linked_armature']
- _.skeleton = armature_def['obj'].cv_data.uid
- #}
+def int_align_to( v, align ):
+#{
+ while(v%align)!=0: v += 1
+ return v
#}
-# Classtype 11
-#
-# Purpose: defines the allocation requirements for a skeleton
-#
-class classtype_skeleton(Structure):
+def bytearray_align_to( buffer, align, w=b'\xaa' ):
#{
- _pack_ = 1
- _fields_ = [("channels",c_uint32),
- ("ik_count",c_uint32),
- ("collider_count",c_uint32),
- ("anim_start",c_uint32),
- ("anim_count",c_uint32)]
+ while (len(buffer) % align) != 0: buffer.extend(w)
+ return buffer
+#}
- def encode_obj(_, node,node_def):
- #{
- node.classtype = 11
-
- _.channels = len( node_def['bones'] )
- _.ik_count = node_def['ik_count']
- _.collider_count = node_def['collider_count']
- _.anim_start = node_def['anim_start']
- _.anim_count = node_def['anim_count']
+def bytearray_print_hex( s, w=16 ):
+#{
+ for r in range((len(s)+(w-1))//w):#{
+ i0=(r+0)*w
+ i1=min((r+1)*w,len(s))
+ print( F'{r*w:06x}| \x1B[31m', end='')
+ print( F"{' '.join('{:02x}'.format(x) for x in s[i0:i1]):<48}",end='' )
+ print( "\x1B[0m", end='')
+ print( ''.join(chr(x) if (x>=33 and x<=126) else '.' for x in s[i0:i1] ) )
#}
#}
-
-# Classtype 10
-#
-# Purpose: intrinsic bone type, stores collision information and limits too
-#
-class classtype_bone(Structure):
+def sr_compile_string( s ):
#{
- _pack_ = 1
- _fields_ = [("deform",c_uint32),
- ("ik_target",c_uint32),
- ("ik_pole",c_uint32),
- ("collider",c_uint32),
- ("use_limits",c_uint32),
- ("angle_limits",(c_float*3)*2),
- ("hitbox",(c_float*3)*2)]
+ if s in sr_compile.string_cache: return sr_compile.string_cache[s]
+
+ index = len( sr_compile.string_data )
+ sr_compile.string_cache[s] = index
+ sr_compile.string_data.extend( s.encode('utf-8') )
+ sr_compile.string_data.extend( b'\0' )
- def encode_obj(_, node,node_def):
- #{
- node.classtype = 10
-
- armature_def = node_def['linked_armature']
- obj = node_def['bone']
-
- _.deform = node_def['deform']
-
- if 'ik_target' in node_def:
- #{
- _.ik_target = armature_def['bones'].index( node_def['ik_target'] )
- _.ik_pole = armature_def['bones'].index( node_def['ik_pole'] )
- #}
-
- # For ragdolls
- #
- if obj.cv_data.collider:
- #{
- _.collider = 1
- _.hitbox[0][0] = obj.cv_data.v0[0]
- _.hitbox[0][1] = obj.cv_data.v0[2]
- _.hitbox[0][2] = -obj.cv_data.v1[1]
- _.hitbox[1][0] = obj.cv_data.v1[0]
- _.hitbox[1][1] = obj.cv_data.v1[2]
- _.hitbox[1][2] = -obj.cv_data.v0[1]
- #}
-
- if obj.cv_data.con0:
- #{
- _.use_limits = 1
- _.angle_limits[0][0] = obj.cv_data.mins[0]
- _.angle_limits[0][1] = obj.cv_data.mins[2]
- _.angle_limits[0][2] = -obj.cv_data.maxs[1]
- _.angle_limits[1][0] = obj.cv_data.maxs[0]
- _.angle_limits[1][1] = obj.cv_data.maxs[2]
- _.angle_limits[1][2] = -obj.cv_data.mins[1]
- #}
- #}
-#}
-
-# Classtype 100
-#
-# Purpose: sends a signal to another entity
-#
-class classtype_trigger(Structure):
-#{
- _pack_ = 1
- _fields_ = [("target",c_uint32)]
-
- def encode_obj(_, node,node_def ):
- #{
- node.classtype = 100
- if node_def['obj'].cv_data.target:
- _.target = node_def['obj'].cv_data.target.cv_data.uid
- #}
-
- @staticmethod
- def draw_scene_helpers( obj ):
- #{
- global cv_view_verts, cv_view_colours
- cv_draw_ucube( obj.matrix_world, [0,1,0,1] )
-
- if obj.cv_data.target:
- cv_draw_arrow( obj.location, obj.cv_data.target.location, [1,1,1,1] )
- #}
-
- @staticmethod
- def editor_interface( layout, obj ):
- #{
- layout.prop( obj.cv_data, "target", text="Triggers" )
- #}
-#}
-
-# Classtype 101
-#
-# Purpose: Gives the player an achievement.
-# No cheating! You shouldn't use this entity anyway, since only ME can
-# add achievements to the steam ;)
-#
-class classtype_logic_achievement(Structure):
-#{
- _pack_ = 1
- _fields_ = [("pstr_name",c_uint32)]
-
- def encode_obj(_, node,node_def ):
- #{
- node.classtype = 101
- _.pstr_name = encoder_process_pstr( node_def['obj'].cv_data.strp )
- #}
-
- @staticmethod
- def editor_interface( layout, obj ):
- #{
- layout.prop( obj.cv_data, "strp", text="Achievement ID" )
- #}
-#}
-
-# Classtype 102
-#
-# Purpose: sends a signal to another entity
-#
-class classtype_logic_relay(Structure):
-#{
- _pack_ = 1
- _fields_ = [("targets",c_uint32*4)]
-
- def encode_obj(_, node,node_def ):
- #{
- node.classtype = 102
- obj = node_def['obj']
- if obj.cv_data.target:
- _.targets[0] = obj.cv_data.target.cv_data.uid
- if obj.cv_data.target1:
- _.targets[1] = obj.cv_data.target1.cv_data.uid
- if obj.cv_data.target2:
- _.targets[2] = obj.cv_data.target2.cv_data.uid
- if obj.cv_data.target3:
- _.targets[3] = obj.cv_data.target3.cv_data.uid
- #}
-
- @staticmethod
- def draw_scene_helpers( obj ):
- #{
- global cv_view_verts, cv_view_colours
-
- if obj.cv_data.target:
- cv_draw_arrow( obj.location, obj.cv_data.target.location, [1,1,1,1] )
- if obj.cv_data.target1:
- cv_draw_arrow( obj.location, obj.cv_data.target1.location, [1,1,1,1] )
- if obj.cv_data.target2:
- cv_draw_arrow( obj.location, obj.cv_data.target2.location, [1,1,1,1] )
- if obj.cv_data.target3:
- cv_draw_arrow( obj.location, obj.cv_data.target3.location, [1,1,1,1] )
- #}
-
- @staticmethod
- def editor_interface( layout, obj ):
- #{
- layout.prop( obj.cv_data, "target", text="Triggers" )
- layout.prop( obj.cv_data, "target1", text="Triggers" )
- layout.prop( obj.cv_data, "target2", text="Triggers" )
- layout.prop( obj.cv_data, "target3", text="Triggers" )
- #}
-#}
-
-# Classtype 14
-#
-# Purpose: Plays some audio (44100hz .ogg vorbis only)
-# NOTE: There is a 32mb limit on the audio buffer, world audio is
-# decompressed and stored in signed 16 bit integers (2 bytes)
-# per sample.
-#
-# volume: not used if has 3D flag
-# flags:
-# AUDIO_FLAG_LOOP 0x1
-# AUDIO_FLAG_ONESHOT 0x2 (DONT USE THIS, it breaks semaphores)
-# AUDIO_FLAG_SPACIAL_3D 0x4 (Probably what you want)
-# AUDIO_FLAG_AUTO_START 0x8 (Play when the world starts)
-# ......
-# the rest are just internal flags, only use the above 3.
-#
-class classtype_audio(Structure):
-#{
- _pack_ = 1
- _fields_ = [("pstr_file",c_uint32),
- ("flags",c_uint32),
- ("volume",c_float)]
-
- def encode_obj(_, node,node_def ):
- #{
- node.classtype = 14
-
- obj = node_def['obj']
-
- _.pstr_file = encoder_process_pstr( obj.cv_data.strp )
-
- flags = 0x00
- if obj.cv_data.bp0: flags |= 0x1
- if obj.cv_data.bp1: flags |= 0x4
- if obj.cv_data.bp2: flags |= 0x8
-
- _.flags = flags
- _.volume = obj.cv_data.fltp
- #}
-
- @staticmethod
- def editor_interface( layout, obj ):
- #{
- layout.prop( obj.cv_data, "strp" )
-
- layout.prop( obj.cv_data, "bp0", text = "Looping" )
- layout.prop( obj.cv_data, "bp1", text = "3D Audio" )
- layout.prop( obj.cv_data, "bp2", text = "Auto Start" )
- #}
-
- @staticmethod
- def draw_scene_helpers( obj ):
- #{
- global cv_view_verts, cv_view_colours
-
- cv_draw_sphere( obj.location, obj.scale[0], [1,1,0,1] )
- #}
-#}
-
-class classtype_spawn_link(Structure):
-#{
- _pack_ = 1
- _fields_ = [("connections",c_uint32*4)]
-
- def encode_obj(_, node,node_def ):
- #{
- node.classtype = 0
- #}
-
- @staticmethod
- def editor_interface( layout, obj ):
- #{
- pass
- #}
-
- @staticmethod
- def draw_scene_helpers( obj ):
- #{
- global cv_view_verts, cv_view_colours
-
- count = 0
-
- for obj1 in bpy.context.collection.objects:
- #{
- if (obj1.cv_data.classtype != 'classtype_spawn_link') and \
- (obj1.cv_data.classtype != 'classtype_spawn') :
- continue
-
- if (obj1.location - obj.location).length < 40.0:
- #{
- cv_draw_line( obj.location, obj1.location, [1,1,1,1] )
- count +=1
- #}
-
- if count == 4:
- break
- #}
-
- cv_draw_sphere( obj.location, 20.0, [0.5,0,0.2,0.4] )
- #}
-#}
-
-# ---------------------------------------------------------------------------- #
-# #
-# Compiler section #
-# #
-# ---------------------------------------------------------------------------- #
-
-# Current encoder state
-#
-g_encoder = None
-
-# Reset encoder
-#
-def encoder_init( collection ):
-#{
- global g_encoder
-
- g_encoder = \
- {
- # The actual file header
- #
- 'header': mdl_header(),
-
- # Options
- #
- 'pack_textures': collection.cv_data.pack_textures,
-
- # Compiled data chunks (each can be read optionally by the client)
- #
- 'data':
- {
- #1---------------------------------
- 'node': [], # Metadata 'chunk'
- 'submesh': [],
- 'material': [],
- 'texture': [],
- 'anim': [],
- 'entdata': bytearray(), # variable width
- 'strings': bytearray(), # .
- #2---------------------------------
- 'keyframe': [], # Animations
- #3---------------------------------
- 'vertex': [], # Mesh data
- 'indice': [],
- #4---------------------------------
- 'pack': bytearray() # Other generic packed data
- },
-
- # All objects of the model in their final heirachy
- #
- "uid_count": 1,
- "scene_graph":{},
- "graph_lookup":{},
-
- # Allows us to reuse definitions
- #
- 'string_cache':{},
- 'mesh_cache': {},
- 'material_cache': {},
- 'texture_cache': {}
- }
-
- g_encoder['header'].identifier = 0xABCD0000
- g_encoder['header'].version = 1
-
- # Add fake NoneID material and texture
- #
- none_material = mdl_material()
- none_material.pstr_name = encoder_process_pstr( "" )
- none_material.texture_id = 0
-
- none_texture = mdl_texture()
- none_texture.pstr_name = encoder_process_pstr( "" )
- none_texture.pack_offset = 0
- none_texture.pack_length = 0
-
- g_encoder['data']['material'] += [none_material]
- g_encoder['data']['texture'] += [none_texture]
-
- g_encoder['data']['pack'].extend( b'datapack\0\0\0\0\0\0\0\0' )
-
- # Add root node
- #
- root = mdl_node()
- root.co[0] = 0
- root.co[1] = 0
- root.co[2] = 0
- root.q[0] = 0
- root.q[1] = 0
- root.q[2] = 0
- root.q[3] = 1
- root.s[0] = 1
- root.s[1] = 1
- root.s[2] = 1
- root.pstr_name = encoder_process_pstr('')
- root.submesh_start = 0
- root.submesh_count = 0
- root.offset = 0
- root.classtype = 0
- root.parent = 0xffffffff
-
- g_encoder['data']['node'] += [root]
-#}
-
-
-# fill with 0x00 until a multiple of align. Returns how many bytes it added
-#
-def bytearray_align_to( buffer, align, offset=0 ):
-#{
- count = 0
-
- while ((len(buffer)+offset) % align) != 0:
- #{
- buffer.extend( b'\0' )
- count += 1
- #}
-
- return count
-#}
-
-# Add a string to the string buffer except if it already exists there then we
-# just return its ID.
-#
-def encoder_process_pstr( s ):
-#{
- global g_encoder
-
- cache = g_encoder['string_cache']
-
- if s in cache:
- return cache[s]
-
- cache[s] = len( g_encoder['data']['strings'] )
-
- buffer = g_encoder['data']['strings']
- buffer.extend( s.encode('utf-8') )
- buffer.extend( b'\0' )
-
- bytearray_align_to( buffer, 4 )
- return cache[s]
-#}
-
-def get_texture_resource_name( img ):
-#{
- return os.path.splitext( img.name )[0]
-#}
-
-# Pack a texture
-#
-def encoder_process_texture( img ):
-#{
- global g_encoder
-
- if img == None:
- return 0
-
- cache = g_encoder['texture_cache']
- buffer = g_encoder['data']['texture']
- pack = g_encoder['data']['pack']
-
- name = get_texture_resource_name( img )
-
- if name in cache:
- return cache[name]
-
- cache[name] = len( buffer )
-
- tex = mdl_texture()
- tex.pstr_name = encoder_process_pstr( name )
-
- if g_encoder['pack_textures']:
- #{
- tex.pack_offset = len( pack )
- pack.extend( qoi_encode( img ) )
- tex.pack_length = len( pack ) - tex.pack_offset
- #}
- else:
- tex.pack_offset = 0
-
- buffer += [ tex ]
- return cache[name]
-#}
+ bytearray_align_to( sr_compile.string_data, 4 )
+ return index
+#}
def material_tex_image(v):
#{
},
"Mix":
{
- "Color1": material_tex_image("tex_diffuse"),
- "Color2": material_tex_image("tex_decal")
+ "A": material_tex_image("tex_diffuse"),
+ "B": material_tex_image("tex_decal")
},
},
"Normal":
#{
info = {}
- # Using the cv_graph_mapping as a reference, go through the shader
+ # Using the cxr_graph_mapping as a reference, go through the shader
# graph and gather all $props from it.
#
- def _graph_read( node_def, node=None, depth=0 ):
- #{
+ def _graph_read( node_def, node=None, depth=0 ):#{
nonlocal mat
nonlocal info
# Find rootnodes
#
- if node == None:
- #{
+ if node == None:#{
_graph_read.extracted = []
- for node_idname in node_def:
- #{
- for n in mat.node_tree.nodes:
- #{
- if n.name == node_idname:
- #{
+ for node_idname in node_def:#{
+ for n in mat.node_tree.nodes:#{
+ if n.name == node_idname:#{
node_def = node_def[node_idname]
node = n
break
#}
#}
- for link in node_def:
- #{
+ for link in node_def:#{
link_def = node_def[link]
- if isinstance( link_def, dict ):
- #{
- node_link = node.inputs[link]
+ if isinstance( link_def, dict ):#{
+ node_link = None
+ for x in node.inputs:#{
+ if isinstance( x, bpy.types.NodeSocketColor ):#{
+ if link == x.name:#{
+ node_link = x
+ break
+ #}
+ #}
+ #}
- if node_link.is_linked:
- #{
+ if node_link and node_link.is_linked:#{
# look for definitions for the connected node type
#
from_node = node_link.links[0].from_node
node_name = from_node.name.split('.')[0]
- if node_name in link_def:
- #{
+ if node_name in link_def:#{
from_node_def = link_def[ node_name ]
_graph_read( from_node_def, from_node, depth+1 )
#}
-
- # No definition! :(
- # TODO: Make a warning for this?
#}
- else:
- #{
- if "default" in link_def:
- #{
+ else:#{
+ if "default" in link_def:#{
prop = link_def['default']
info[prop] = node_link.default_value
#}
#}
#}
- else:
- #{
+ else:#{
prop = link_def
info[prop] = getattr( node, link )
#}
return info
#}
-# Add a material to the material buffer. Returns 0 (None ID) if invalid
-#
-def encoder_process_material( mat ):
+def vg_str_bin( s ):
+#{
+ decoded = bytearray()
+ for i in range(len(s)//2):#{
+ c = (ord(s[i*2+0])-0x41)
+ c |= (ord(s[i*2+1])-0x41)<<4
+ decoded.extend(bytearray(c_uint8(c))) #??
+ #}
+ return decoded
+#}
+
+def sr_pack_file( file, path, data ):
#{
- global g_encoder
+ file.path = sr_compile_string( path )
+ file.pack_offset = len( sr_compile.pack_data )
+ file.pack_size = len( data )
+
+ sr_compile.pack_data.extend( data )
+ bytearray_align_to( sr_compile.pack_data, 16 )
+#}
- if mat == None:
+def sr_compile_texture( img ):
+#{
+ if img == None:
return 0
- cache = g_encoder['material_cache']
- buffer = g_encoder['data']['material']
+ name = os.path.splitext( img.name )[0]
+
+ if name in sr_compile.texture_cache:
+ return sr_compile.texture_cache[name]
+
+ texture_index = (len(sr_compile.texture_data)//sizeof(mdl_texture)) +1
+
+ tex = mdl_texture()
+ tex.glname = 0
+
+ if sr_compile.pack_textures:#{
+ filedata = qoi_encode( img )
+ sr_pack_file( tex.file, name, filedata )
+ #}
+
+ sr_compile.texture_cache[name] = texture_index
+ sr_compile.texture_data.extend( bytearray(tex) )
+ return texture_index
+#}
- if mat.name in cache:
- return cache[mat.name]
+def sr_compile_material( mat ):
+#{
+ if mat == None:
+ return 0
+ if mat.name in sr_compile.material_cache:
+ return sr_compile.material_cache[mat.name]
- cache[mat.name] = len( buffer )
+ index = (len(sr_compile.material_data)//sizeof(mdl_material))+1
+ sr_compile.material_cache[mat.name] = index
- dest = mdl_material()
- dest.pstr_name = encoder_process_pstr( mat.name )
+ m = mdl_material()
+ m.pstr_name = sr_compile_string( mat.name )
flags = 0x00
- if mat.cv_data.collision:
- flags |= 0x2
- if mat.cv_data.skate_surface: flags |= 0x1
- if mat.cv_data.grind_surface: flags |= (0x8|0x1)
+ if mat.SR_data.collision:#{
+ flags |= 0x2 # collision flag
+ if (mat.SR_data.shader != 'invisible') and \
+ (mat.SR_data.shader != 'boundary'):#{
+ if mat.SR_data.skate_surface: flags |= 0x1
+ if mat.SR_data.grow_grass: flags |= 0x4
+ if mat.SR_data.grind_surface: flags |= 0x8
+ #}
+ if mat.SR_data.shader == 'invisible': flags |= 0x10
+ if mat.SR_data.shader == 'boundary': flags |= (0x10|0x20)
+ #}
- if mat.cv_data.grow_grass: flags |= 0x4
- dest.flags = flags
+ m.flags = flags
- if mat.cv_data.surface_prop == 'concrete': dest.surface_prop = 0
- if mat.cv_data.surface_prop == 'wood': dest.surface_prop = 1
- if mat.cv_data.surface_prop == 'grass': dest.surface_prop = 2
+ m.surface_prop = int(mat.SR_data.surface_prop)
- if mat.cv_data.shader == 'standard': dest.shader = 0
- if mat.cv_data.shader == 'standard_cutout': dest.shader = 1
- if mat.cv_data.shader == 'terrain_blend':
- #{
- dest.shader = 2
+ if mat.SR_data.shader == 'standard': m.shader = 0
+ if mat.SR_data.shader == 'standard_cutout': m.shader = 1
+ if mat.SR_data.shader == 'terrain_blend':#{
+ m.shader = 2
- dest.colour[0] = pow( mat.cv_data.sand_colour[0], 1.0/2.2 )
- dest.colour[1] = pow( mat.cv_data.sand_colour[1], 1.0/2.2 )
- dest.colour[2] = pow( mat.cv_data.sand_colour[2], 1.0/2.2 )
- dest.colour[3] = 1.0
+ m.colour[0] = pow( mat.SR_data.sand_colour[0], 1.0/2.2 )
+ m.colour[1] = pow( mat.SR_data.sand_colour[1], 1.0/2.2 )
+ m.colour[2] = pow( mat.SR_data.sand_colour[2], 1.0/2.2 )
+ m.colour[3] = 1.0
- dest.colour1[0] = mat.cv_data.blend_offset[0]
- dest.colour1[1] = mat.cv_data.blend_offset[1]
+ m.colour1[0] = mat.SR_data.blend_offset[0]
+ m.colour1[1] = mat.SR_data.blend_offset[1]
#}
- if mat.cv_data.shader == 'vertex_blend':
- #{
- dest.shader = 3
+ if mat.SR_data.shader == 'vertex_blend':#{
+ m.shader = 3
- dest.colour1[0] = mat.cv_data.blend_offset[0]
- dest.colour1[1] = mat.cv_data.blend_offset[1]
+ m.colour1[0] = mat.SR_data.blend_offset[0]
+ m.colour1[1] = mat.SR_data.blend_offset[1]
#}
- if mat.cv_data.shader == 'water':
- #{
- dest.shader = 4
+ if mat.SR_data.shader == 'water':#{
+ m.shader = 4
+
+ m.colour[0] = pow( mat.SR_data.shore_colour[0], 1.0/2.2 )
+ m.colour[1] = pow( mat.SR_data.shore_colour[1], 1.0/2.2 )
+ m.colour[2] = pow( mat.SR_data.shore_colour[2], 1.0/2.2 )
+ m.colour[3] = 1.0
+ m.colour1[0] = pow( mat.SR_data.ocean_colour[0], 1.0/2.2 )
+ m.colour1[1] = pow( mat.SR_data.ocean_colour[1], 1.0/2.2 )
+ m.colour1[2] = pow( mat.SR_data.ocean_colour[2], 1.0/2.2 )
+ m.colour1[3] = 1.0
+ #}
+
+ if mat.SR_data.shader == 'invisible':#{
+ m.shader = 5
+ #}
- dest.colour[0] = pow( mat.cv_data.shore_colour[0], 1.0/2.2 )
- dest.colour[1] = pow( mat.cv_data.shore_colour[1], 1.0/2.2 )
- dest.colour[2] = pow( mat.cv_data.shore_colour[2], 1.0/2.2 )
- dest.colour[3] = 1.0
- dest.colour1[0] = pow( mat.cv_data.ocean_colour[0], 1.0/2.2 )
- dest.colour1[1] = pow( mat.cv_data.ocean_colour[1], 1.0/2.2 )
- dest.colour1[2] = pow( mat.cv_data.ocean_colour[2], 1.0/2.2 )
- dest.colour1[3] = 1.0
+ if mat.SR_data.shader == 'boundary':#{
+ m.shader = 6
#}
inf = material_info( mat )
-
- if mat.cv_data.shader == 'standard' or \
- mat.cv_data.shader == 'standard_cutout' or \
- mat.cv_data.shader == 'terrain_blend' or \
- mat.cv_data.shader == 'vertex_blend':
+
+ if mat.SR_data.shader == 'standard' or \
+ mat.SR_data.shader == 'standard_cutout' or \
+ mat.SR_data.shader == 'terrain_blend' or \
+ mat.SR_data.shader == 'vertex_blend':
#{
if 'tex_diffuse' in inf:
- dest.tex_diffuse = encoder_process_texture(inf['tex_diffuse'])
+ m.tex_diffuse = sr_compile_texture(inf['tex_diffuse'])
#}
- buffer += [dest]
- return cache[mat.name]
+ sr_compile.material_data.extend( bytearray(m) )
+ return index
#}
-# Create a tree structure containing all the objects in the collection
-#
-def encoder_build_scene_graph( collection ):
+def sr_armature_bones( armature ):
#{
- global g_encoder
-
- print( " creating scene graph" )
-
- # initialize root
- #
- graph = g_encoder['scene_graph']
- graph_lookup = g_encoder['graph_lookup']
- graph["obj"] = None
- graph["depth"] = 0
- graph["children"] = []
- graph["uid"] = 0
- graph["parent"] = None
-
- def _new_uid():
+ def _recurse_bone( b ):
#{
- global g_encoder
- uid = g_encoder['uid_count']
- g_encoder['uid_count'] += 1
- return uid
+ yield b
+ for c in b.children: yield from _recurse_bone( c )
#}
- for obj in collection.all_objects:
- #{
- if obj.parent: continue
-
- def _extend( p, n, d ):
- #{
- uid = _new_uid()
- tree = {}
- tree["uid"] = uid
- tree["children"] = []
- tree["depth"] = d
- tree["obj"] = n
- tree["parent"] = p
- n.cv_data.uid = uid
-
- # Descend into amature
- #
- if n.type == 'ARMATURE':
- #{
- tree["bones"] = [None] # None is the root transform
- tree["ik_count"] = 0
- tree["collider_count"] = 0
-
- # Here also collects some information about constraints, ik and
- # counts colliders for the armature.
- #
- def _extendb( p, n, d ):
- #{
- nonlocal tree
-
- btree = {}
- btree["bone"] = n
- btree["linked_armature"] = tree
- btree["uid"] = _new_uid()
- btree["children"] = []
- btree["depth"] = d
- btree["parent"] = p
- tree["bones"] += [n.name]
-
- for c in n.children:
- #{
- _extendb( btree, c, d+1 )
- #}
-
- for c in tree['obj'].pose.bones[n.name].constraints:
- #{
- if c.type == 'IK':
- #{
- btree["ik_target"] = c.subtarget
- btree["ik_pole"] = c.pole_subtarget
- tree["ik_count"] += 1
- #}
- #}
+ for b in armature.data.bones:
+ if not b.parent:
+ yield from _recurse_bone( b )
+#}
- if n.cv_data.collider:
- tree['collider_count'] += 1
+def sr_entity_id( obj ):
+#{
+ tipo = get_entity_enum_id( obj_ent_type(obj) )
+ index = sr_compile.entity_ids[ obj.name ]
- btree['deform'] = n.use_deform
- p['children'] += [btree]
- #}
+ return (tipo&0xffff)<<16 | (index&0xffff)
+#}
- for b in n.data.bones:
- if not b.parent:
- _extendb( tree, b, d+1 )
- #}
-
- # Recurse into children of this object
- #
- for obj1 in n.children:
- #{
- nonlocal collection
- for c1 in obj1.users_collection:
- #{
- if c1 == collection:
- #{
- _extend( tree, obj1, d+1 )
- break
- #}
- #}
- #}
+# Returns submesh_start,count and armature_id
+def sr_compile_mesh_internal( obj ):
+#{
+ can_use_cache = True
+ armature = None
- p["children"] += [tree]
- graph_lookup[n] = tree
+ submesh_start = 0
+ submesh_count = 0
+ armature_id = 0
+ for mod in obj.modifiers:#{
+ if mod.type == 'DATA_TRANSFER' or mod.type == 'SHRINKWRAP' or \
+ mod.type == 'BOOLEAN' or mod.type == 'CURVE' or \
+ mod.type == 'ARRAY':
+ #{
+ can_use_cache = False
#}
- _extend( graph, obj, 1 )
+ if mod.type == 'ARMATURE': #{
+ armature = mod.object
+ rig_weight_groups = \
+ ['0 [ROOT]']+[_.name for _ in sr_armature_bones(mod.object)]
+ armature_id = sr_compile.entity_ids[armature.name]
+ POSE_OR_REST_CACHE = armature.data.pose_position
+ armature.data.pose_position = 'REST'
+ #}
#}
-#}
-
-# Kind of a useless thing i made but it looks cool and adds complexity!!1
-#
-def encoder_graph_iterator( root ):
-#{
- for c in root['children']:
- #{
- yield c
- yield from encoder_graph_iterator(c)
- #}
-#}
-
-
-# Push a vertex into the model file, or return a cached index (c_uint32)
-#
-def encoder_vertex_push( vertex_reference, co,norm,uv,colour,groups,weights ):
-#{
- global g_encoder
- buffer = g_encoder['data']['vertex']
-
- TOLERENCE = 4
- m = float(10**TOLERENCE)
-
- # Would be nice to know if this can be done faster than it currently runs,
- # its quite slow.
- #
- key = (int(co[0]*m+0.5),
- int(co[1]*m+0.5),
- int(co[2]*m+0.5),
- int(norm[0]*m+0.5),
- int(norm[1]*m+0.5),
- int(norm[2]*m+0.5),
- int(uv[0]*m+0.5),
- int(uv[1]*m+0.5),
- colour[0], # these guys are already quantized
- colour[1], # .
- colour[2], # .
- colour[3], # .
- weights[0], # v
- weights[1],
- weights[2],
- weights[3],
- groups[0],
- groups[1],
- groups[2],
- groups[3])
-
- if key in vertex_reference:
- return vertex_reference[key]
- else:
- #{
- index = c_uint32( len(vertex_reference) )
- vertex_reference[key] = index
-
- v = mdl_vert()
- v.co[0] = co[0]
- v.co[1] = co[2]
- v.co[2] = -co[1]
- v.norm[0] = norm[0]
- v.norm[1] = norm[2]
- v.norm[2] = -norm[1]
- v.uv[0] = uv[0]
- v.uv[1] = uv[1]
- v.colour[0] = colour[0]
- v.colour[1] = colour[1]
- v.colour[2] = colour[2]
- v.colour[3] = colour[3]
- v.weights[0] = weights[0]
- v.weights[1] = weights[1]
- v.weights[2] = weights[2]
- v.weights[3] = weights[3]
- v.groups[0] = groups[0]
- v.groups[1] = groups[1]
- v.groups[2] = groups[2]
- v.groups[3] = groups[3]
-
- buffer += [v]
- return index
- #}
-#}
-
-
-# Compile a mesh (or use one from the cache) onto node, based on node_def
-# No return value
-#
-def encoder_compile_mesh( node, node_def ):
-#{
- global g_encoder
-
- graph = g_encoder['scene_graph']
- graph_lookup = g_encoder['graph_lookup']
- mesh_cache = g_encoder['mesh_cache']
- obj = node_def['obj']
- armature_def = None
- can_use_cache = True
-
- # Check for modifiers that typically change the data per-instance
- # there is no well defined rule for the choices here, its just what i've
- # needed while producing the game.
- #
- # It may be possible to detect these cases automatically.
- #
- for mod in obj.modifiers:
- #{
- if mod.type == 'DATA_TRANSFER' or mod.type == 'SHRINKWRAP' or \
- mod.type == 'BOOLEAN' or mod.type == 'CURVE' or \
- mod.type == 'ARRAY':
- #{
- can_use_cache = False
- #}
-
- if mod.type == 'ARMATURE':
- armature_def = graph_lookup[mod.object]
-
- # Check the cache first
- #
- if can_use_cache and (obj.data.name in mesh_cache):
- #{
- ref = mesh_cache[obj.data.name]
- node.submesh_start = ref.submesh_start
- node.submesh_count = ref.submesh_count
- return
- #}
+ # Check the cache first
+ #
+ if can_use_cache and (obj.data.name in sr_compile.mesh_cache):#{
+ ref = sr_compile.mesh_cache[obj.data.name]
+ submesh_start = ref[0]
+ submesh_count = ref[1]
+ return (submesh_start,submesh_count,armature_id)
+ #}
# Compile a whole new mesh
#
- node.submesh_start = len( g_encoder['data']['submesh'] )
- node.submesh_count = 0
+ submesh_start = len(sr_compile.submesh_data)//sizeof(mdl_submesh)
+ submesh_count = 0
dgraph = bpy.context.evaluated_depsgraph_get()
data = obj.evaluated_get(dgraph).data
# Mesh is split into submeshes based on their material
#
mat_list = data.materials if len(data.materials) > 0 else [None]
- for material_id, mat in enumerate(mat_list):
- #{
+ for material_id, mat in enumerate(mat_list): #{
mref = {}
sm = mdl_submesh()
- sm.indice_start = len( g_encoder['data']['indice'] )
- sm.vertex_start = len( g_encoder['data']['vertex'] )
+ sm.indice_start = len(sr_compile.indice_data)//sizeof(c_uint32)
+ sm.vertex_start = len(sr_compile.vertex_data)//sizeof(mdl_vert)
sm.vertex_count = 0
sm.indice_count = 0
- sm.material_id = encoder_process_material( mat )
+ sm.material_id = sr_compile_material( mat )
- for i in range(3):
- #{
- sm.bbx[0][i] = 999999
- sm.bbx[1][i] = -999999
+ INF=99999999.99999999
+ for i in range(3):#{
+ sm.bbx[0][i] = INF
+ sm.bbx[1][i] = -INF
#}
# Keep a reference to very very very similar vertices
+ # i have no idea how to speed it up.
#
vertex_reference = {}
# Write the vertex / indice data
#
- for tri_index, tri in enumerate(data.loop_triangles):
- #{
- if tri.material_index != material_id:
- continue
+ for tri_index, tri in enumerate(data.loop_triangles):#{
+ if tri.material_index != material_id: continue
- for j in range(3):
- #{
+ for j in range(3):#{
vert = data.vertices[tri.vertices[j]]
li = tri.loops[j]
vi = data.loops[li].vertex_index
# Vertex Colours
#
- if data.vertex_colors:
- #{
+ if data.vertex_colors:#{
colour = data.vertex_colors.active.data[li].color
colour = (int(colour[0]*255.0),\
int(colour[1]*255.0),\
#}
# Weight groups: truncates to the 3 with the most influence. The
- # fourth bone ID is never used by the shader so it is
- # always 0
+ # fourth bone ID is never used by the shader so it
+ # is always 0
#
- if armature_def:
- #{
+ if armature:#{
src_groups = [_ for _ in data.vertices[vi].groups \
if obj.vertex_groups[_.group].name in \
- armature_def['bones']]
+ rig_weight_groups ]
weight_groups = sorted( src_groups, key = \
lambda a: a.weight, reverse=True )
tot = 0.0
- for ml in range(3):
- #{
- if len(weight_groups) > ml:
- #{
+ for ml in range(3):#{
+ if len(weight_groups) > ml:#{
g = weight_groups[ml]
name = obj.vertex_groups[g.group].name
weight = g.weight
-
weights[ml] = weight
- groups[ml] = armature_def['bones'].index(name)
+ groups[ml] = rig_weight_groups.index(name)
tot += weight
#}
#}
- if len(weight_groups) > 0:
- #{
+ if len(weight_groups) > 0:#{
inv_norm = (1.0/tot) * 65535.0
- for ml in range(3):
- #{
+ for ml in range(3):#{
weights[ml] = int( weights[ml] * inv_norm )
weights[ml] = min( weights[ml], 65535 )
weights[ml] = max( weights[ml], 0 )
#}
#}
#}
- else:
- #{
+ else:#{
li1 = tri.loops[(j+1)%3]
vi1 = data.loops[li1].vertex_index
e0 = data.edges[ data.loops[li].edge_index ]
weights[0] = 1
#}
#}
+
+ TOLERENCE = float(10**4)
+ key = (int(co[0]*TOLERENCE+0.5),
+ int(co[1]*TOLERENCE+0.5),
+ int(co[2]*TOLERENCE+0.5),
+ int(norm[0]*TOLERENCE+0.5),
+ int(norm[1]*TOLERENCE+0.5),
+ int(norm[2]*TOLERENCE+0.5),
+ int(uv[0]*TOLERENCE+0.5),
+ int(uv[1]*TOLERENCE+0.5),
+ colour[0], # these guys are already quantized
+ colour[1], # .
+ colour[2], # .
+ colour[3], # .
+ weights[0], # v
+ weights[1],
+ weights[2],
+ weights[3],
+ groups[0],
+ groups[1],
+ groups[2],
+ groups[3])
+
+ if key in vertex_reference:
+ index = vertex_reference[key]
+ else:#{
+ index = bytearray(c_uint32(sm.vertex_count))
+ sm.vertex_count+=1
+
+ vertex_reference[key] = index
+ v = mdl_vert()
+ v.co[0] = co[0]
+ v.co[1] = co[2]
+ v.co[2] = -co[1]
+ v.norm[0] = norm[0]
+ v.norm[1] = norm[2]
+ v.norm[2] = -norm[1]
+ v.uv[0] = uv[0]
+ v.uv[1] = uv[1]
+ v.colour[0] = colour[0]
+ v.colour[1] = colour[1]
+ v.colour[2] = colour[2]
+ v.colour[3] = colour[3]
+ v.weights[0] = weights[0]
+ v.weights[1] = weights[1]
+ v.weights[2] = weights[2]
+ v.weights[3] = weights[3]
+ v.groups[0] = groups[0]
+ v.groups[1] = groups[1]
+ v.groups[2] = groups[2]
+ v.groups[3] = groups[3]
+
+ for i in range(3):#{
+ sm.bbx[0][i] = min( sm.bbx[0][i], v.co[i] )
+ sm.bbx[1][i] = max( sm.bbx[1][i], v.co[i] )
+ #}
+
+ sr_compile.vertex_data.extend(bytearray(v))
+ #}
- # Add vertex and expand bound box
- #
- index = encoder_vertex_push( vertex_reference, co, \
- norm, \
- uv, \
- colour, \
- groups, \
- weights )
- g_encoder['data']['indice'] += [index]
+ sm.indice_count += 1
+ sr_compile.indice_data.extend( index )
#}
#}
- # How many unique verts did we add in total
- #
- sm.vertex_count = len(g_encoder['data']['vertex']) - sm.vertex_start
- sm.indice_count = len(g_encoder['data']['indice']) - sm.indice_start
-
# Make sure bounding box isn't -inf -> inf if no vertices
#
if sm.vertex_count == 0:
for j in range(2):
for i in range(3):
sm.bbx[j][i] = 0
- else:
- #{
- for j in range(sm.vertex_count):
- #{
- vert = g_encoder['data']['vertex'][ sm.vertex_start + j ]
- for i in range(3):
- #{
- sm.bbx[0][i] = min( sm.bbx[0][i], vert.co[i] )
- sm.bbx[1][i] = max( sm.bbx[1][i], vert.co[i] )
- #}
- #}
- #}
-
# Add submesh to encoder
#
- g_encoder['data']['submesh'] += [sm]
- node.submesh_count += 1
+ sr_compile.submesh_data.extend( bytearray(sm) )
+ submesh_count += 1
+ #}
+ if armature:#{
+ armature.data.pose_position = POSE_OR_REST_CACHE
#}
- # Save a reference to this node since we want to reuse the submesh indices
+ # Save a reference to this mesh since we want to reuse the submesh indices
# later.
- g_encoder['mesh_cache'][obj.data.name] = node
+ sr_compile.mesh_cache[obj.data.name]=(submesh_start,submesh_count)
+ return (submesh_start,submesh_count,armature_id)
#}
-
-def encoder_compile_ent_as( name, node, node_def ):
+def sr_compile_mesh( obj ):
#{
- global g_encoder
+ node=mdl_mesh()
+ compile_obj_transform(obj, node.transform)
+ node.pstr_name = sr_compile_string(obj.name)
+ ent_type = obj_ent_type( obj )
- if name == 'classtype_none':
- #{
- node.offset = 0
- node.classtype = 0
- return
- #}
- elif name not in globals():
- #{
- print( "Classtype '" +name + "' is unknown!" )
- return
+ node.entity_id = 0
+
+ if ent_type != 'none':#{
+ ent_id_lwr = sr_compile.entity_ids[obj.name]
+ ent_id_upr = get_entity_enum_id( obj_ent_type(obj) )
+ node.entity_id = (ent_id_upr << 16) | ent_id_lwr
#}
- buffer = g_encoder['data']['entdata']
- node.offset = len(buffer)
+ node.submesh_start, node.submesh_count, node.armature_id = \
+ sr_compile_mesh_internal( obj )
- cl = globals()[ name ]
- inst = cl()
- inst.encode_obj( node, node_def )
+ sr_compile.mesh_data.extend(bytearray(node))
+#}
+
+def sr_compile_fonts( collection ):
+#{
+ print( F"[SR] Compiling fonts" )
+
+ glyph_count = 0
+ variant_count = 0
+
+ for obj in collection.all_objects:#{
+ if obj_ent_type(obj) != 'ent_font': continue
+
+ data = obj.SR_data.ent_font[0]
+
+ font=ent_font()
+ font.alias = sr_compile_string( data.alias )
+ font.variant_start = variant_count
+ font.variant_count = 0
+ font.glyph_start = glyph_count
+
+ glyph_base = data.glyphs[0].utf32
+ glyph_range = data.glyphs[-1].utf32 - glyph_base
+
+ font.glyph_utf32_base = glyph_base
+ font.glyph_count = glyph_range
+
+ for i in range(len(data.variants)):#{
+ data_var = data.variants[i]
+ if not data_var.mesh: continue
+
+ mesh = data_var.mesh.data
+
+ variant = ent_font_variant()
+ variant.name = sr_compile_string( data_var.tipo )
+
+ # fonts (variants) only support one material each
+ mat = None
+ if len(mesh.materials) != 0:
+ mat = mesh.materials[0]
+ variant.material_id = sr_compile_material( mat )
+
+ font.variant_count += 1
+
+ islands = mesh_utils.mesh_linked_triangles(mesh)
+ centroids = [Vector((0,0)) for _ in range(len(islands))]
+
+ for j in range(len(islands)):#{
+ for tri in islands[j]:#{
+ centroids[j].x += tri.center[0]
+ centroids[j].y += tri.center[2]
+ #}
+
+ centroids[j] /= len(islands[j])
+ #}
+
+ for j in range(glyph_range):#{
+ data_glyph = data.glyphs[j]
+ glyph = ent_glyph()
+ glyph.indice_start = len(sr_compile.indice_data)//sizeof(c_uint32)
+ glyph.indice_count = 0
+ glyph.size[0] = data_glyph.bounds[2]
+ glyph.size[1] = data_glyph.bounds[3]
+
+ vertex_reference = {}
+
+ for k in range(len(islands)):#{
+ if centroids[k].x < data_glyph.bounds[0] or \
+ centroids[k].x > data_glyph.bounds[0]+data_glyph.bounds[2] or\
+ centroids[k].y < data_glyph.bounds[1] or \
+ centroids[k].y > data_glyph.bounds[1]+data_glyph.bounds[3]:
+ #{
+ continue
+ #}
- buffer.extend( bytearray(inst) )
- bytearray_align_to( buffer, 4 )
+ for l in range(len(islands[k])):#{
+ tri = islands[k][l]
+ for m in range(3):#{
+ vert = mesh.vertices[tri.vertices[m]]
+ li = tri.loops[m]
+ vi = mesh.loops[li].vertex_index
+
+ # Gather vertex information
+ #
+ co = [vert.co[_] for _ in range(3)]
+ co[0] -= data_glyph.bounds[0]
+ co[2] -= data_glyph.bounds[1]
+ norm = mesh.loops[li].normal
+ uv = (0,0)
+ if mesh.uv_layers: uv = mesh.uv_layers.active.data[li].uv
+
+ TOLERENCE = float(10**4)
+ key = (int(co[0]*TOLERENCE+0.5),
+ int(co[1]*TOLERENCE+0.5),
+ int(co[2]*TOLERENCE+0.5),
+ int(norm[0]*TOLERENCE+0.5),
+ int(norm[1]*TOLERENCE+0.5),
+ int(norm[2]*TOLERENCE+0.5),
+ int(uv[0]*TOLERENCE+0.5),
+ int(uv[1]*TOLERENCE+0.5))
+
+ if key in vertex_reference:
+ index = vertex_reference[key]
+ else:#{
+ vindex = len(sr_compile.vertex_data)//sizeof(mdl_vert)
+ index = bytearray(c_uint32(vindex))
+ vertex_reference[key] = index
+ v = mdl_vert()
+ v.co[0] = co[0]
+ v.co[1] = co[2]
+ v.co[2] = -co[1]
+ v.norm[0] = norm[0]
+ v.norm[1] = norm[2]
+ v.norm[2] = -norm[1]
+ v.uv[0] = uv[0]
+ v.uv[1] = uv[1]
+
+ sr_compile.vertex_data.extend(bytearray(v))
+ #}
+
+ glyph.indice_count += 1
+ sr_compile.indice_data.extend( index )
+ #}
+ #}
+ #}
+ sr_ent_push( glyph )
+ #}
+ sr_ent_push( variant )
+ #}
+ sr_ent_push( font )
+ #}
#}
-# Compiles animation data into model and gives us some extra node_def entries
-#
-def encoder_compile_armature( node, node_def ):
+def sr_compile_armature( obj ):
#{
- global g_encoder
-
- entdata = g_encoder['data']['entdata']
- animdata = g_encoder['data']['anim']
- keyframedata = g_encoder['data']['keyframe']
- mesh_cache = g_encoder['mesh_cache']
- obj = node_def['obj']
- bones = node_def['bones']
-
- # extra info
- node_def['anim_start'] = len(animdata)
- node_def['anim_count'] = 0
+ node = mdl_armature()
+ node.bone_start = len(sr_compile.bone_data)//sizeof(mdl_bone)
+ node.bone_count = 0
+ node.anim_start = len(sr_compile.anim_data)//sizeof(mdl_animation)
+ node.anim_count = 0
+ bones = [_ for _ in sr_armature_bones(obj)]
+ bones_names = [None]+[_.name for _ in bones]
+
+ for b in bones:#{
+ bone = mdl_bone()
+ if b.use_deform: bone.flags = 0x1
+ if b.parent: bone.parent = bones_names.index(b.parent.name)
+
+ bone.collider = int(b.SR_data.collider)
+
+ if bone.collider>0:#{
+ bone.hitbox[0][0] = b.SR_data.collider_min[0]
+ bone.hitbox[0][1] = b.SR_data.collider_min[2]
+ bone.hitbox[0][2] = -b.SR_data.collider_max[1]
+ bone.hitbox[1][0] = b.SR_data.collider_max[0]
+ bone.hitbox[1][1] = b.SR_data.collider_max[2]
+ bone.hitbox[1][2] = -b.SR_data.collider_min[1]
+ #}
+
+ if b.SR_data.cone_constraint:#{
+ bone.flags |= 0x4
+ bone.conevx[0] = b.SR_data.conevx[0]
+ bone.conevx[1] = b.SR_data.conevx[2]
+ bone.conevx[2] = -b.SR_data.conevx[1]
+ bone.conevy[0] = b.SR_data.conevy[0]
+ bone.conevy[1] = b.SR_data.conevy[2]
+ bone.conevy[2] = -b.SR_data.conevy[1]
+ bone.coneva[0] = b.SR_data.coneva[0]
+ bone.coneva[1] = b.SR_data.coneva[2]
+ bone.coneva[2] = -b.SR_data.coneva[1]
+ bone.conet = b.SR_data.conet
+ #}
+
+ bone.co[0] = b.head_local[0]
+ bone.co[1] = b.head_local[2]
+ bone.co[2] = -b.head_local[1]
+ bone.end[0] = b.tail_local[0] - bone.co[0]
+ bone.end[1] = b.tail_local[2] - bone.co[1]
+ bone.end[2] = -b.tail_local[1] - bone.co[2]
+ bone.pstr_name = sr_compile_string( b.name )
+
+ for c in obj.pose.bones[b.name].constraints:#{
+ if c.type == 'IK':#{
+ bone.flags |= 0x2
+ bone.ik_target = bones_names.index(c.subtarget)
+ bone.ik_pole = bones_names.index(c.pole_subtarget)
+ #}
+ #}
+
+ node.bone_count += 1
+ sr_compile.bone_data.extend(bytearray(bone))
+ #}
+
# Compile anims
#
- if obj.animation_data:
- #{
+ if obj.animation_data and sr_compile.pack_animations: #{
# So we can restore later
#
previous_frame = bpy.context.scene.frame_current
POSE_OR_REST_CACHE = obj.data.pose_position
obj.data.pose_position = 'POSE'
- for NLALayer in obj.animation_data.nla_tracks:
- #{
- for NLAStrip in NLALayer.strips:
- #{
+ for NLALayer in obj.animation_data.nla_tracks:#{
+ for NLAStrip in NLALayer.strips:#{
# set active
#
- for a in bpy.data.actions:
- #{
- if a.name == NLAStrip.name:
- #{
+ for a in bpy.data.actions:#{
+ if a.name == NLAStrip.name:#{
obj.animation_data.action = a
break
#}
# Export strips
#
anim = mdl_animation()
- anim.pstr_name = encoder_process_pstr( NLAStrip.action.name )
+ anim.pstr_name = sr_compile_string( NLAStrip.action.name )
anim.rate = 30.0
- anim.offset = len(keyframedata)
+ anim.keyframe_start = len(sr_compile.keyframe_data)//\
+ sizeof(mdl_transform)
anim.length = anim_end-anim_start
+ i = 0
# Export the keyframes
- for frame in range(anim_start,anim_end):
- #{
+ for frame in range(anim_start,anim_end):#{
bpy.context.scene.frame_set(frame)
- for bone_name in bones:
- #{
- for pb in obj.pose.bones:
- #{
- if pb.name != bone_name: continue
-
- rb = obj.data.bones[ bone_name ]
-
- # relative bone matrix
- if rb.parent is not None:
- #{
- offset_mtx = rb.parent.matrix_local
- offset_mtx = offset_mtx.inverted_safe() @ \
- rb.matrix_local
-
- inv_parent = pb.parent.matrix @ offset_mtx
- inv_parent.invert_safe()
- fpm = inv_parent @ pb.matrix
- #}
- else:
- #{
- bone_mtx = rb.matrix.to_4x4()
- local_inv = rb.matrix_local.inverted_safe()
- fpm = bone_mtx @ local_inv @ pb.matrix
- #}
-
- loc, rot, sca = fpm.decompose()
-
- # local position
- final_pos = Vector(( loc[0], loc[2], -loc[1] ))
-
- # rotation
- lc_m = pb.matrix_channel.to_3x3()
- if pb.parent is not None:
- #{
- smtx = pb.parent.matrix_channel.to_3x3()
- lc_m = smtx.inverted() @ lc_m
- #}
- rq = lc_m.to_quaternion()
-
- kf = mdl_keyframe()
- kf.co[0] = final_pos[0]
- kf.co[1] = final_pos[1]
- kf.co[2] = final_pos[2]
-
- kf.q[0] = rq[1]
- kf.q[1] = rq[3]
- kf.q[2] = -rq[2]
- kf.q[3] = rq[0]
-
- # scale
- kf.s[0] = sca[0]
- kf.s[1] = sca[2]
- kf.s[2] = sca[1]
+ for rb in bones:#{
+ pb = obj.pose.bones[rb.name]
+
+ # relative bone matrix
+ if rb.parent is not None:#{
+ offset_mtx = rb.parent.matrix_local
+ offset_mtx = offset_mtx.inverted_safe() @ \
+ rb.matrix_local
+
+ inv_parent = pb.parent.matrix @ offset_mtx
+ inv_parent.invert_safe()
+ fpm = inv_parent @ pb.matrix
+ #}
+ else:#{
+ bone_mtx = rb.matrix.to_4x4()
+ local_inv = rb.matrix_local.inverted_safe()
+ fpm = bone_mtx @ local_inv @ pb.matrix
+ #}
- keyframedata += [kf]
- break
+ loc, rot, sca = fpm.decompose()
+
+ # rotation
+ lc_m = pb.matrix_channel.to_3x3()
+ if pb.parent is not None:#{
+ smtx = pb.parent.matrix_channel.to_3x3()
+ lc_m = smtx.inverted() @ lc_m
#}
+ rq = lc_m.to_quaternion()
+
+ kf = mdl_transform()
+ kf.co[0] = loc[0]
+ kf.co[1] = loc[2]
+ kf.co[2] = -loc[1]
+ kf.q[0] = rq[1]
+ kf.q[1] = rq[3]
+ kf.q[2] = -rq[2]
+ kf.q[3] = rq[0]
+ kf.s[0] = sca[0]
+ kf.s[1] = sca[1]
+ kf.s[2] = sca[2]
+ sr_compile.keyframe_data.extend(bytearray(kf))
+
+ i+=1
#}
#}
# Add to animation buffer
#
- animdata += [anim]
- node_def['anim_count'] += 1
+ sr_compile.anim_data.extend(bytearray(anim))
+ node.anim_count += 1
# Report progress
#
- status_name = F" " + " |"*(node_def['depth']-1)
- print( F"{status_name} | *anim: {NLAStrip.action.name}" )
+ print( F"[SR] | anim( {NLAStrip.action.name} )" )
#}
#}
obj.animation_data.action = previous_action
obj.data.pose_position = POSE_OR_REST_CACHE
#}
+
+ sr_compile.armature_data.extend(bytearray(node))
+#}
+
+def sr_ent_push( struct ):
+#{
+ clase = type(struct).__name__
+
+ if clase not in sr_compile.entity_data:#{
+ sr_compile.entity_data[ clase ] = bytearray()
+ sr_compile.entity_info[ clase ] = { 'size': sizeof(struct) }
+ #}
+
+ index = len(sr_compile.entity_data[ clase ])//sizeof(struct)
+ sr_compile.entity_data[ clase ].extend( bytearray(struct) )
+ return index
+#}
+
+def sr_array_title( arr, name, count, size, offset ):
+#{
+ for i in range(len(name)):#{
+ arr.name[i] = ord(name[i])
+ #}
+ arr.file_offset = offset
+ arr.item_count = count
+ arr.item_size = size
+#}
+
+def sr_compile( collection ):
+#{
+ print( F"[SR] compiler begin ({collection.name}.mdl)" )
+
+ #settings
+ sr_compile.pack_textures = collection.SR_data.pack_textures
+ sr_compile.pack_animations = collection.SR_data.animations
+
+ # caches
+ sr_compile.string_cache = {}
+ sr_compile.mesh_cache = {}
+ sr_compile.material_cache = {}
+ sr_compile.texture_cache = {}
+
+ # compiled data
+ sr_compile.mesh_data = bytearray()
+ sr_compile.submesh_data = bytearray()
+ sr_compile.vertex_data = bytearray()
+ sr_compile.indice_data = bytearray()
+ sr_compile.bone_data = bytearray()
+ sr_compile.material_data = bytearray()
+ sr_compile.armature_data = bytearray()
+ sr_compile.anim_data = bytearray()
+ sr_compile.keyframe_data = bytearray()
+ sr_compile.texture_data = bytearray()
+
+ # just bytes not structures
+ sr_compile.string_data = bytearray()
+ sr_compile.pack_data = bytearray()
+
+ # variable
+ sr_compile.entity_data = {}
+ sr_compile.entity_info = {}
+
+ print( F"[SR] assign entity ID's" )
+ sr_compile.entities = {}
+ sr_compile.entity_ids = {}
+
+ mesh_count = 0
+ for obj in collection.all_objects: #{
+ if obj.type == 'MESH':#{
+ mesh_count += 1
+ #}
+
+ ent_type = obj_ent_type( obj )
+ if ent_type == 'none': continue
+
+ if ent_type not in sr_compile.entities: sr_compile.entities[ent_type] = []
+ sr_compile.entity_ids[obj.name] = len( sr_compile.entities[ent_type] )
+ sr_compile.entities[ent_type] += [obj]
+ #}
+
+ print( F"[SR] Compiling geometry" )
+ i=0
+ for obj in collection.all_objects:#{
+ if obj.type == 'MESH':#{
+ i+=1
+
+ ent_type = obj_ent_type( obj )
+
+ # entity ignore mesh list
+ #
+ if ent_type == 'ent_traffic': continue
+ if ent_type == 'ent_font': continue
+ if ent_type == 'ent_font_variant': continue
+ #--------------------------
+
+ print( F'[SR] {i: 3}/{mesh_count} {obj.name:<40}', end='\r' )
+ sr_compile_mesh( obj )
+ #}
+ #}
+
+ audio_clip_count = 0
+
+ for ent_type, arr in sr_compile.entities.items():#{
+ print(F"[SR] Compiling {len(arr)} {ent_type}{'s' if len(arr)>1 else ''}")
+
+ for i in range(len(arr)):#{
+ obj = arr[i]
+
+ print( F"[SR] {i+1: 3}/{len(arr)} {obj.name:<40} ",end='\r' )
+
+ if ent_type == 'mdl_armature': sr_compile_armature(obj)
+ elif ent_type == 'ent_light': #{
+ light = ent_light()
+ compile_obj_transform( obj, light.transform )
+ light.daytime = obj.data.SR_data.daytime
+ if obj.data.type == 'POINT':#{
+ light.type = 0
+ #}
+ elif obj.data.type == 'SPOT':#{
+ light.type = 1
+ light.angle = obj.data.spot_size*0.5
+ #}
+ light.range = obj.data.cutoff_distance
+ light.colour[0] = obj.data.color[0]
+ light.colour[1] = obj.data.color[1]
+ light.colour[2] = obj.data.color[2]
+ light.colour[3] = obj.data.energy
+ sr_ent_push( light )
+ #}
+ elif ent_type == 'ent_camera': #{
+ cam = ent_camera()
+ compile_obj_transform( obj, cam.transform )
+ cam.fov = obj.data.angle
+ sr_ent_push(cam)
+ #}
+ elif ent_type == 'ent_gate': #{
+ gate = ent_gate()
+ obj_data = obj.SR_data.ent_gate[0]
+ mesh_data = obj.data.SR_data.ent_gate[0]
+
+ if obj_data.tipo == 'default':#{
+ if obj_data.target:#{
+ gate.target = sr_compile.entity_ids[obj_data.target.name]
+ gate.type = 1
+ #}
+ #}
+ elif obj_data.tipo == 'nonlocal':#{
+ gate.target = sr_compile_string(obj_data.key)
+ gate.type = 2
+ #}
+ else: gate.type = 0
+
+ gate.dimensions[0] = mesh_data.dimensions[0]
+ gate.dimensions[1] = mesh_data.dimensions[1]
+ gate.dimensions[2] = mesh_data.dimensions[2]
+
+ q = [obj.matrix_local.to_quaternion(), (0,0,0,1)]
+ co = [obj.matrix_world @ Vector((0,0,0)), (0,0,0)]
+
+ if obj_data.target:#{
+ q[1] = obj_data.target.matrix_local.to_quaternion()
+ co[1]= obj_data.target.matrix_world @ Vector((0,0,0))
+ #}
+
+ # Setup transform
+ #
+ for x in range(2):#{
+ gate.co[x][0] = co[x][0]
+ gate.co[x][1] = co[x][2]
+ gate.co[x][2] = -co[x][1]
+ gate.q[x][0] = q[x][1]
+ gate.q[x][1] = q[x][3]
+ gate.q[x][2] = -q[x][2]
+ gate.q[x][3] = q[x][0]
+ #}
+
+ sr_ent_push( gate )
+ #}
+ elif ent_type == 'ent_spawn': #{
+ spawn = ent_spawn()
+ compile_obj_transform( obj, spawn.transform )
+ obj_data = obj.SR_data.ent_spawn[0]
+ spawn.pstr_name = sr_compile_string( obj_data.alias )
+ sr_ent_push( spawn )
+ #}
+ elif ent_type == 'ent_water':#{
+ water = ent_water()
+ compile_obj_transform( obj, water.transform )
+ water.max_dist = 0.0
+ sr_ent_push( water )
+ #}
+ elif ent_type == 'ent_audio':#{
+ obj_data = obj.SR_data.ent_audio[0]
+ audio = ent_audio()
+ compile_obj_transform( obj, audio.transform )
+ audio.clip_start = audio_clip_count
+ audio.clip_count = len(obj_data.files)
+ audio_clip_count += audio.clip_count
+ audio.max_channels = obj_data.max_channels
+ audio.volume = obj_data.volume
+
+ # TODO flags:
+ # - allow/disable doppler
+ # - channel group tags with random colours
+ # - transition properties
+
+ if obj_data.flag_loop: audio.flags |= 0x1
+ if obj_data.flag_nodoppler: audio.flags |= 0x2
+ if obj_data.flag_3d: audio.flags |= 0x4
+ if obj_data.flag_auto: audio.flags |= 0x8
+ if obj_data.formato == '0': audio.flags |= 0x000
+ elif obj_data.formato == '1': audio.flags |= 0x400
+ elif obj_data.formato == '2': audio.flags |= 0x1000
+
+ audio.channel_behaviour = int(obj_data.channel_behaviour)
+ if audio.channel_behaviour >= 1:#{
+ audio.group = obj_data.group
+ #}
+ if audio.channel_behaviour == 2:#{
+ audio.crossfade = obj_data.transition_duration
+ #}
+ audio.probability_curve = int(obj_data.probability_curve)
+
+ for ci in range(audio.clip_count):#{
+ entry = obj_data.files[ci]
+ clip = ent_audio_clip()
+ clip.probability = entry.probability
+ if obj_data.formato == '2':#{
+ sr_pack_file( clip._anon.file, '', vg_str_bin(entry.path) )
+ #}
+ else:#{
+ clip._anon.file.path = sr_compile_string( entry.path )
+ clip._anon.file.pack_offset = 0
+ clip._anon.file.pack_size = 0
+ #}
+ sr_ent_push( clip )
+ #}
+ sr_ent_push( audio )
+ #}
+ elif ent_type == 'ent_volume':#{
+ obj_data = obj.SR_data.ent_volume[0]
+ volume = ent_volume()
+ volume.type = int(obj_data.subtype)
+ compile_obj_transform( obj, volume.transform )
+
+ if obj_data.target:#{
+ volume.target = sr_entity_id( obj_data.target )
+ #}
+
+ sr_ent_push(volume)
+ #}
+ elif ent_type == 'ent_marker':#{
+ marker = ent_marker()
+ marker.name = sr_compile_string( obj.SR_data.ent_marker[0].alias )
+ compile_obj_transform( obj, marker.transform )
+ sr_ent_push(marker)
+ #}
+ elif ent_type == 'ent_skateshop':#{
+ skateshop = ent_skateshop()
+ obj_data = obj.SR_data.ent_skateshop[0]
+ skateshop.id_display = sr_entity_id( obj_data.mark_display )
+ skateshop.id_info = sr_entity_id( obj_data.mark_info )
+ skateshop.id_rack = sr_entity_id( obj_data.mark_rack )
+ skateshop.id_camera = sr_entity_id( obj_data.cam )
+ compile_obj_transform( obj, skateshop.transform )
+ sr_ent_push(skateshop)
+ #}
+ #}
+ #}
+
+ sr_compile_fonts(collection)
+
+ def _children( col ):#{
+ yield col
+ for c in col.children:#{
+ yield from _children(c)
+ #}
+ #}
+
+ checkpoint_count = 0
+ pathindice_count = 0
+ routenode_count = 0
+
+ for col in _children(collection):#{
+ print( F"Adding routes for subcollection: {col.name}" )
+ route_gates = []
+ route_curves = []
+ routes = []
+ traffics = []
+
+ for obj in col.objects:#{
+ if obj.type == 'ARMATURE': pass
+ else:#{
+ ent_type = obj_ent_type( obj )
+
+ if ent_type == 'ent_gate':
+ route_gates += [obj]
+ elif ent_type == 'ent_route_node':#{
+ if obj.type == 'CURVE':#{
+ route_curves += [obj]
+ #}
+ #}
+ elif ent_type == 'ent_route':
+ routes += [obj]
+ elif ent_type == 'ent_traffic':
+ traffics += [obj]
+ #}
+ #}
+
+ dij = create_node_graph( route_curves, route_gates )
+
+ for obj in routes:#{
+ obj_data = obj.SR_data.ent_route[0]
+ route = ent_route()
+ route.pstr_name = sr_compile_string( obj_data.alias )
+ route.checkpoints_start = checkpoint_count
+ route.checkpoints_count = 0
+
+ for ci in range(3):
+ route.colour[ci] = obj_data.colour[ci]
+ route.colour[3] = 1.0
+
+ compile_obj_transform( obj, route.transform )
+ checkpoints = obj_data.gates
+
+ for i in range(len(checkpoints)):#{
+ gi = checkpoints[i].target
+ gj = checkpoints[(i+1)%len(checkpoints)].target
+ gate = gi
+
+ if gi:#{
+ dest = gi.SR_data.ent_gate[0].target
+ gi = dest
+ #}
+
+ if gi==gj: continue # error?
+ if not gi or not gj: continue
+
+ checkpoint = ent_checkpoint()
+ checkpoint.gate_index = sr_compile.entity_ids[gate.name]
+ checkpoint.path_start = pathindice_count
+ checkpoint.path_count = 0
+
+ path = solve_graph( dij, gi.name, gj.name )
+
+ if path:#{
+ for pi in range(len(path)):#{
+ pathindice = ent_path_index()
+ pathindice.index = routenode_count + path[pi]
+ sr_ent_push( pathindice )
+
+ checkpoint.path_count += 1
+ pathindice_count += 1
+ #}
+ #}
+
+ sr_ent_push( checkpoint )
+ route.checkpoints_count += 1
+ checkpoint_count += 1
+ #}
+
+ sr_ent_push( route )
+ #}
+
+ for obj in traffics:#{
+ traffic = ent_traffic()
+ compile_obj_transform( obj, traffic.transform )
+ traffic.submesh_start, traffic.submesh_count, _ = \
+ sr_compile_mesh_internal( obj )
+
+ # find best subsection
+
+ graph_keys = list(dij.graph)
+ min_dist = 100.0
+ best_point = 0
+
+ for j in range(len(dij.points)):#{
+ point = dij.points[j]
+ dist = (point-obj.location).magnitude
+
+ if dist < min_dist:#{
+ min_dist = dist
+ best_point = j
+ #}
+ #}
+
+ # scan to each edge
+ best_begin = best_point
+ best_end = best_point
+
+ while True:#{
+ map0 = dij.subsections[best_begin]
+ if map0[1] == -1: break
+ best_begin = map0[1]
+ #}
+ while True:#{
+ map1 = dij.subsections[best_end]
+ if map1[2] == -1: break
+ best_end = map1[2]
+ #}
+
+ traffic.start_node = routenode_count + best_begin
+ traffic.node_count = best_end - best_begin
+ traffic.index = best_point - best_begin
+ traffic.speed = obj.SR_data.ent_traffic[0].speed
+ traffic.t = 0.0
+
+ sr_ent_push(traffic)
+ #}
+
+ for point in dij.points:#{
+ rn = ent_route_node()
+ rn.co[0] = point[0]
+ rn.co[1] = point[2]
+ rn.co[2] = -point[1]
+ sr_ent_push( rn )
+ #}
+
+ routenode_count += len(dij.points)
+ #}
+
+ print( F"[SR] Writing file" )
+
+ file_array_instructions = {}
+ file_offset = 0
+
+ def _write_array( name, item_size, data ):#{
+ nonlocal file_array_instructions, file_offset
+
+ count = len(data)//item_size
+ file_array_instructions[name] = {'count':count, 'size':item_size,\
+ 'data':data, 'offset': file_offset}
+ file_offset += len(data)
+ file_offset = int_align_to( file_offset, 8 )
+ #}
+
+ _write_array( 'strings', 1, sr_compile.string_data )
+ _write_array( 'mdl_mesh', sizeof(mdl_mesh), sr_compile.mesh_data )
+ _write_array( 'mdl_submesh', sizeof(mdl_submesh), sr_compile.submesh_data )
+ _write_array( 'mdl_material', sizeof(mdl_material), sr_compile.material_data)
+ _write_array( 'mdl_texture', sizeof(mdl_texture), sr_compile.texture_data)
+ _write_array( 'mdl_armature', sizeof(mdl_armature), sr_compile.armature_data)
+ _write_array( 'mdl_bone', sizeof(mdl_bone), sr_compile.bone_data )
+
+ for name, buffer in sr_compile.entity_data.items():#{
+ _write_array( name, sr_compile.entity_info[name]['size'], buffer )
+ #}
+
+ _write_array( 'mdl_animation', sizeof(mdl_animation), sr_compile.anim_data)
+ _write_array( 'mdl_keyframe', sizeof(mdl_transform),sr_compile.keyframe_data)
+ _write_array( 'mdl_vert', sizeof(mdl_vert), sr_compile.vertex_data )
+ _write_array( 'mdl_indice', sizeof(c_uint32), sr_compile.indice_data )
+ _write_array( 'pack', 1, sr_compile.pack_data )
+
+ header_size = int_align_to( sizeof(mdl_header), 8 )
+ index_size = int_align_to( sizeof(mdl_array)*len(file_array_instructions),8 )
+
+ folder = bpy.path.abspath(bpy.context.scene.SR_data.export_dir)
+ path = F"{folder}{collection.name}.mdl"
+ print( path )
+
+ fp = open( path, "wb" )
+ header = mdl_header()
+ header.version = 100
+ sr_array_title( header.arrays, \
+ 'index', len(file_array_instructions), \
+ sizeof(mdl_array), header_size )
+
+ fp.write( bytearray_align_to( bytearray(header), 8 ) )
+
+ print( F'[SR] {"name":>16}| count | offset' )
+ index = bytearray()
+ for name,info in file_array_instructions.items():#{
+ arr = mdl_array()
+ offset = info['offset'] + header_size + index_size
+ sr_array_title( arr, name, info['count'], info['size'], offset )
+ index.extend( bytearray(arr) )
+
+ print( F'[SR] {name:>16}| {info["count"]: 8} '+\
+ F' 0x{info["offset"]:02x}' )
+ #}
+ fp.write( bytearray_align_to( index, 8 ) )
+ #bytearray_print_hex( index )
+
+ for name,info in file_array_instructions.items():#{
+ fp.write( bytearray_align_to( info['data'], 8 ) )
+ #}
+
+ fp.close()
+
+ print( '[SR] done' )
+#}
+
+class SR_SCENE_SETTINGS(bpy.types.PropertyGroup):
+#{
+ use_hidden: bpy.props.BoolProperty( name="use hidden", default=False )
+ export_dir: bpy.props.StringProperty( name="Export Dir", subtype='DIR_PATH' )
+ gizmos: bpy.props.BoolProperty( name="Draw Gizmos", default=True )
+
+ panel: bpy.props.EnumProperty(
+ name='Panel',
+ description='',
+ items=[
+ ('EXPORT', 'Export', '', 'MOD_BUILD',0),
+ ('ENTITY', 'Entity', '', 'MONKEY',1),
+ ('SETTINGS', 'Settings', 'Settings', 'PREFERENCES',2),
+ ],
+ )
+#}
+
+class SR_COLLECTION_SETTINGS(bpy.types.PropertyGroup):
+#{
+ pack_textures: bpy.props.BoolProperty( name="Pack Textures", default=False )
+ animations: bpy.props.BoolProperty( name="Export animation", default=True)
+#}
+
+def sr_get_mirror_bone( bones ):
+#{
+ side = bones.active.name[-1:]
+ other_name = bones.active.name[:-1]
+ if side == 'L': other_name += 'R'
+ elif side == 'R': other_name += 'L'
+ else: return None
+
+ for b in bones:#{
+ if b.name == other_name:
+ return b
+ #}
+
+ return None
+#}
+
+class SR_MIRROR_BONE_X(bpy.types.Operator):
+#{
+ bl_idname="skaterift.mirror_bone"
+ bl_label="Mirror bone attributes - SkateRift"
+
+ def execute(_,context):
+ #{
+ active_object = context.active_object
+ bones = active_object.data.bones
+ a = bones.active
+ b = sr_get_mirror_bone( bones )
+
+ if not b: return {'FINISHED'}
+
+ b.SR_data.collider = a.SR_data.collider
+
+ def _v3copyflipy( a, b ):#{
+ b[0] = a[0]
+ b[1] = -a[1]
+ b[2] = a[2]
+ #}
+
+ _v3copyflipy( a.SR_data.collider_min, b.SR_data.collider_min )
+ _v3copyflipy( a.SR_data.collider_max, b.SR_data.collider_max )
+ b.SR_data.collider_min[1] = -a.SR_data.collider_max[1]
+ b.SR_data.collider_max[1] = -a.SR_data.collider_min[1]
+
+ b.SR_data.cone_constraint = a.SR_data.cone_constraint
+
+ _v3copyflipy( a.SR_data.conevx, b.SR_data.conevy )
+ _v3copyflipy( a.SR_data.conevy, b.SR_data.conevx )
+ _v3copyflipy( a.SR_data.coneva, b.SR_data.coneva )
+
+ b.SR_data.conet = a.SR_data.conet
+
+ # redraw
+ ob = bpy.context.scene.objects[0]
+ ob.hide_render = ob.hide_render
+ return {'FINISHED'}
+ #}
+#}
+
+class SR_COMPILE(bpy.types.Operator):
+#{
+ bl_idname="skaterift.compile_all"
+ bl_label="Compile All"
+
+ def execute(_,context):
+ #{
+ view_layer = bpy.context.view_layer
+ for col in view_layer.layer_collection.children["export"].children:
+ if not col.hide_viewport or bpy.context.scene.SR_data.use_hidden:
+ sr_compile( bpy.data.collections[col.name] )
+
+ return {'FINISHED'}
+ #}
+#}
+
+class SR_COMPILE_THIS(bpy.types.Operator):
+#{
+ bl_idname="skaterift.compile_this"
+ bl_label="Compile This collection"
+
+ def execute(_,context):
+ #{
+ col = bpy.context.collection
+ sr_compile( col )
+
+ return {'FINISHED'}
+ #}
+#}
+
+class SR_INTERFACE(bpy.types.Panel):
+#{
+ bl_idname = "VIEW3D_PT_skate_rift"
+ bl_label = "Skate Rift"
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'UI'
+ bl_category = "Skate Rift"
+
+ def draw(_, context):
+ #{
+ # Compiler section
+
+ row = _.layout.row()
+ row.scale_y = 1.75
+ row.prop( context.scene.SR_data, 'panel', expand=True )
+
+ if context.scene.SR_data.panel == 'SETTINGS': #{
+ _.layout.prop( context.scene.SR_data, 'gizmos' )
+ #}
+ elif context.scene.SR_data.panel == 'EXPORT': #{
+ _.layout.prop( context.scene.SR_data, "export_dir" )
+ col = bpy.context.collection
+
+ found_in_export = False
+ export_count = 0
+ view_layer = bpy.context.view_layer
+ for c1 in view_layer.layer_collection.children["export"].children: #{
+ if not c1.hide_viewport or bpy.context.scene.SR_data.use_hidden:
+ export_count += 1
+
+ if c1.name == col.name: #{
+ found_in_export = True
+ #}
+ #}
+
+ box = _.layout.box()
+ row = box.row()
+ row.alignment = 'CENTER'
+ row.scale_y = 1.5
+
+ if found_in_export: #{
+ row.label( text=col.name + ".mdl" )
+ box.prop( col.SR_data, "pack_textures" )
+ box.prop( col.SR_data, "animations" )
+ box.operator( "skaterift.compile_this" )
+ #}
+ else: #{
+ row.enabled=False
+ row.label( text=col.name )
+
+ row = box.row()
+ row.enabled=False
+ row.alignment = 'CENTER'
+ row.scale_y = 1.5
+ row.label( text="This collection is not in the export group" )
+ #}
+
+ box = _.layout.box()
+ row = box.row()
+
+ split = row.split( factor=0.3, align=True )
+ split.prop( context.scene.SR_data, "use_hidden", text="hidden" )
+
+ row1 = split.row()
+ if export_count == 0:
+ row1.enabled=False
+ row1.operator( "skaterift.compile_all", \
+ text=F"Compile all ({export_count} collections)" )
+ #}
+ elif context.scene.SR_data.panel == 'ENTITY': #{
+ active_object = context.active_object
+ if not active_object: return
+
+ _.layout.operator( 'skaterift.copy_entity_data', \
+ text=F'Copy entity data to {len(context.selected_objects)-1} '+\
+ F'other objects' )
+
+ box = _.layout.box()
+ row = box.row()
+ row.alignment = 'CENTER'
+ row.label( text=active_object.name )
+ row.scale_y = 1.5
+
+ def _draw_prop_collection( data ): #{
+ nonlocal box
+ row = box.row()
+ row.alignment = 'CENTER'
+ row.enabled = False
+ row.scale_y = 1.5
+ row.label( text=F'{data[0]}' )
+
+ if hasattr(type(data[0]),'sr_inspector'):#{
+ type(data[0]).sr_inspector( box, data )
+ #}
+ else:#{
+ for a in data[0].__annotations__:
+ box.prop( data[0], a )
+ #}
+ #}
+
+ if active_object.type == 'ARMATURE': #{
+ if active_object.mode == 'POSE': #{
+ bones = active_object.data.bones
+ mb = sr_get_mirror_bone( bones )
+ if mb:#{
+ box.operator( "skaterift.mirror_bone", \
+ text=F'Mirror attributes to {mb.name}' )
+ #}
+
+ _draw_prop_collection( [bones.active.SR_data ] )
+ #}
+ else: #{
+ row = box.row()
+ row.alignment='CENTER'
+ row.scale_y=2.0
+ row.enabled=False
+ row.label( text="Enter pose mode to modify bone properties" )
+ #}
+ #}
+ elif active_object.type == 'LIGHT': #{
+ _draw_prop_collection( [active_object.data.SR_data] )
+ #}
+ elif active_object.type in ['EMPTY','CURVE','MESH']:#{
+ box.prop( active_object.SR_data, "ent_type" )
+ ent_type = active_object.SR_data.ent_type
+
+ col = getattr( active_object.SR_data, ent_type, None )
+ if col != None and len(col)!=0: _draw_prop_collection( col )
+
+ if active_object.type == 'MESH':#{
+ col = getattr( active_object.data.SR_data, ent_type, None )
+ if col != None and len(col)!=0: _draw_prop_collection( col )
+ #}
+ #}
+ #}
+ #}
+#}
+
+class SR_MATERIAL_PANEL(bpy.types.Panel):
+#{
+ bl_label="Skate Rift material"
+ bl_idname="MATERIAL_PT_sr_material"
+ bl_space_type='PROPERTIES'
+ bl_region_type='WINDOW'
+ bl_context="material"
+
+ def draw(_,context):
+ #{
+ active_object = bpy.context.active_object
+ if active_object == None: return
+ active_mat = active_object.active_material
+ if active_mat == None: return
+
+ info = material_info( active_mat )
+
+ if 'tex_diffuse' in info:#{
+ _.layout.label( icon='INFO', \
+ text=F"{info['tex_diffuse'].name} will be compiled" )
+ #}
+
+ _.layout.prop( active_mat.SR_data, "shader" )
+ _.layout.prop( active_mat.SR_data, "surface_prop" )
+ _.layout.prop( active_mat.SR_data, "collision" )
+
+ if active_mat.SR_data.collision:#{
+ box = _.layout.box()
+ row = box.row()
+
+ if (active_mat.SR_data.shader != 'invisible') and \
+ (active_mat.SR_data.shader != 'boundary'):#{
+ row.prop( active_mat.SR_data, "skate_surface" )
+ row.prop( active_mat.SR_data, "grind_surface" )
+ row.prop( active_mat.SR_data, "grow_grass" )
+ #}
+ #}
+
+ if active_mat.SR_data.shader == "terrain_blend":#{
+ box = _.layout.box()
+ box.prop( active_mat.SR_data, "blend_offset" )
+ box.prop( active_mat.SR_data, "sand_colour" )
+ #}
+ elif active_mat.SR_data.shader == "vertex_blend":#{
+ box = _.layout.box()
+ box.label( icon='INFO', text="Uses vertex colours, the R channel" )
+ box.prop( active_mat.SR_data, "blend_offset" )
+ #}
+ elif active_mat.SR_data.shader == "water":#{
+ box = _.layout.box()
+ box.label( icon='INFO', text="Depth scale of 16 meters" )
+ box.prop( active_mat.SR_data, "shore_colour" )
+ box.prop( active_mat.SR_data, "ocean_colour" )
+ #}
+ #}
+#}
+
+def sr_get_type_enum( scene, context ):
+#{
+ items = [('none','None',"")]
+ mesh_entities=['ent_gate','ent_water']
+ point_entities=['ent_spawn','ent_route_node','ent_route']
+
+ for e in point_entities: items += [(e,e,'')]
+
+ if context.scene.SR_data.panel == 'ENTITY': #{
+ if context.active_object.type == 'MESH': #{
+ for e in mesh_entities: items += [(e,e,'')]
+ #}
+ #}
+ else: #{
+ for e in mesh_entities: items += [(e,e,'')]
+ #}
+
+ return items
+#}
+
+def sr_on_type_change( _, context ):
+#{
+ obj = context.active_object
+ ent_type = obj.SR_data.ent_type
+ if ent_type == 'none': return
+ if obj.type == 'MESH':#{
+ col = getattr( obj.data.SR_data, ent_type, None )
+ if col != None and len(col)==0: col.add()
+ #}
+
+ col = getattr( obj.SR_data, ent_type, None )
+ if col != None and len(col)==0: col.add()
+#}
+
+class SR_OBJECT_ENT_SPAWN(bpy.types.PropertyGroup):
+#{
+ alias: bpy.props.StringProperty( name='alias' )
+#}
+
+class SR_OBJECT_ENT_GATE(bpy.types.PropertyGroup):
+#{
+ target: bpy.props.PointerProperty( \
+ type=bpy.types.Object, name="destination", \
+ poll=lambda self,obj: sr_filter_ent_type(obj,['ent_gate']))
+
+ key: bpy.props.StringProperty()
+ tipo: bpy.props.EnumProperty(items=(('default', 'Default', ""),
+ ('nonlocal', 'Non-Local', ""),))
+
+ @staticmethod
+ def sr_inspector( layout, data ):
+ #{
+ box = layout.box()
+ box.prop( data[0], 'tipo', text="subtype" )
+
+ if data[0].tipo == 'default': box.prop( data[0], 'target' )
+ elif data[0].tipo == 'nonlocal': box.prop( data[0], 'key' )
+ #}
+#}
+
+class SR_MESH_ENT_GATE(bpy.types.PropertyGroup):
+#{
+ dimensions: bpy.props.FloatVectorProperty(name="dimensions",size=3)
+#}
+
+class SR_OBJECT_ENT_ROUTE_ENTRY(bpy.types.PropertyGroup):
+#{
+ target: bpy.props.PointerProperty( \
+ type=bpy.types.Object, name='target', \
+ poll=lambda self,obj: sr_filter_ent_type(obj,['ent_gate']))
+#}
+
+class SR_UL_ROUTE_NODE_LIST(bpy.types.UIList):
+#{
+ bl_idname = 'SR_UL_ROUTE_NODE_LIST'
+
+ def draw_item(_,context,layout,data,item,icon,active_data,active_propname):
+ #{
+ layout.prop( item, 'target', text='', emboss=False )
+ #}
+#}
+
+def internal_listdel_execute(self,context,ent_name,collection_name):
+#{
+ active_object = context.active_object
+ data = getattr(active_object.SR_data,ent_name)[0]
+ lista = getattr(data,collection_name)
+ index = getattr(data,F'{collection_name}_index')
+
+ lista.remove(index)
+
+ setattr(data,F'{collection_name}_index', min(max(0,index-1), len(lista)-1))
+ return{'FINISHED'}
+#}
+
+def internal_listadd_execute(self,context,ent_name,collection_name):
+#{
+ active_object = context.active_object
+ getattr(getattr(active_object.SR_data,ent_name)[0],collection_name).add()
+ return{'FINISHED'}
+#}
+
+def copy_propgroup( de, to ):
+#{
+ for a in de.__annotations__:#{
+ if isinstance(getattr(de,a), bpy.types.bpy_prop_collection):#{
+ ca = getattr(de,a)
+ cb = getattr(to,a)
+
+ while len(cb) != len(ca):#{
+ if len(cb) < len(ca): cb.add()
+ else: cb.remove(0)
+ #}
+ for i in range(len(ca)):#{
+ copy_propgroup(ca[i],cb[i])
+ #}
+ #}
+ else:#{
+ setattr(to,a,getattr(de,a))
+ #}
+ #}
+#}
+
+class SR_OT_COPY_ENTITY_DATA(bpy.types.Operator):
+#{
+ bl_idname = "skaterift.copy_entity_data"
+ bl_label = "Copy entity data"
+
+ def execute(self, context):#{
+ data = context.active_object.SR_data
+ new_type = data.ent_type
+ print( F"Copy entity data from: {context.active_object.name}" )
+
+ for obj in context.selected_objects:#{
+ if obj != context.active_object:#{
+ print( F" To: {obj.name}" )
+
+ obj.SR_data.ent_type = new_type
+
+ if active_object.type == 'MESH':#{
+ col = getattr( obj.data.SR_data, new_type, None )
+ if col != None and len(col)==0: col.add()
+ mdata = context.active_object.data.SR_data
+ copy_propgroup( getattr(mdata,new_type)[0], col[0] )
+ #}
+
+ col = getattr( obj.SR_data, new_type, None )
+ if col != None and len(col)==0: col.add()
+ copy_propgroup( getattr(data,new_type)[0], col[0] )
+ #}
+ #}
+ return{'FINISHED'}
+ #}
+#}
+
+class SR_OT_ROUTE_LIST_NEW_ITEM(bpy.types.Operator):
+#{
+ bl_idname = "skaterift.new_entry"
+ bl_label = "Add gate"
+
+ def execute(self, context):#{
+ return internal_listadd_execute(self,context,'ent_route','gates')
+ #}
+#}
+
+class SR_OT_ROUTE_LIST_DEL_ITEM(bpy.types.Operator):
+#{
+ bl_idname = "skaterift.del_entry"
+ bl_label = "Remove gate"
+
+ @classmethod
+ def poll(cls, context):#{
+ active_object = context.active_object
+ if obj_ent_type(active_object) == 'ent_route':#{
+ return active_object.SR_data.ent_route[0].gates
+ #}
+ else: return False
+ #}
+
+ def execute(self, context):#{
+ return internal_listdel_execute(self,context,'ent_route','gates')
+ #}
+#}
+
+class SR_OT_AUDIO_LIST_NEW_ITEM(bpy.types.Operator):
+#{
+ bl_idname = "skaterift.al_new_entry"
+ bl_label = "Add file"
+
+ def execute(self, context):#{
+ return internal_listadd_execute(self,context,'ent_audio','files')
+ #}
+#}
+
+class SR_OT_AUDIO_LIST_DEL_ITEM(bpy.types.Operator):
+#{
+ bl_idname = "skaterift.al_del_entry"
+ bl_label = "Remove file"
+
+ @classmethod
+ def poll(cls, context):#{
+ active_object = context.active_object
+ if obj_ent_type(active_object) == 'ent_audio':#{
+ return active_object.SR_data.ent_audio[0].files
+ #}
+ else: return False
+ #}
+
+ def execute(self, context):#{
+ return internal_listdel_execute(self,context,'ent_audio','files')
+ return{'FINISHED'}
+ #}
+#}
+
+class SR_OT_GLYPH_LIST_NEW_ITEM(bpy.types.Operator):
+#{
+ bl_idname = "skaterift.gl_new_entry"
+ bl_label = "Add glyph"
+
+ def execute(self, context):#{
+ active_object = context.active_object
+
+ font = active_object.SR_data.ent_font[0]
+ font.glyphs.add()
+
+ if len(font.glyphs) > 1:#{
+ prev = font.glyphs[-2]
+ cur = font.glyphs[-1]
+
+ cur.bounds = prev.bounds
+ cur.utf32 = prev.utf32+1
+ #}
+
+ return{'FINISHED'}
+ #}
+#}
+
+class SR_OT_GLYPH_LIST_DEL_ITEM(bpy.types.Operator):
+#{
+ bl_idname = "skaterift.gl_del_entry"
+ bl_label = "Remove Glyph"
+
+ @classmethod
+ def poll(cls, context):#{
+ active_object = context.active_object
+ if obj_ent_type(active_object) == 'ent_font':#{
+ return active_object.SR_data.ent_font[0].glyphs
+ #}
+ else: return False
+ #}
+
+ def execute(self, context):#{
+ return internal_listdel_execute(self,context,'ent_font','glyphs')
+ #}
+#}
+
+class SR_OT_GLYPH_LIST_MOVE_ITEM(bpy.types.Operator):
+#{
+ bl_idname = "skaterift.gl_move_item"
+ bl_label = "aa"
+ direction: bpy.props.EnumProperty(items=(('UP', 'Up', ""),
+ ('DOWN', 'Down', ""),))
+
+ @classmethod
+ def poll(cls, context):#{
+ active_object = context.active_object
+ if obj_ent_type(active_object) == 'ent_font':#{
+ return active_object.SR_data.ent_font[0].glyphs
+ #}
+ else: return False
+ #}
+
+ def execute(_, context):#{
+ active_object = context.active_object
+ data = active_object.SR_data.ent_font[0]
+
+ index = data.glyphs_index
+ neighbor = index + (-1 if _.direction == 'UP' else 1)
+ data.glyphs.move( neighbor, index )
+
+ list_length = len(data.glyphs) - 1
+ new_index = index + (-1 if _.direction == 'UP' else 1)
+
+ data.glyphs_index = max(0, min(new_index, list_length))
+
+ return{'FINISHED'}
+ #}
+#}
+
+class SR_OT_FONT_VARIANT_LIST_NEW_ITEM(bpy.types.Operator):
+#{
+ bl_idname = "skaterift.fv_new_entry"
+ bl_label = "Add variant"
+
+ def execute(self, context):#{
+ return internal_listadd_execute(self,context,'ent_font','variants')
+ #}
+#}
+
+class SR_OT_FONT_VARIANT_LIST_DEL_ITEM(bpy.types.Operator):
+#{
+ bl_idname = "skaterift.fv_del_entry"
+ bl_label = "Remove variant"
+
+ @classmethod
+ def poll(cls, context):#{
+ active_object = context.active_object
+ if obj_ent_type(active_object) == 'ent_font':#{
+ return active_object.SR_data.ent_font[0].variants
+ #}
+ else: return False
+ #}
+
+ def execute(self, context):#{
+ return internal_listdel_execute(self,context,'ent_font','variants')
+ #}
+#}
+
+class SR_OBJECT_ENT_AUDIO_FILE_ENTRY(bpy.types.PropertyGroup):
+#{
+ path: bpy.props.StringProperty( name="Path" )
+ probability: bpy.props.FloatProperty( name="Probability",default=100.0 )
+#}
+
+class SR_UL_AUDIO_LIST(bpy.types.UIList):
+#{
+ bl_idname = 'SR_UL_AUDIO_LIST'
+
+ def draw_item(_,context,layout,data,item,icon,active_data,active_propname):
+ #{
+ split = layout.split(factor=0.7)
+ c = split.column()
+ c.prop( item, 'path', text='', emboss=False )
+ c = split.column()
+ c.prop( item, 'probability', text='%', emboss=True )
+ #}
+#}
+
+class SR_UL_FONT_VARIANT_LIST(bpy.types.UIList):
+#{
+ bl_idname = 'SR_UL_FONT_VARIANT_LIST'
+
+ def draw_item(_,context,layout,data,item,icon,active_data,active_propname):
+ #{
+ layout.prop( item, 'mesh', emboss=False )
+ layout.prop( item, 'tipo' )
+ #}
#}
-# We are trying to compile this node_def
-#
-def encoder_process_definition( node_def ):
+class SR_UL_FONT_GLYPH_LIST(bpy.types.UIList):
#{
- global g_encoder
+ bl_idname = 'SR_UL_FONT_GLYPH_LIST'
- # data sources for object/bone are taken differently
- #
- if 'obj' in node_def:
+ def draw_item(_,context,layout,data,item,icon,active_data,active_propname):
#{
- obj = node_def['obj']
- obj_type = obj.type
- obj_co = obj.location
+ s0 = layout.split(factor=0.3)
+ c = s0.column()
+ s1 = c.split(factor=0.3)
+ c = s1.column()
+ row = c.row()
+ lbl = chr(item.utf32) if item.utf32 >= 32 and item.utf32 <= 126 else 'ERR'
+ row.label(text=lbl)
+ c = s1.column()
+ c.prop( item, 'utf32', text='', emboss=True )
+ c = s0.column()
+ row = c.row()
+ row.prop( item, 'bounds', text='', emboss=False )
+ #}
+#}
- if obj_type == 'ARMATURE':
- obj_classtype = 'classtype_skeleton'
- else:
- #{
- obj_classtype = obj.cv_data.classtype
+class SR_OBJECT_ENT_ROUTE(bpy.types.PropertyGroup):
+#{
+ gates: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_ROUTE_ENTRY)
+ gates_index: bpy.props.IntProperty()
- # Check for armature deform
- #
- for mod in obj.modifiers:
- #{
- if mod.type == 'ARMATURE':
- #{
- obj_classtype = 'classtype_skin'
-
- # Make sure to freeze armature in rest while we collect
- # vertex information
- #
- armature_def = g_encoder['graph_lookup'][mod.object]
- POSE_OR_REST_CACHE = armature_def['obj'].data.pose_position
- armature_def['obj'].data.pose_position = 'REST'
- node_def['linked_armature'] = armature_def
- break
- #}
- #}
- #}
- #}
+ colour: bpy.props.FloatVectorProperty( \
+ name="Colour",\
+ subtype='COLOR',\
+ min=0.0,max=1.0,\
+ default=Vector((0.79,0.63,0.48)),\
+ description="Route colour"\
+ )
+
+ alias: bpy.props.StringProperty(\
+ name="Alias",\
+ default="Untitled Course")
- elif 'bone' in node_def:
+ @staticmethod
+ def sr_inspector( layout, data ):
#{
- obj = node_def['bone']
- obj_type = 'BONE'
- obj_co = obj.head_local
- obj_classtype = 'classtype_bone'
+ layout.prop( data[0], 'alias' )
+ layout.prop( data[0], 'colour' )
+
+ layout.label( text='Checkpoints' )
+ layout.template_list('SR_UL_ROUTE_NODE_LIST', 'Checkpoints', \
+ data[0], 'gates', data[0], 'gates_index', rows=5)
+
+ row = layout.row()
+ row.operator( 'skaterift.new_entry', text='Add' )
+ row.operator( 'skaterift.del_entry', text='Remove' )
#}
+#}
- # Create node
- #
- node = mdl_node()
- node.pstr_name = encoder_process_pstr( obj.name )
+class SR_OBJECT_ENT_VOLUME(bpy.types.PropertyGroup):
+#{
+ subtype: bpy.props.EnumProperty(
+ name="Subtype",
+ items=[('0','Trigger',''),
+ ('1','Particles (0.1s)','')]
+ )
- if node_def["parent"]:
- node.parent = node_def["parent"]["uid"]
+ target: bpy.props.PointerProperty( \
+ type=bpy.types.Object, name="Target", \
+ poll=lambda self,obj: sr_filter_ent_type(obj,\
+ ['ent_audio','ent_skateshop']))
- # Setup transform
- #
- node.co[0] = obj_co[0]
- node.co[1] = obj_co[2]
- node.co[2] = -obj_co[1]
-
- # Convert rotation quat to our space type
- #
- quat = obj.matrix_local.to_quaternion()
- node.q[0] = quat[1]
- node.q[1] = quat[3]
- node.q[2] = -quat[2]
- node.q[3] = quat[0]
-
- # Bone scale is just a vector to the tail
- #
- if obj_type == 'BONE':
- #{
- node.s[0] = obj.tail_local[0] - node.co[0]
- node.s[1] = obj.tail_local[2] - node.co[1]
- node.s[2] = -obj.tail_local[1] - node.co[2]
- #}
- else:
+ @staticmethod
+ def sr_inspector( layout, data ):
#{
- node.s[0] = obj.scale[0]
- node.s[1] = obj.scale[2]
- node.s[2] = obj.scale[1]
+ data = data[0]
+ layout.prop( data, 'subtype' )
+ layout.prop( data, 'target' )
#}
+#}
+
+class SR_OBJECT_ENT_AUDIO(bpy.types.PropertyGroup):
+#{
+ files: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_AUDIO_FILE_ENTRY)
+ files_index: bpy.props.IntProperty()
+
+ flag_3d: bpy.props.BoolProperty( name="3D audio",default=True )
+ flag_loop: bpy.props.BoolProperty( name="Loop",default=False )
+ flag_auto: bpy.props.BoolProperty( name="Play at start",default=False )
+ flag_nodoppler: bpy.props.BoolProperty( name="No Doppler",default=False )
+
+ group: bpy.props.IntProperty( name="Group ID", default=0 )
+ formato: bpy.props.EnumProperty(
+ name="Format",
+ items=[('0','Uncompressed Mono',''),
+ ('1','Compressed Vorbis',''),
+ ('2','[vg] Bird Synthesis','')]
+ )
+ probability_curve: bpy.props.EnumProperty(
+ name="Probability Curve",
+ items=[('0','Constant',''),
+ ('1','Wildlife Daytime',''),
+ ('2','Wildlife Nighttime','')])
+ channel_behaviour: bpy.props.EnumProperty(
+ name="Channel Behaviour",
+ items=[('0','Unlimited',''),
+ ('1','Discard if group full', ''),
+ ('2','Crossfade if group full','')])
+
+ transition_duration: bpy.props.FloatProperty(name="Transition Time",\
+ default=0.2)
- # Report status
- #
- tot_uid = g_encoder['uid_count']-1
- obj_uid = node_def['uid']
- obj_depth = node_def['depth']-1
+ max_channels: bpy.props.IntProperty( name="Max Channels", default=1 )
+ volume: bpy.props.FloatProperty( name="Volume",default=1.0 )
- status_id = F" [{obj_uid: 3}/{tot_uid}]" + " |"*obj_depth
- status_name = status_id + F" L {obj.name}"
+ @staticmethod
+ def sr_inspector( layout, data ):
+ #{
+ layout.prop( data[0], 'formato' )
+ layout.prop( data[0], 'volume' )
- if obj_classtype != 'classtype_none': status_type = obj_classtype
- else: status_type = obj_type
+ box = layout.box()
+ box.label( text='Channels' )
+ split = box.split(factor=0.3)
+ c = split.column()
+ c.prop( data[0], 'max_channels' )
+ c = split.column()
+ c.prop( data[0], 'channel_behaviour', text='Behaviour' )
+ if data[0].channel_behaviour >= '1':
+ box.prop( data[0], 'group' )
+ if data[0].channel_behaviour == '2':
+ box.prop( data[0], 'transition_duration' )
- status_parent = F"{node.parent: 3}"
- status_armref = ""
+ box = layout.box()
+ box.label( text='Flags' )
+ box.prop( data[0], 'flag_3d' )
+ if data[0].flag_3d: box.prop( data[0], 'flag_nodoppler' )
- if obj_classtype == 'classtype_skin':
- status_armref = F" [armature -> {armature_def['obj'].cv_data.uid}]"
+ box.prop( data[0], 'flag_loop' )
+ box.prop( data[0], 'flag_auto' )
- print(F"{status_name:<32} {status_type:<22} {status_parent} {status_armref}")
+ split = layout.split(factor=0.7)
+ c = split.column()
+ c.label( text='Filepath' )
+ c = split.column()
+ c.label( text='Chance (0.1s)' )
- # Process mesh if needed
- #
- if obj_type == 'MESH':
- #{
- encoder_compile_mesh( node, node_def )
- #}
- elif obj_type == 'ARMATURE':
- #{
- encoder_compile_armature( node, node_def )
- #}
+ layout.prop( data[0], 'probability_curve' )
- encoder_compile_ent_as( obj_classtype, node, node_def )
+ layout.template_list('SR_UL_AUDIO_LIST', 'Files', \
+ data[0], 'files', data[0], 'file_index', rows=5)
- # Make sure to reset the armature we just mucked about with
- #
- if obj_classtype == 'classtype_skin':
- armature_def['obj'].data.pose_position = POSE_OR_REST_CACHE
+ row = layout.row()
+ row.operator( 'skaterift.al_new_entry', text='Add' )
+ row.operator( 'skaterift.al_del_entry', text='Remove' )
+ #}
+#}
- g_encoder['data']['node'] += [node]
+class SR_OBJECT_ENT_MARKER(bpy.types.PropertyGroup):
+#{
+ alias: bpy.props.StringProperty()
#}
-# The post processing step or the pre processing to the writing step
-#
-def encoder_write_to_file( path ):
+class SR_OBJECT_ENT_GLYPH(bpy.types.PropertyGroup):
#{
- global g_encoder
-
- # Compile down to a byte array
- #
- header = g_encoder['header']
- file_pos = sizeof(header)
- file_data = bytearray()
- print( " Compositing data arrays" )
-
- for array_name in g_encoder['data']:
- #{
- file_pos += bytearray_align_to( file_data, 16, sizeof(header) )
- arr = g_encoder['data'][array_name]
+ mini: bpy.props.FloatVectorProperty(size=2)
+ maxi: bpy.props.FloatVectorProperty(size=2)
+ utf32: bpy.props.IntProperty()
+#}
- setattr( header, array_name + "_offset", file_pos )
+class SR_OBJECT_ENT_GLYPH_ENTRY(bpy.types.PropertyGroup):
+#{
+ bounds: bpy.props.FloatVectorProperty(size=4,subtype='NONE')
+ utf32: bpy.props.IntProperty()
+#}
- print( F" {array_name:<16} @{file_pos:> 8X}[{len(arr)}]" )
+class SR_OBJECT_ENT_FONT_VARIANT(bpy.types.PropertyGroup):
+#{
+ mesh: bpy.props.PointerProperty(type=bpy.types.Object)
+ tipo: bpy.props.StringProperty()
+#}
- if isinstance( arr, bytearray ):
- #{
- setattr( header, array_name + "_size", len(arr) )
+class SR_OBJECT_ENT_FONT(bpy.types.PropertyGroup):
+#{
+ variants: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_FONT_VARIANT)
+ glyphs: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_GLYPH_ENTRY)
+ alias: bpy.props.StringProperty()
- file_data.extend( arr )
- file_pos += len(arr)
- #}
- else:
- #{
- setattr( header, array_name + "_count", len(arr) )
+ glyphs_index: bpy.props.IntProperty()
+ variants_index: bpy.props.IntProperty()
- for item in arr:
- #{
- bbytes = bytearray(item)
- file_data.extend( bbytes )
- file_pos += sizeof(item)
- #}
- #}
- #}
+ @staticmethod
+ def sr_inspector( layout, data ):
+ #{
+ layout.prop( data[0], 'alias' )
+
+ layout.label( text='Variants' )
+ layout.template_list('SR_UL_FONT_VARIANT_LIST', 'Variants', \
+ data[0], 'variants', data[0], 'variants_index',\
+ rows=5 )
+ row = layout.row()
+ row.operator( 'skaterift.fv_new_entry', text='Add' )
+ row.operator( 'skaterift.fv_del_entry', text='Remove' )
+
+ layout.label( text='ASCII Glyphs' )
+ layout.template_list('SR_UL_FONT_GLYPH_LIST', 'Glyphs', \
+ data[0], 'glyphs', data[0], 'glyphs_index', rows=5)
+
+ row = layout.row()
+ row.operator( 'skaterift.gl_new_entry', text='Add' )
+ row.operator( 'skaterift.gl_del_entry', text='Remove' )
+ row.operator( 'skaterift.gl_move_item', text='^' ).direction='UP'
+ row.operator( 'skaterift.gl_move_item', text='v' ).direction='DOWN'
+ #}
+#}
+
+class SR_OBJECT_ENT_TRAFFIC(bpy.types.PropertyGroup):
+#{
+ speed: bpy.props.FloatProperty(default=1.0)
+#}
+
+class SR_OBJECT_ENT_SKATESHOP(bpy.types.PropertyGroup):
+#{
+ mark_rack: bpy.props.PointerProperty( \
+ type=bpy.types.Object, name="Board Rack", \
+ poll=lambda self,obj: sr_filter_ent_type(obj,['ent_marker']))
+ mark_display: bpy.props.PointerProperty( \
+ type=bpy.types.Object, name="Selected Board Display", \
+ poll=lambda self,obj: sr_filter_ent_type(obj,['ent_marker']))
+ mark_info: bpy.props.PointerProperty( \
+ type=bpy.types.Object, name="Selected Board Info", \
+ poll=lambda self,obj: sr_filter_ent_type(obj,['ent_marker']))
+ cam: bpy.props.PointerProperty( \
+ type=bpy.types.Object, name="Viewpoint", \
+ poll=lambda self,obj: sr_filter_ent_type(obj,['ent_camera']))
+#}
+
+class SR_OBJECT_PROPERTIES(bpy.types.PropertyGroup):
+#{
+ ent_gate: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_GATE)
+ ent_spawn: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_SPAWN)
+ ent_route: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_ROUTE)
+ ent_volume: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_VOLUME)
+ ent_audio: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_AUDIO)
+ ent_marker: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_MARKER)
+ ent_glyph: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_GLYPH)
+ ent_font: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_FONT)
+ ent_traffic: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_TRAFFIC)
+ ent_skateshop: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_SKATESHOP)
+ ent_type: bpy.props.EnumProperty(
+ name="Type",
+ items=sr_entity_list,
+ update=sr_on_type_change
+ )
+#}
- # This imperitive for this field to be santized in the future!
- #
- header.file_length = file_pos
+class SR_MESH_PROPERTIES(bpy.types.PropertyGroup):
+#{
+ ent_gate: bpy.props.CollectionProperty(type=SR_MESH_ENT_GATE)
+#}
- print( " Writing file" )
- # Write header and data chunk to file
- #
- fp = open( path, "wb" )
- fp.write( bytearray( header ) )
- fp.write( file_data )
- fp.close()
+class SR_LIGHT_PROPERTIES(bpy.types.PropertyGroup):
+#{
+ daytime: bpy.props.BoolProperty( name='Daytime' )
#}
-# Main compiler, uses string as the identifier for the collection
-#
-def write_model(collection_name):
+class SR_BONE_PROPERTIES(bpy.types.PropertyGroup):
#{
- global g_encoder
- print( F"Model graph | Create mode '{collection_name}'" )
- folder = bpy.path.abspath(bpy.context.scene.cv_data.export_dir)
- path = F"{folder}{collection_name}.mdl"
- print( path )
-
- collection = bpy.data.collections[collection_name]
+ collider: bpy.props.EnumProperty( name='Collider Type',
+ items=[('0','none',''),
+ ('1','box',''),
+ ('2','capsule','')])
- encoder_init( collection )
- encoder_build_scene_graph( collection )
+ collider_min: bpy.props.FloatVectorProperty( name='Collider Min', size=3 )
+ collider_max: bpy.props.FloatVectorProperty( name='Collider Max', size=3 )
- # Compile
- #
- print( " Comping objects" )
- it = encoder_graph_iterator( g_encoder['scene_graph'] )
- for node_def in it:
- encoder_process_definition( node_def )
+ cone_constraint: bpy.props.BoolProperty( name='Cone constraint' )
- # Write
- #
- encoder_write_to_file( path )
+ conevx: bpy.props.FloatVectorProperty( name='vx' )
+ conevy: bpy.props.FloatVectorProperty( name='vy' )
+ coneva: bpy.props.FloatVectorProperty( name='va' )
+ conet: bpy.props.FloatProperty( name='t' )
+
+ @staticmethod
+ def sr_inspector( layout, data ):
+ #{
+ data = data[0]
+ box = layout.box()
+ box.prop( data, 'collider' )
+
+ if int(data.collider)>0:#{
+ row = box.row()
+ row.prop( data, 'collider_min' )
+ row = box.row()
+ row.prop( data, 'collider_max' )
+ #}
+
+ box = layout.box()
+ box.prop( data, 'cone_constraint' )
+ if data.cone_constraint:#{
+ row = box.row()
+ row.prop( data, 'conevx' )
+ row = box.row()
+ row.prop( data, 'conevy' )
+ row = box.row()
+ row.prop( data, 'coneva' )
+ box.prop( data, 'conet' )
+ #}
+ #}
+#}
+
+class SR_MATERIAL_PROPERTIES(bpy.types.PropertyGroup):
+#{
+ shader: bpy.props.EnumProperty(
+ name="Format",
+ items = [
+ ('standard',"standard",''),
+ ('standard_cutout', "standard_cutout", ''),
+ ('terrain_blend', "terrain_blend", ''),
+ ('vertex_blend', "vertex_blend", ''),
+ ('water',"water",''),
+ ('invisible','Invisible',''),
+ ('boundary','Boundary','')
+ ])
- print( F"Completed {collection_name}.mdl" )
+ surface_prop: bpy.props.EnumProperty(
+ name="Surface Property",
+ items = [
+ ('0','concrete',''),
+ ('1','wood',''),
+ ('2','grass',''),
+ ('3','tiles',''),
+ ('4','metal','')
+ ])
+
+ collision: bpy.props.BoolProperty( \
+ name="Collisions Enabled",\
+ default=True,\
+ description = "Can the player collide with this material?"\
+ )
+ skate_surface: bpy.props.BoolProperty( \
+ name="Skate Target", \
+ default=True,\
+ description = "Should the game try to target this surface?" \
+ )
+ grind_surface: bpy.props.BoolProperty( \
+ name="Grindable", \
+ default=True,\
+ description = "Can you grind on this surface?" \
+ )
+ grow_grass: bpy.props.BoolProperty( \
+ name="Grow Grass", \
+ default=False,\
+ description = "Spawn grass sprites on this surface?" \
+ )
+ blend_offset: bpy.props.FloatVectorProperty( \
+ name="Blend Offset", \
+ size=2, \
+ default=Vector((0.5,0.0)),\
+ description="When surface is more than 45 degrees, add this vector " +\
+ "to the UVs" \
+ )
+ sand_colour: bpy.props.FloatVectorProperty( \
+ name="Sand Colour",\
+ subtype='COLOR',\
+ min=0.0,max=1.0,\
+ default=Vector((0.79,0.63,0.48)),\
+ description="Blend to this colour near the 0 coordinate on UP axis"\
+ )
+ shore_colour: bpy.props.FloatVectorProperty( \
+ name="Shore Colour",\
+ subtype='COLOR',\
+ min=0.0,max=1.0,\
+ default=Vector((0.03,0.32,0.61)),\
+ description="Water colour at the shoreline"\
+ )
+ ocean_colour: bpy.props.FloatVectorProperty( \
+ name="Ocean Colour",\
+ subtype='COLOR',\
+ min=0.0,max=1.0,\
+ default=Vector((0.0,0.006,0.03)),\
+ description="Water colour in the deep bits"\
+ )
#}
# ---------------------------------------------------------------------------- #
pi = 3.14159265358979323846264
- for i in range(16):
- #{
+ for i in range(16):#{
t = ((i+1.0) * 1.0/16.0) * pi * 2.0
s = math.sin(t)
c = math.cos(t)
cv_draw_lines()
#}
+# Draw axis alligned sphere at position with radius
+#
+def cv_draw_halfsphere( pos, tx, ty, tz, radius, colour ):
+#{
+ global cv_view_verts, cv_view_colours
+
+ ly = pos + tz*radius
+ lx = pos + ty*radius
+ lz = pos + tz*radius
+
+ pi = 3.14159265358979323846264
+
+ for i in range(16):#{
+ t = ((i+1.0) * 1.0/16.0) * pi
+ s = math.sin(t)
+ c = math.cos(t)
+
+ s1 = math.sin(t*2.0)
+ c1 = math.cos(t*2.0)
+
+ py = pos + s*tx*radius + c *tz*radius
+ px = pos + s*tx*radius + c *ty*radius
+ pz = pos + s1*ty*radius + c1*tz*radius
+
+ cv_view_verts += [ px, lx ]
+ cv_view_verts += [ py, ly ]
+ cv_view_verts += [ pz, lz ]
+
+ cv_view_colours += [ colour, colour, colour, colour, colour, colour ]
+
+ ly = py
+ lx = px
+ lz = pz
+ #}
+ cv_draw_lines()
+#}
+
# Draw transformed -1 -> 1 cube
#
-def cv_draw_ucube( transform, colour ):
+def cv_draw_ucube( transform, colour, s=Vector((1,1,1)), o=Vector((0,0,0)) ):
#{
global cv_view_verts, cv_view_colours
- a = Vector((-1,-1,-1))
- b = Vector((1,1,1))
+ a = o + -1.0 * s
+ b = o + 1.0 * s
vs = [None]*8
vs[0] = transform @ Vector((a[0], a[1], a[2]))
indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
(0,4),(1,5),(2,6),(3,7)]
- for l in indices:
- #{
+ for l in indices:#{
v0 = vs[l[0]]
v1 = vs[l[1]]
cv_view_verts += [(v0[0],v0[1],v0[2])]
cv_view_verts += [(v1[0],v1[1],v1[2])]
- cv_view_colours += [(0,1,0,1),(0,1,0,1)]
+ cv_view_colours += [colour, colour]
#}
cv_draw_lines()
#}
cv_draw_lines()
#}
-# Just the tx because we dont really need ty for this app
+#
#
-def cv_tangent_basis_tx( n, tx ):
+def cv_tangent_basis( n, tx, ty ):
#{
- if abs( n[0] ) >= 0.57735027:
- #{
+ if abs( n[0] ) >= 0.57735027:#{
tx[0] = n[1]
tx[1] = -n[0]
tx[2] = 0.0
#}
- else:
- #{
+ else:#{
tx[0] = 0.0
tx[1] = n[2]
tx[2] = -n[1]
#}
tx.normalize()
+ _ty = n.cross( tx )
+
+ ty[0] = _ty[0]
+ ty[1] = _ty[1]
+ ty[2] = _ty[2]
#}
# Draw coloured arrow
#
-def cv_draw_arrow( p0, p1, c0 ):
+def cv_draw_arrow( p0, p1, c0, size=0.15 ):
#{
global cv_view_verts, cv_view_colours
n.normalize()
tx = Vector((1,0,0))
- cv_tangent_basis_tx( n, tx )
+ ty = Vector((1,0,0))
+ cv_tangent_basis( n, tx, ty )
- cv_view_verts += [p0,p1, midpt+(tx-n)*0.15,midpt, midpt+(-tx-n)*0.15,midpt ]
+ cv_view_verts += [p0,p1, midpt+(tx-n)*size,midpt, midpt+(-tx-n)*size,midpt ]
cv_view_colours += [c0,c0,c0,c0,c0,c0]
- cv_draw_lines()
+ #cv_draw_lines()
+#}
+
+def cv_draw_line_dotted( p0, p1, c0, dots=10 ):
+#{
+ global cv_view_verts, cv_view_colours
+
+ for i in range(dots):#{
+ t0 = i/dots
+ t1 = (i+0.25)/dots
+
+ p2 = p0*(1.0-t0)+p1*t0
+ p3 = p0*(1.0-t1)+p1*t1
+
+ cv_view_verts += [p2,p3]
+ cv_view_colours += [c0,c0]
+ #}
+ #cv_draw_lines()
#}
# Drawhandles of a bezier control point
global cv_view_verts, cv_view_colours
last = p0
- for i in range(10):
- #{
+ for i in range(10):#{
t = (i+1)/10
a0 = 1-t
ay = major*f
ax = minor*f
- for x in range(16):
- #{
+ for x in range(16):#{
t0 = x/16
t1 = (x+1)/16
a0 = amin*(1.0-t0)+amax*t0
cv_view_verts += [p0,p1]
cv_view_colours += [colour,colour]
- if x == 0:
- #{
+ if x == 0:#{
cv_view_verts += [p0,center]
cv_view_colours += [colour,colour]
#}
- if x == 15:
- #{
+ if x == 15:#{
cv_view_verts += [p1,center]
cv_view_colours += [colour,colour]
#}
cv_draw_lines()
#}
+# Cone and twist limit
+#
+def draw_cone_twist( center, vx, vy, va ):
+#{
+ global cv_view_verts, cv_view_colours
+ axis = vy.cross( vx )
+ axis.normalize()
+
+ size = 0.12
+
+ cv_view_verts += [center, center+va*size]
+ cv_view_colours += [ (1,1,1), (1,1,1) ]
+
+ for x in range(32):#{
+ t0 = (x/32) * math.tau
+ t1 = ((x+1)/32) * math.tau
+
+ c0 = math.cos(t0)
+ s0 = math.sin(t0)
+ c1 = math.cos(t1)
+ s1 = math.sin(t1)
+
+ p0 = center + (axis + vx*c0 + vy*s0).normalized() * size
+ p1 = center + (axis + vx*c1 + vy*s1).normalized() * size
+
+ col0 = ( abs(c0), abs(s0), 0.0, 1.0 )
+ col1 = ( abs(c1), abs(s1), 0.0, 1.0 )
+
+ cv_view_verts += [center, p0, p0, p1]
+ cv_view_colours += [ (0,0,0), col0, col0, col1 ]
+ #}
+
+ cv_draw_lines()
+#}
+
# Draws constraints and stuff for the skeleton. This isnt documented and wont be
#
def draw_skeleton_helpers( obj ):
#{
global cv_view_verts, cv_view_colours
- for bone in obj.data.bones:
- #{
- if bone.cv_data.collider and (obj.data.pose_position == 'REST'):
- #{
- c = bone.head_local
- a = bone.cv_data.v0
- b = bone.cv_data.v1
-
+ if obj.data.pose_position != 'REST':#{
+ return
+ #}
+
+ for bone in obj.data.bones:#{
+ c = bone.head_local
+ a = Vector((bone.SR_data.collider_min[0],
+ bone.SR_data.collider_min[1],
+ bone.SR_data.collider_min[2]))
+ b = Vector((bone.SR_data.collider_max[0],
+ bone.SR_data.collider_max[1],
+ bone.SR_data.collider_max[2]))
+
+ if bone.SR_data.collider == '1':#{
vs = [None]*8
vs[0]=obj.matrix_world@Vector((c[0]+a[0],c[1]+a[1],c[2]+a[2]))
vs[1]=obj.matrix_world@Vector((c[0]+a[0],c[1]+b[1],c[2]+a[2]))
indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
(0,4),(1,5),(2,6),(3,7)]
- for l in indices:
- #{
+ for l in indices:#{
v0 = vs[l[0]]
- v1 = vs[l[1]]
-
- cv_view_verts += [(v0[0],v0[1],v0[2])]
- cv_view_verts += [(v1[0],v1[1],v1[2])]
- cv_view_colours += [(0.5,0.5,0.5,0.5),(0.5,0.5,0.5,0.5)]
- #}
-
- center = obj.matrix_world @ c
- if bone.cv_data.con0:
- #{
- draw_limit( obj, c, Vector((0,1,0)),Vector((0,0,1)), \
- bone.cv_data.mins[0], bone.cv_data.maxs[0], \
- (1,0,0,1))
- draw_limit( obj, c, Vector((0,0,1)),Vector((1,0,0)), \
- bone.cv_data.mins[1], bone.cv_data.maxs[1], \
- (0,1,0,1))
- draw_limit( obj, c, Vector((1,0,0)),Vector((0,1,0)), \
- bone.cv_data.mins[2], bone.cv_data.maxs[2], \
- (0,0,1,1))
- #}
- #}
- #}
-#}
-
-def cv_draw():
-#{
- global cv_view_shader
- global cv_view_verts
- global cv_view_colours
- global cv_view_course_i
-
- cv_view_course_i = 0
- cv_view_verts = []
- cv_view_colours = []
-
- cv_view_shader.bind()
- gpu.state.depth_mask_set(False)
- gpu.state.line_width_set(2.0)
- gpu.state.face_culling_set('BACK')
- gpu.state.depth_test_set('LESS')
- gpu.state.blend_set('NONE')
-
- for obj in bpy.context.collection.objects:
- #{
- if obj.type == 'ARMATURE':
- #{
- if obj.data.pose_position == 'REST':
- draw_skeleton_helpers( obj )
+ v1 = vs[l[1]]
+
+ cv_view_verts += [(v0[0],v0[1],v0[2])]
+ cv_view_verts += [(v1[0],v1[1],v1[2])]
+ cv_view_colours += [(0.5,0.5,0.5),(0.5,0.5,0.5)]
+ #}
#}
- else:
- #{
- classtype = obj.cv_data.classtype
- if (classtype != 'classtype_none') and (classtype in globals()):
- #{
- cl = globals()[ classtype ]
-
- if getattr( cl, "draw_scene_helpers", None ):
- #{
- cl.draw_scene_helpers( obj )
+ elif bone.SR_data.collider == '2':#{
+ v0 = b-a
+ major_axis = 0
+ largest = -1.0
+
+ for i in range(3):#{
+ if abs(v0[i]) > largest:#{
+ largest = abs(v0[i])
+ major_axis = i
#}
#}
- #}
- #}
- cv_draw_lines()
- return
-#}
-
+ v1 = Vector((0,0,0))
+ v1[major_axis] = 1.0
-# ---------------------------------------------------------------------------- #
-# #
-# Blender #
-# #
-# ---------------------------------------------------------------------------- #
+ tx = Vector((0,0,0))
+ ty = Vector((0,0,0))
-# Checks whether this object has a classtype assigned. we can only target other
-# classes
-def cv_poll_target(scene, obj):
-#{
- if obj == bpy.context.active_object:
- return False
- if obj.cv_data.classtype == 'classtype_none':
- return False
+ cv_tangent_basis( v1, tx, ty )
+ r = (abs(tx.dot( v0 )) + abs(ty.dot( v0 ))) * 0.25
+ l = v0[ major_axis ] - r*2
- return True
-#}
+ p0 = obj.matrix_world@Vector( c + (a+b)*0.5 + v1*l*-0.5 )
+ p1 = obj.matrix_world@Vector( c + (a+b)*0.5 + v1*l* 0.5 )
-class CV_MESH_SETTINGS(bpy.types.PropertyGroup):
-#{
- v0: bpy.props.FloatVectorProperty(name="v0",size=3)
- v1: bpy.props.FloatVectorProperty(name="v1",size=3)
- v2: bpy.props.FloatVectorProperty(name="v2",size=3)
- v3: bpy.props.FloatVectorProperty(name="v3",size=3)
+ colour = [0.2,0.2,0.2]
+ colour[major_axis] = 0.5
+
+ cv_draw_halfsphere( p0, -v1, ty, tx, r, colour )
+ cv_draw_halfsphere( p1, v1, ty, tx, r, colour )
+ cv_draw_line( p0+tx* r, p1+tx* r, colour )
+ cv_draw_line( p0+tx*-r, p1+tx*-r, colour )
+ cv_draw_line( p0+ty* r, p1+ty* r, colour )
+ cv_draw_line( p0+ty*-r, p1+ty*-r, colour )
+ #}
+ else:#{
+ continue
+ #}
+
+ center = obj.matrix_world @ c
+ if bone.SR_data.cone_constraint:#{
+ vx = Vector([bone.SR_data.conevx[_] for _ in range(3)])
+ vy = Vector([bone.SR_data.conevy[_] for _ in range(3)])
+ va = Vector([bone.SR_data.coneva[_] for _ in range(3)])
+ draw_cone_twist( center, vx, vy, va )
+ #}
+ #}
#}
-class CV_OBJ_SETTINGS(bpy.types.PropertyGroup):
+def cv_ent_gate( obj ):
#{
- uid: bpy.props.IntProperty( name="" )
+ global cv_view_verts, cv_view_colours
- strp: bpy.props.StringProperty( name="strp" )
- intp: bpy.props.IntProperty( name="intp" )
- fltp: bpy.props.FloatProperty( name="fltp" )
- bp0: bpy.props.BoolProperty( name="bp0" )
- bp1: bpy.props.BoolProperty( name="bp1" )
- bp2: bpy.props.BoolProperty( name="bp2" )
- bp3: bpy.props.BoolProperty( name="bp3" )
+ if obj.type != 'MESH': return
- target: bpy.props.PointerProperty( type=bpy.types.Object, name="target", \
- poll=cv_poll_target )
- target1: bpy.props.PointerProperty( type=bpy.types.Object, name="target1", \
- poll=cv_poll_target )
- target2: bpy.props.PointerProperty( type=bpy.types.Object, name="target2", \
- poll=cv_poll_target )
- target3: bpy.props.PointerProperty( type=bpy.types.Object, name="target3", \
- poll=cv_poll_target )
+ mesh_data = obj.data.SR_data.ent_gate[0]
+ data = obj.SR_data.ent_gate[0]
+ dims = mesh_data.dimensions
- colour: bpy.props.FloatVectorProperty( name="colour",subtype='COLOR',\
- min=0.0,max=1.0)
+ vs = [None]*9
+ c = Vector((0,0,dims[2]))
- classtype: bpy.props.EnumProperty(
- name="Format",
- items = [
- ('classtype_none', "classtype_none", "", 0),
- ('classtype_gate', "classtype_gate", "", 1),
- ('classtype_spawn', "classtype_spawn", "", 3),
- ('classtype_water', "classtype_water", "", 4),
- ('classtype_route_node', "classtype_route_node", "", 8 ),
- ('classtype_route', "classtype_route", "", 9 ),
- ('classtype_audio',"classtype_audio","",14),
- ('classtype_trigger',"classtype_trigger","",100),
- ('classtype_logic_achievement',"classtype_logic_achievement","",101),
- ('classtype_logic_relay',"classtype_logic_relay","",102),
- ('classtype_spawn_link',"classtype_spawn_link","",150),
- ])
-#}
+ vs[0] = obj.matrix_world @ Vector((-dims[0],0.0,-dims[1]+dims[2]))
+ vs[1] = obj.matrix_world @ Vector((-dims[0],0.0, dims[1]+dims[2]))
+ vs[2] = obj.matrix_world @ Vector(( dims[0],0.0, dims[1]+dims[2]))
+ vs[3] = obj.matrix_world @ Vector(( dims[0],0.0,-dims[1]+dims[2]))
+ vs[4] = obj.matrix_world @ (c+Vector((-1,0,-2)))
+ vs[5] = obj.matrix_world @ (c+Vector((-1,0, 2)))
+ vs[6] = obj.matrix_world @ (c+Vector(( 1,0, 2)))
+ vs[7] = obj.matrix_world @ (c+Vector((-1,0, 0)))
+ vs[8] = obj.matrix_world @ (c+Vector(( 1,0, 0)))
-class CV_BONE_SETTINGS(bpy.types.PropertyGroup):
-#{
- collider: bpy.props.BoolProperty(name="Collider",default=False)
- v0: bpy.props.FloatVectorProperty(name="v0",size=3)
- v1: bpy.props.FloatVectorProperty(name="v1",size=3)
+ indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(7,8)]
+
+ r3d = bpy.context.area.spaces.active.region_3d
- con0: bpy.props.BoolProperty(name="Constriant 0",default=False)
- mins: bpy.props.FloatVectorProperty(name="mins",size=3)
- maxs: bpy.props.FloatVectorProperty(name="maxs",size=3)
+ p0 = r3d.view_matrix.inverted().translation
+ v0 = (obj.matrix_world@Vector((0,0,0))) - p0
+ v1 = obj.matrix_world.to_3x3() @ Vector((0,1,0))
+
+ if v0.dot(v1) > 0.0: cc = (0,1,0)
+ else: cc = (1,0,0)
+
+ for l in indices:#{
+ v0 = vs[l[0]]
+ v1 = vs[l[1]]
+ cv_view_verts += [(v0[0],v0[1],v0[2])]
+ cv_view_verts += [(v1[0],v1[1],v1[2])]
+ cv_view_colours += [cc,cc]
+ #}
+
+ sw = (0.4,0.4,0.4)
+ if data.target != None:
+ cv_draw_arrow( obj.location, data.target.location, sw )
#}
-class CV_BONE_PANEL(bpy.types.Panel):
+def cv_ent_volume( obj ):
#{
- bl_label="Bone Config"
- bl_idname="SCENE_PT_cv_bone"
- bl_space_type='PROPERTIES'
- bl_region_type='WINDOW'
- bl_context='bone'
+ global cv_view_verts, cv_view_colours
- def draw(_,context):
- #{
- active_object = context.active_object
- if active_object == None: return
+ data = obj.SR_data.ent_volume[0]
- bone = active_object.data.bones.active
- if bone == None: return
+ if data.subtype == '0':#{
+ cv_draw_ucube( obj.matrix_world, (0,1,0) )
- _.layout.prop( bone.cv_data, "collider" )
- _.layout.prop( bone.cv_data, "v0" )
- _.layout.prop( bone.cv_data, "v1" )
+ if data.target:#{
+ cv_draw_line( obj.location, data.target.location, (0,1,0) )
+ #}
+ #}
+ elif data.subtype == '1':#{
+ cv_draw_ucube( obj.matrix_world, (1,1,0) )
- _.layout.label( text="Angle Limits" )
- _.layout.prop( bone.cv_data, "con0" )
- _.layout.prop( bone.cv_data, "mins" )
- _.layout.prop( bone.cv_data, "maxs" )
+ if data.target:#{
+ cv_draw_line( obj.location, data.target.location, (1,1,0) )
+ #}
#}
#}
-class CV_SCENE_SETTINGS(bpy.types.PropertyGroup):
+def dijkstra( graph, start_node, target_node ):
#{
- use_hidden: bpy.props.BoolProperty( name="use hidden", default=False )
- export_dir: bpy.props.StringProperty( name="Export Dir", subtype='DIR_PATH' )
+ unvisited = [_ for _ in graph]
+ shortest_path = {}
+ previous_nodes = {}
+
+ for n in unvisited:
+ shortest_path[n] = 9999999.999999
+ shortest_path[start_node] = 0
+
+ while unvisited:#{
+ current_min_node = None
+ for n in unvisited:#{
+ if current_min_node == None:
+ current_min_node = n
+ elif shortest_path[n] < shortest_path[current_min_node]:
+ current_min_node = n
+ #}
+
+ for branch in graph[current_min_node]:#{
+ tentative_value = shortest_path[current_min_node]
+ tentative_value += graph[current_min_node][branch]
+ if tentative_value < shortest_path[branch]:#{
+ shortest_path[branch] = tentative_value
+ previous_nodes[branch] = current_min_node
+ #}
+ #}
+
+ unvisited.remove(current_min_node)
+ #}
+
+ path = []
+ node = target_node
+ while node != start_node:#{
+ path.append(node)
+
+ if node not in previous_nodes: return None
+ node = previous_nodes[node]
+ #}
+
+ # Add the start node manually
+ path.append(start_node)
+ return path
#}
-class CV_COLLECTION_SETTINGS(bpy.types.PropertyGroup):
+class dij_graph():
#{
- pack_textures: bpy.props.BoolProperty( name="Pack Textures", default=False )
+ def __init__(_,points,graph,subsections):#{
+ _.points = points
+ _.graph = graph
+ _.subsections = subsections
+ #}
#}
-class CV_MATERIAL_SETTINGS(bpy.types.PropertyGroup):
+def create_node_graph( curves, gates ):
#{
- shader: bpy.props.EnumProperty(
- name="Format",
- items = [
- ('standard',"standard","",0),
- ('standard_cutout', "standard_cutout", "", 1),
- ('terrain_blend', "terrain_blend", "", 2),
- ('vertex_blend', "vertex_blend", "", 3),
- ('water',"water","",4),
- ])
+ # add endpoints of curves
+ graph = {}
+ route_points = []
+ subsections = []
+ point_count = 0
+ spline_count = 0
- surface_prop: bpy.props.EnumProperty(
- name="Surface Property",
- items = [
- ('concrete','concrete','',0),
- ('wood','wood','',1),
- ('grass','grass','',2)
- ])
-
- collision: bpy.props.BoolProperty( \
- name="Collisions Enabled",\
- default=True,\
- description = "Can the player collide with this material"\
- )
- skate_surface: bpy.props.BoolProperty( \
- name="Skate Surface", \
- default=True,\
- description = "Should the game try to target this surface?" \
- )
- grind_surface: bpy.props.BoolProperty( \
- name="Grind Surface", \
- default=False,\
- description = "Grind face?" \
- )
- grow_grass: bpy.props.BoolProperty( \
- name="Grow Grass", \
- default=False,\
- description = "Spawn grass sprites on this surface?" \
- )
- blend_offset: bpy.props.FloatVectorProperty( \
- name="Blend Offset", \
- size=2, \
- default=Vector((0.5,0.0)),\
- description="When surface is more than 45 degrees, add this vector " +\
- "to the UVs" \
- )
- sand_colour: bpy.props.FloatVectorProperty( \
- name="Sand Colour",\
- subtype='COLOR',\
- min=0.0,max=1.0,\
- default=Vector((0.79,0.63,0.48)),\
- description="Blend to this colour near the 0 coordinate on UP axis"\
- )
- shore_colour: bpy.props.FloatVectorProperty( \
- name="Shore Colour",\
- subtype='COLOR',\
- min=0.0,max=1.0,\
- default=Vector((0.03,0.32,0.61)),\
- description="Water colour at the shoreline"\
- )
- ocean_colour: bpy.props.FloatVectorProperty( \
- name="Ocean Colour",\
- subtype='COLOR',\
- min=0.0,max=1.0,\
- default=Vector((0.0,0.006,0.03)),\
- description="Water colour in the deep bits"\
- )
-#}
+ for c in range(len(curves)):#{
+ for s in range(len(curves[c].data.splines)):#{
+ spline = curves[c].data.splines[s]
+ l = len(spline.points)
+ if l < 2: continue
-class CV_MATERIAL_PANEL(bpy.types.Panel):
-#{
- bl_label="Skate Rift material"
- bl_idname="MATERIAL_PT_cv_material"
- bl_space_type='PROPERTIES'
- bl_region_type='WINDOW'
- bl_context="material"
-
- def draw(_,context):
- #{
- active_object = bpy.context.active_object
- if active_object == None: return
- active_mat = active_object.active_material
- if active_mat == None: return
+ dist = round(spline.calc_length(),2)
- info = material_info( active_mat )
+ ia = point_count
+ ib = point_count+l-1
- _.layout.prop( active_mat.cv_data, "shader" )
- _.layout.prop( active_mat.cv_data, "surface_prop" )
- _.layout.prop( active_mat.cv_data, "collision" )
+ graph[ia] = { ib: dist }
+ graph[ib] = { ia: dist }
+
+ for i in range(len(spline.points)):#{
+ wco = curves[c].matrix_world @ spline.points[i].co
+ route_points.append(Vector((wco[0],wco[1],wco[2]+0.5)))
- if active_mat.cv_data.collision:
- _.layout.prop( active_mat.cv_data, "skate_surface" )
- _.layout.prop( active_mat.cv_data, "grind_surface" )
- _.layout.prop( active_mat.cv_data, "grow_grass" )
+ previous = ia+i-1
+ proxima = ia+i+1
- if active_mat.cv_data.shader == "terrain_blend":
- #{
- box = _.layout.box()
- box.prop( active_mat.cv_data, "blend_offset" )
- box.prop( active_mat.cv_data, "sand_colour" )
- #}
- elif active_mat.cv_data.shader == "vertex_blend":
- #{
- box = _.layout.box()
- box.label( icon='INFO', text="Uses vertex colours, the R channel" )
- box.prop( active_mat.cv_data, "blend_offset" )
- #}
- elif active_mat.cv_data.shader == "water":
- #{
- box = _.layout.box()
- box.label( icon='INFO', text="Depth scale of 16 meters" )
- box.prop( active_mat.cv_data, "shore_colour" )
- box.prop( active_mat.cv_data, "ocean_colour" )
+ if i == 0: previous = -1
+ if i == len(spline.points)-1: proxima = -1
+
+ subsections.append((spline_count,previous,proxima))
+ point_count += 1
+ #}
+
+ spline_count += 1
#}
#}
-#}
-class CV_OBJ_PANEL(bpy.types.Panel):
-#{
- bl_label="Entity Config"
- bl_idname="SCENE_PT_cv_entity"
- bl_space_type='PROPERTIES'
- bl_region_type='WINDOW'
- bl_context="object"
-
- def draw(_,context):
- #{
- active_object = bpy.context.active_object
- if active_object == None: return
- if active_object.type == 'ARMATURE':
- #{
- row = _.layout.row()
- row.enabled = False
- row.label( text="This object has the intrinsic classtype of skeleton" )
- return
+ # link endpoints
+ graph_keys = list(graph)
+ for i in range(len(graph_keys)-1):#{
+ for j in range(i+1, len(graph_keys)):#{
+ if i%2==0 and i+1==j: continue
+
+ ni = graph_keys[i]
+ nj = graph_keys[j]
+ pi = route_points[ni]
+ pj = route_points[nj]
+
+ dist = round((pj-pi).magnitude,2)
+
+ if dist < 10.0:#{
+ graph[ni][nj] = dist
+ graph[nj][ni] = dist
+ #}
#}
+ #}
- _.layout.prop( active_object.cv_data, "classtype" )
+ # add and link gates( by name )
+ for gate in gates:#{
+ v1 = gate.matrix_world.to_3x3() @ Vector((0,1,0))
+ if gate.SR_data.ent_gate[0].target:
+ v1 = v1 * -1.0
- classtype = active_object.cv_data.classtype
+ graph[ gate.name ] = {}
- if (classtype != 'classtype_none') and (classtype in globals()):
- #{
- cl = globals()[ classtype ]
+ for i in range(len(graph_keys)):#{
+ ni = graph_keys[i]
+ pi = route_points[ni]
+
+ v0 = pi-gate.location
+ if v0.dot(v1) < 0.0: continue
+
+ dist = round(v0.magnitude,2)
- if getattr( cl, "editor_interface", None ):
- #{
- cl.editor_interface( _.layout, active_object )
+ if dist < 10.0:#{
+ graph[ gate.name ][ ni ] = dist
+ graph[ ni ][ gate.name ] = dist
#}
#}
#}
+
+ return dij_graph(route_points,graph,subsections)
#}
-class CV_COMPILE(bpy.types.Operator):
+def solve_graph( dij, start, end ):
#{
- bl_idname="carve.compile_all"
- bl_label="Compile All"
+ path = dijkstra( dij.graph, end, start )
+ full = []
- def execute(_,context):
- #{
- view_layer = bpy.context.view_layer
- for col in view_layer.layer_collection.children["export"].children:
- if not col.hide_viewport or bpy.context.scene.cv_data.use_hidden:
- write_model( col.name )
+ if path:#{
+ for sj in range(1,len(path)-2):#{
+ i0 = path[sj]
+ i1 = path[sj+1]
+ map0 = dij.subsections[i0]
+ map1 = dij.subsections[i1]
- return {'FINISHED'}
+ if map0[0] == map1[0]:#{
+ if map0[1] == -1: direction = 2
+ else: direction = 1
+ sent = 0
+
+ while True:#{
+ map0 = dij.subsections[i0]
+ i1 = map0[direction]
+ if i1 == -1: break
+
+ full.append( i0 )
+ sent += 1
+ i0 = i1
+ if sent > 50: break
+ #}
+ #}
+ else:#{
+ full.append( i0 )
+ #}
+ #}
+
+ full.append( path[-2] )
#}
+ return full
#}
-class CV_COMPILE_THIS(bpy.types.Operator):
+def cv_draw_route( route, dij ):
#{
- bl_idname="carve.compile_this"
- bl_label="Compile This collection"
+ pole = Vector((0.2,0.2,10))
+ hat = Vector((1,8,0.2))
+ cc = (route.SR_data.ent_route[0].colour[0],
+ route.SR_data.ent_route[0].colour[1],
+ route.SR_data.ent_route[0].colour[2])
- def execute(_,context):
- #{
- col = bpy.context.collection
- write_model( col.name )
+ cv_draw_ucube(route.matrix_world,cc,Vector((0.5,-7.5,6)),\
+ Vector((0,-6.5,5.5)))
+ cv_draw_ucube(route.matrix_world,cc,pole, Vector(( 0.5, 0.5,0)) )
+ cv_draw_ucube(route.matrix_world,cc,pole, Vector(( 0.5,-13.5,0)) )
+ cv_draw_ucube(route.matrix_world,cc,hat, Vector((-0.5,-6.5, 12)) )
+ cv_draw_ucube(route.matrix_world,cc,hat, Vector((-0.5,-6.5,-1)) )
- return {'FINISHED'}
+ checkpoints = route.SR_data.ent_route[0].gates
+
+ for i in range(len(checkpoints)):#{
+ gi = checkpoints[i].target
+ gj = checkpoints[(i+1)%len(checkpoints)].target
+
+ if gi:#{
+ dest = gi.SR_data.ent_gate[0].target
+ if dest:
+ cv_draw_line_dotted( gi.location, dest.location, cc )
+ gi = dest
+ #}
+
+ if gi==gj: continue # error?
+ if not gi or not gj: continue
+
+ path = solve_graph( dij, gi.name, gj.name )
+
+ if path:#{
+ cv_draw_arrow(gi.location,dij.points[path[0]],cc,1.5)
+ cv_draw_arrow(dij.points[path[len(path)-1]],gj.location,cc,1.5)
+ for j in range(len(path)-1):#{
+ i0 = path[j]
+ i1 = path[j+1]
+ o0 = dij.points[ i0 ]
+ o1 = dij.points[ i1 ]
+ cv_draw_arrow(o0,o1,cc,1.5)
+ #}
+ #}
+ else:#{
+ cv_draw_line_dotted( gi.location, gj.location, cc )
+ #}
#}
#}
-class CV_INTERFACE(bpy.types.Panel):
+def cv_draw():
#{
- bl_idname = "VIEW3D_PT_carve"
- bl_label = "Skate Rift"
- bl_space_type = 'VIEW_3D'
- bl_region_type = 'UI'
- bl_category = "Skate Rift"
+ global cv_view_shader
+ global cv_view_verts
+ global cv_view_colours
+ global cv_view_course_i
- def draw(_, context):
- #{
- layout = _.layout
- layout.prop( context.scene.cv_data, "export_dir" )
-
- col = bpy.context.collection
-
- found_in_export = False
- export_count = 0
- view_layer = bpy.context.view_layer
- for c1 in view_layer.layer_collection.children["export"].children:
- #{
- if not c1.hide_viewport or bpy.context.scene.cv_data.use_hidden:
- export_count += 1
+ cv_view_course_i = 0
+ cv_view_verts = []
+ cv_view_colours = []
- if c1.name == col.name:
- #{
- found_in_export = True
- #}
- #}
+ cv_view_shader.bind()
+ gpu.state.depth_mask_set(False)
+ gpu.state.line_width_set(2.0)
+ gpu.state.face_culling_set('BACK')
+ gpu.state.depth_test_set('LESS')
+ gpu.state.blend_set('NONE')
- box = layout.box()
- if found_in_export:
- #{
- box.label( text=col.name + ".mdl" )
- box.prop( col.cv_data, "pack_textures" )
- box.operator( "carve.compile_this" )
- #}
- else:
- #{
- row = box.row()
- row.enabled=False
- row.label( text=col.name )
- box.label( text="This collection is not in the export group" )
+ route_gates = []
+ route_curves = []
+ routes = []
+
+ for obj in bpy.context.collection.objects:#{
+ if obj.type == 'ARMATURE':#{
+ if obj.data.pose_position == 'REST':
+ draw_skeleton_helpers( obj )
#}
+ else:#{
+ ent_type = obj_ent_type( obj )
- box = layout.box()
- row = box.row()
+ if ent_type == 'ent_gate':#{
+ cv_ent_gate( obj )
+ route_gates += [obj]
+ #}
+ elif ent_type == 'ent_route_node':#{
+ if obj.type == 'CURVE':#{
+ route_curves += [obj]
+ #}
+ #}
+ elif ent_type == 'ent_route':
+ routes += [obj]
+ elif ent_type == 'ent_volume':#{
+ cv_ent_volume( obj )
+ #}
+ elif ent_type == 'ent_audio':#{
+ if obj.SR_data.ent_audio[0].flag_3d:
+ cv_draw_sphere( obj.location, obj.scale[0], (1,1,0) )
+ #}
+ elif ent_type == 'ent_font':#{
+ data = obj.SR_data.ent_font[0]
+
+ for i in range(len(data.variants)):#{
+ sub = data.variants[i].mesh
+ if not sub: continue
+
+ for ch in data.glyphs:#{
+ mini = (ch.bounds[0],ch.bounds[1])
+ maxi = (ch.bounds[2]+mini[0],ch.bounds[3]+mini[1])
+ p0 = sub.matrix_world @ Vector((mini[0],0.0,mini[1]))
+ p1 = sub.matrix_world @ Vector((maxi[0],0.0,mini[1]))
+ p2 = sub.matrix_world @ Vector((maxi[0],0.0,maxi[1]))
+ p3 = sub.matrix_world @ Vector((mini[0],0.0,maxi[1]))
+
+ if i == data.variants_index: cc = (0.5,0.5,0.5)
+ else: cc = (0,0,0)
+
+ cv_view_verts += [p0,p1,p1,p2,p2,p3,p3,p0]
+ cv_view_colours += [cc,cc,cc,cc,cc,cc,cc,cc]
+ #}
+ #}
+ #}
+ elif ent_type == 'ent_skateshop':#{
+ cc = (0.0,0.9,0.6)
+ cc1 = (0.4,0.9,0.2)
+ cc2 = (0.9,0.6,0.1)
+
+ data = obj.SR_data.ent_skateshop[0]
+ display = data.mark_display
+ info = data.mark_info
+ rack = data.mark_rack
+
+ rack_cu = Vector((3.15,2.0,0.1))*0.5
+ rack_co = Vector((0.0,0.0,0.0))
+ display_cu = Vector((0.3,1.2,0.1))*0.5
+ display_co = Vector((0.0,0.0,0.1))*0.5
+ info_cu = Vector((1.2,0.01,0.3))*0.5
+ info_co = Vector((0.0,0.0,0.0))*0.5
+
+ if rack:
+ cv_draw_ucube( rack.matrix_world, cc, rack_cu, rack_co )
+ if display:
+ cv_draw_ucube( display.matrix_world, cc1, display_cu, display_co)
+ if info:
+ cv_draw_ucube( info.matrix_world, cc2, info_cu, info_co )
+ #}
+ #}
+ #}
- split = row.split( factor = 0.3, align=True )
- split.prop( context.scene.cv_data, "use_hidden", text="hidden" )
-
- row1 = split.row()
- if export_count == 0:
- row1.enabled=False
- row1.operator( "carve.compile_all", \
- text=F"Compile all ({export_count} collections)" )
+ dij = create_node_graph( route_curves, route_gates )
+
+ #cv_draw_route_map( route_nodes )
+ for route in routes:#{
+ cv_draw_route( route, dij )
#}
-#}
+ cv_draw_lines()
+ return
+#}
-classes = [CV_OBJ_SETTINGS,CV_OBJ_PANEL,CV_COMPILE,CV_INTERFACE,\
- CV_MESH_SETTINGS, CV_SCENE_SETTINGS, CV_BONE_SETTINGS,\
- CV_BONE_PANEL, CV_COLLECTION_SETTINGS, CV_COMPILE_THIS,\
- CV_MATERIAL_SETTINGS, CV_MATERIAL_PANEL ]
+classes = [ SR_INTERFACE, SR_MATERIAL_PANEL,\
+ SR_COLLECTION_SETTINGS, SR_SCENE_SETTINGS, \
+ SR_COMPILE, SR_COMPILE_THIS, SR_MIRROR_BONE_X,\
+ \
+ SR_OBJECT_ENT_GATE, SR_MESH_ENT_GATE, SR_OBJECT_ENT_SPAWN, \
+ SR_OBJECT_ENT_ROUTE_ENTRY, SR_UL_ROUTE_NODE_LIST, \
+ SR_OBJECT_ENT_ROUTE, SR_OT_ROUTE_LIST_NEW_ITEM,\
+ SR_OT_GLYPH_LIST_NEW_ITEM, SR_OT_GLYPH_LIST_DEL_ITEM,\
+ SR_OT_GLYPH_LIST_MOVE_ITEM,\
+ SR_OT_AUDIO_LIST_NEW_ITEM,SR_OT_AUDIO_LIST_DEL_ITEM,\
+ SR_OT_FONT_VARIANT_LIST_NEW_ITEM,SR_OT_FONT_VARIANT_LIST_DEL_ITEM,\
+ SR_OT_COPY_ENTITY_DATA, \
+ SR_OBJECT_ENT_VOLUME, \
+ SR_UL_AUDIO_LIST, SR_OBJECT_ENT_AUDIO_FILE_ENTRY,\
+ SR_OT_ROUTE_LIST_DEL_ITEM,\
+ SR_OBJECT_ENT_AUDIO,SR_OBJECT_ENT_MARKER,SR_OBJECT_ENT_GLYPH,\
+ SR_OBJECT_ENT_FONT_VARIANT,
+ SR_OBJECT_ENT_GLYPH_ENTRY,\
+ SR_UL_FONT_VARIANT_LIST,SR_UL_FONT_GLYPH_LIST,\
+ SR_OBJECT_ENT_FONT,SR_OBJECT_ENT_TRAFFIC,SR_OBJECT_ENT_SKATESHOP,\
+ \
+ SR_OBJECT_PROPERTIES, SR_LIGHT_PROPERTIES, SR_BONE_PROPERTIES,
+ SR_MESH_PROPERTIES, SR_MATERIAL_PROPERTIES \
+ ]
def register():
#{
- global cv_view_draw_handler
-
for c in classes:
bpy.utils.register_class(c)
- bpy.types.Object.cv_data = bpy.props.PointerProperty(type=CV_OBJ_SETTINGS)
- bpy.types.Mesh.cv_data = bpy.props.PointerProperty(type=CV_MESH_SETTINGS)
- bpy.types.Scene.cv_data = bpy.props.PointerProperty(type=CV_SCENE_SETTINGS)
- bpy.types.Bone.cv_data = bpy.props.PointerProperty(type=CV_BONE_SETTINGS)
- bpy.types.Collection.cv_data = \
- bpy.props.PointerProperty(type=CV_COLLECTION_SETTINGS)
- bpy.types.Material.cv_data = \
- bpy.props.PointerProperty(type=CV_MATERIAL_SETTINGS)
+ bpy.types.Scene.SR_data = \
+ bpy.props.PointerProperty(type=SR_SCENE_SETTINGS)
+ bpy.types.Collection.SR_data = \
+ bpy.props.PointerProperty(type=SR_COLLECTION_SETTINGS)
+
+ bpy.types.Object.SR_data = \
+ bpy.props.PointerProperty(type=SR_OBJECT_PROPERTIES)
+ bpy.types.Light.SR_data = \
+ bpy.props.PointerProperty(type=SR_LIGHT_PROPERTIES)
+ bpy.types.Bone.SR_data = \
+ bpy.props.PointerProperty(type=SR_BONE_PROPERTIES)
+ bpy.types.Mesh.SR_data = \
+ bpy.props.PointerProperty(type=SR_MESH_PROPERTIES)
+ bpy.types.Material.SR_data = \
+ bpy.props.PointerProperty(type=SR_MATERIAL_PROPERTIES)
+ global cv_view_draw_handler
cv_view_draw_handler = bpy.types.SpaceView3D.draw_handler_add(\
cv_draw,(),'WINDOW','POST_VIEW')
#}
def unregister():
#{
- global cv_view_draw_handler
-
for c in classes:
bpy.utils.unregister_class(c)
+ global cv_view_draw_handler
bpy.types.SpaceView3D.draw_handler_remove(cv_view_draw_handler,'WINDOW')
#}
#{
data = bytearray()
- print(F" . Encoding {img.name}.qoi[{img.size[0]},{img.size[1]}]")
+ print(F"{' ':<30}",end='\r')
+ print(F"[QOI] Encoding {img.name}.qoi[{img.size[0]},{img.size[1]}]",end='\r')
index = [ qoi_rgba_t() for _ in range(64) ]
px.a = c_uint8(255)
px_len = img.size[0] * img.size[1]
-
paxels = [ int(min(max(_,0),1)*255) for _ in img.pixels ]
- for px_pos in range( px_len ):
- #{
+ for px_pos in range( px_len ): #{
idx = px_pos * img.channels
nc = img.channels-1
px.b = paxels[idx+min(2,nc)]
px.a = paxels[idx+min(3,nc)]
- if qoi_eq( px, px_prev ):
- #{
+ if qoi_eq( px, px_prev ): #{
run += 1
- if (run == 62) or (px_pos == px_len-1):
- #{
+ if (run == 62) or (px_pos == px_len-1): #{
data.extend( bytearray( c_uint8(QOI_OP_RUN | (run-1))) )
run = 0
#}
#}
- else:
- #{
- if run > 0:
- #{
+ else: #{
+ if run > 0: #{
data.extend( bytearray( c_uint8(QOI_OP_RUN | (run-1))) )
run = 0
#}
index_pos = qoi_colour_hash(px) % 64
- if qoi_eq( index[index_pos], px ):
- #{
+ if qoi_eq( index[index_pos], px ): #{
data.extend( bytearray( c_uint8(QOI_OP_INDEX | index_pos)) )
#}
- else:
- #{
+ else: #{
index[ index_pos ].r = px.r
index[ index_pos ].g = px.g
index[ index_pos ].b = px.b
index[ index_pos ].a = px.a
- if px.a == px_prev.a:
- #{
+ if px.a == px_prev.a: #{
vr = int(px.r) - int(px_prev.r)
vg = int(px.g) - int(px_prev.g)
vb = int(px.b) - int(px_prev.b)
data.extend( bytearray( c_uint8(op) ) )
data.extend( bytearray( c_uint8(delta) ))
#}
- else:
- #{
+ else: #{
data.extend( bytearray( c_uint8(QOI_OP_RGB) ) )
data.extend( bytearray( c_uint8(px.r) ))
data.extend( bytearray( c_uint8(px.g) ))
data.extend( bytearray( c_uint8(px.b) ))
#}
#}
- else:
- #{
+ else: #{
data.extend( bytearray( c_uint8(QOI_OP_RGBA) ) )
data.extend( bytearray( c_uint8(px.r) ))
data.extend( bytearray( c_uint8(px.g) ))
for i in range(7):
data.extend( bytearray( c_uint8(0) ))
data.extend( bytearray( c_uint8(1) ))
- bytearray_align_to( data, 16, 0 )
+ bytearray_align_to( data, 16, b'\x00' )
return data
#}