scraped a small amount of crap
[carveJwlIkooP6JGAAIwe30JlM.git] / blender_export.py
index dee0d8f1ee0102774ce2f48da898b035f46f9bc6..cbd2a1989087302c7844d535e44cd73b5aaf1a3a 100644 (file)
@@ -16,6 +16,17 @@ bl_info = {
    "category":"Import/Export",
 }
 
+sr_entity_alias = {
+   'ent_gate': 1,
+   'ent_spawn': 2,
+   'ent_route_node': 3,
+   'ent_route': 4,
+   'ent_water': 5,
+   'ent_volume': 6,
+   'ent_audio': 7,
+   'ent_marker': 8
+}
+
 class mdl_vert(Structure):              # 48 bytes. Quite large. Could compress
 #{                                      # the normals and uvs to i16s. Not an
    _pack_ = 1                           # real issue, yet.
@@ -175,6 +186,26 @@ class ent_path_index(Structure):
    _fields_ = [("index",c_uint16)]
 #}
 
+class vg_audio_clip(Structure):
+#{
+   _fields_ = [("path",c_uint64),
+               ("flags",c_uint32),
+               ("size",c_uint32),
+               ("data",c_uint64)]
+#}
+
+class union_file_audio_clip(Union):
+#{
+   _fields_ = [("file",mdl_file),
+               ("reserved",vg_audio_clip)]
+#}
+
+class ent_audio_clip(Structure):
+#{
+   _fields_ = [("_anon",union_file_audio_clip),
+               ("probability",c_float)]
+#}
+
 class ent_checkpoint(Structure):
 #{
    _fields_ = [("gate_index",c_uint16),
@@ -182,7 +213,7 @@ class ent_checkpoint(Structure):
                ("path_count",c_uint16)]
 #}
 
-class ent_route(Structure):
+class ent_route(Structure): 
 #{
    _fields_ = [("transform",mdl_transform),
                ("pstr_name",c_uint32),
@@ -196,6 +227,68 @@ class ent_route(Structure):
                ("latest_pass",c_double)]
 #}
 
+class ent_water(Structure):
+#{
+   _fields_ = [("transform",mdl_transform),
+               ("max_dist",c_float),
+               ("reserved0",c_uint32),
+               ("reserved1",c_uint32)]
+#}
+
+class volume_trigger(Structure):
+#{
+   _fields_ = [("event",c_uint32),
+               ("blank",c_uint32)]
+#}
+
+class volume_particles(Structure):
+#{
+   _fields_ = [("blank",c_uint32),
+               ("blank2",c_uint32)]
+#}
+
+class volume_union(Union):
+#{
+   _fields_ = [("trigger",volume_trigger),
+               ("particles",volume_particles)]
+#}
+
+class ent_index(Structure):
+#{
+   _fields_ = [("type",c_uint32),
+               ("index",c_uint32)]
+#}
+
+class ent_volume(Structure):
+#{
+   _fields_ = [("transform",mdl_transform),
+               ("to_world",(c_float*3)*4),
+               ("to_local",(c_float*3)*4),
+               ("type",c_uint32),
+               ("target",ent_index),
+               ("_anon",volume_union)]
+#}
+
+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)]
+#}
+
+class ent_marker(Structure):
+#{
+   _fields_ = [("transform",mdl_transform),
+               ("name",c_uint32)]
+#}
+
 def obj_ent_type( obj ):
 #{
    if obj.type == 'ARMATURE': return 'mdl_armature'
@@ -203,14 +296,14 @@ def obj_ent_type( obj ):
    else: return obj.SR_data.ent_type
 #}
 
-def sr_filter_ent_type( obj, ent_type ):
+def sr_filter_ent_type( obj, ent_types ):
 #{
    if obj == bpy.context.active_object: return False
 
    for c0 in obj.users_collection:#{
       for c1 in bpy.context.active_object.users_collection:#{
          if c0 == c1:#{
-            return ent_type == obj_ent_type( obj )
+            return  obj_ent_type( obj ) in ent_types
          #}
       #}
    #}
@@ -366,9 +459,6 @@ def material_info(mat):
 
                   _graph_read( from_node_def, from_node, depth+1 )
                #}
-               
-               # No definition! :(
-               #  TODO: Make a warning for this?
             #}
             else:#{
                if "default" in link_def:#{
@@ -388,6 +478,17 @@ def material_info(mat):
    return info
 #}
 
+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 ):
 #{
    file.path = sr_compile_string( path )
@@ -1003,8 +1104,7 @@ def sr_compile( collection ):
       #}
    #}
 
-   checkpoint_count = 0
-   pathindice_count = 0
+   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 ''}")
@@ -1072,79 +1172,187 @@ def sr_compile( collection ):
             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.name )
+            spawn.pstr_name = sr_compile_string( obj_data.alias )
             sr_ent_push( spawn )
          #}
-         elif ent_type == 'ent_route': #{
-            obj_data = obj.SR_data.ent_route[0]
-            route = ent_route()
-            route.pstr_name = sr_compile_string( obj_data.alias ) #TODO
-            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
-            route_nodes = []
-
-            for uc in obj.users_collection[0].objects:#{
-               uc_type = obj_ent_type( uc )
-               if uc_type == 'ent_gate' or uc_type == 'ent_route_node':
-                  route_nodes += [uc]
+         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
             #}
-            graph = node_graph( route_nodes )
+            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 )
 
-            for i in range(len(checkpoints)):#{
-               gi = checkpoints[i].target
-               gj = checkpoints[(i+1)%len(checkpoints)].target
-               gate = gi
+            if obj_data.target:#{
+               target = obj_data.target
+               volume.target.type = sr_entity_alias[obj_ent_type(target)]
+               volume.target.index = sr_compile.entity_ids[ target.name ]
+            #}
 
-               if gi:#{
-                  dest = gi.SR_data.ent_gate[0].target
-                  gi = dest
-               #}
+            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)
+         #}
+      #}
+   #}
 
-               if gi==gj: continue # error?
-               if not gi or not gj: continue
+   def _children( col ):#{
+      yield col
+      for c in col.children:#{
+         yield from _children(c)
+      #}
+   #}
 
-               checkpoint = ent_checkpoint()
-               checkpoint.gate_index = sr_compile.entity_ids[gate.name]
-               checkpoint.path_start = pathindice_count
-               checkpoint.path_count = 0
-               
-               path = dijkstra( graph, gj.name, gi.name )
-               if path:#{
-                  for pi in range(1,len(path)-1):#{
-                     pathindice = ent_path_index()
-                     pathindice.index = sr_compile.entity_ids[path[pi]]
-                     sr_ent_push( pathindice )
-
-                     checkpoint.path_count += 1
-                     pathindice_count += 1
-                  #}
+   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 = []
+
+      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]
                #}
-               
-               sr_ent_push( checkpoint )
-               route.checkpoints_count += 1
-               checkpoint_count += 1
             #}
-
-            sr_ent_push( route )
+            elif ent_type == 'ent_route':
+               routes += [obj]
          #}
-         elif ent_type == 'ent_route_node':#{
-            rn = ent_route_node()
-            rn.co[0] =  obj.location[0]
-            rn.co[1] =  obj.location[2]
-            rn.co[2] = -obj.location[1]
-            sr_ent_push( rn )
+      #}
+
+      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 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 = {}
@@ -1446,7 +1654,7 @@ class SR_INTERFACE(bpy.types.Panel):
          elif active_object.type == 'LIGHT': #{
             _draw_prop_collection( [active_object.data.SR_data] )
          #}
-         elif active_object.type == 'EMPTY' or active_object.type == 'MESH': #{
+         elif active_object.type in ['EMPTY','CURVE','MESH']:#{
             box.prop( active_object.SR_data, "ent_type" )
             ent_type = active_object.SR_data.ent_type
             
@@ -1516,7 +1724,7 @@ class SR_MATERIAL_PANEL(bpy.types.Panel):
 def sr_get_type_enum( scene, context ):
 #{
    items = [('none','None',"")]
-   mesh_entities=['ent_gate']
+   mesh_entities=['ent_gate','ent_water']
    point_entities=['ent_spawn','ent_route_node','ent_route']
 
    for e in point_entities: items += [(e,e,'')]
@@ -1556,7 +1764,7 @@ 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'))
+               poll=lambda self,obj: sr_filter_ent_type(obj,['ent_gate']))
 #}
 
 class SR_MESH_ENT_GATE(bpy.types.PropertyGroup):
@@ -1568,7 +1776,7 @@ 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'))
+               poll=lambda self,obj: sr_filter_ent_type(obj,['ent_gate']))
 #}
 
 class SR_UL_ROUTE_NODE_LIST(bpy.types.UIList):
@@ -1601,7 +1809,7 @@ class SR_OT_ROUTE_LIST_DEL_ITEM(bpy.types.Operator):
    @classmethod 
    def poll(cls, context):#{
       active_object = context.active_object
-      if obj_ent_type == 'ent_gate':#{
+      if obj_ent_type(active_object) == 'ent_gate':#{
          return active_object.SR_data.ent_route[0].gates
       #}
       else: return False
@@ -1618,6 +1826,64 @@ class SR_OT_ROUTE_LIST_DEL_ITEM(bpy.types.Operator):
    #}
 #}
 
+class SR_OT_AUDIO_LIST_NEW_ITEM(bpy.types.Operator):
+#{
+   bl_idname = "skaterift.al_new_entry"
+   bl_label = "Add file"
+   
+   def execute(self, context):#{
+      active_object = context.active_object
+      active_object.SR_data.ent_audio[0].files.add()
+      return{'FINISHED'}
+   #}
+#}
+
+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):#{
+      active_object = context.active_object
+      lista = active_object.SR_data.ent_audio[0].files
+      index = active_object.SR_data.ent_audio[0].file_index
+      lista.remove(index) 
+      active_object.SR_data.ent_audio[0].file_index = \
+            min(max(0, index-1), len(lista) - 1)
+      return{'FINISHED'}
+   #}
+#}
+
+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_OBJECT_ENT_ROUTE(bpy.types.PropertyGroup):
 #{
    gates: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_ROUTE_ENTRY)
@@ -1651,11 +1917,117 @@ class SR_OBJECT_ENT_ROUTE(bpy.types.PropertyGroup):
    #}
 #}
 
+class SR_OBJECT_ENT_VOLUME(bpy.types.PropertyGroup):
+#{
+   subtype: bpy.props.EnumProperty(
+      name="Subtype",
+      items=[('0','Trigger',''),
+             ('1','Particles (0.1s)','')]
+   )
+
+   target: bpy.props.PointerProperty( \
+           type=bpy.types.Object, name="Target", \
+           poll=lambda self,obj: sr_filter_ent_type(obj,['ent_audio']))
+
+   @staticmethod
+   def sr_inspector( layout, data ):
+   #{
+      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)
+   file_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)
+   
+   max_channels: bpy.props.IntProperty( name="Max Channels", default=1 )
+   volume: bpy.props.FloatProperty( name="Volume",default=1.0 )
+
+   @staticmethod
+   def sr_inspector( layout, data ):
+   #{
+      layout.prop( data[0], 'formato' )
+      layout.prop( data[0], 'volume' )
+
+      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' )
+
+      box = layout.box()
+      box.label( text='Flags' )
+      box.prop( data[0], 'flag_3d' )
+      if data[0].flag_3d: box.prop( data[0], 'flag_nodoppler' )
+
+      box.prop( data[0], 'flag_loop' )
+      box.prop( data[0], 'flag_auto' )
+
+      split = layout.split(factor=0.7)
+      c = split.column()
+      c.label( text='Filepath' )
+      c = split.column()
+      c.label( text='Chance (0.1s)' )
+
+      layout.prop( data[0], 'probability_curve' )
+
+      layout.template_list('SR_UL_AUDIO_LIST', 'Files', \
+                            data[0], 'files', data[0], 'file_index', rows=5)
+
+      row = layout.row()
+      row.operator( 'skaterift.al_new_entry', text='Add' )
+      row.operator( 'skaterift.al_del_entry', text='Remove' )
+   #}
+#}
+
+class SR_OBJECT_ENT_MARKER(bpy.types.PropertyGroup):
+#{
+   alias: bpy.props.StringProperty()
+#}
+
 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_type: bpy.props.EnumProperty(
       name="Type",
@@ -1663,7 +2035,11 @@ class SR_OBJECT_PROPERTIES(bpy.types.PropertyGroup):
              ('ent_gate','Gate','', 1),
              ('ent_spawn','Spawn','', 2),
              ('ent_route_node', 'Route Node', '', 3 ),
-             ('ent_route', 'Route', '', 4)],
+             ('ent_route', 'Route', '', 4),
+             ('ent_water', 'Water Surface', '', 5),
+             ('ent_volume', 'Volume', '', 6 ),
+             ('ent_audio', 'Audio Files', '', 7),
+             ('ent_marker', 'Marker', '', 8)],
       update=sr_on_type_change
    )
 #}
@@ -1741,7 +2117,8 @@ class SR_MATERIAL_PROPERTIES(bpy.types.PropertyGroup):
       ('0','concrete',''),
       ('1','wood',''),
       ('2','grass',''),
-      ('3','tiles','')
+      ('3','tiles',''),
+      ('4','metal','')
       ])
    
    collision: bpy.props.BoolProperty( \
@@ -1818,8 +2195,7 @@ def cv_draw_sphere( pos, radius, colour ):
    
    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)
@@ -1853,8 +2229,7 @@ def cv_draw_halfsphere( pos, tx, ty, tz, radius, colour ):
    
    pi = 3.14159265358979323846264
 
-   for i in range(16):
-   #{
+   for i in range(16):#{
       t = ((i+1.0) * 1.0/16.0) * pi
       s = math.sin(t)
       c = math.cos(t)
@@ -1901,8 +2276,7 @@ def cv_draw_ucube( transform, colour, s=Vector((1,1,1)), o=Vector((0,0,0)) ):
    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])]
@@ -1938,14 +2312,12 @@ def cv_draw_line2( p0, p1, c0, c1 ):
 #
 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]
@@ -1975,7 +2347,7 @@ def cv_draw_arrow( p0, p1, c0, size=0.15 ):
    
    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 ):
@@ -1992,7 +2364,7 @@ def cv_draw_line_dotted( p0, p1, c0, dots=10 ):
       cv_view_verts += [p2,p3]
       cv_view_colours += [c0,c0]
    #}
-   cv_draw_lines()
+   #cv_draw_lines()
 #}
 
 # Drawhandles of a bezier control point
@@ -2017,8 +2389,7 @@ def cv_draw_bezier( p0,h0,p1,h1,c0,c1 ):
    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
 
@@ -2129,7 +2500,7 @@ def draw_cone_twist( center, vx, vy, va ):
    size = 0.12
 
    cv_view_verts += [center, center+va*size]
-   cv_view_colours += [ (1,1,1,1), (1,1,1,1) ]
+   cv_view_colours += [ (1,1,1), (1,1,1) ]
 
    for x in range(32):#{
       t0 = (x/32) * math.tau
@@ -2147,7 +2518,7 @@ def draw_cone_twist( center, vx, vy, va ):
       col1 = ( abs(c1), abs(s1), 0.0, 1.0 )
 
       cv_view_verts += [center, p0, p0, p1]
-      cv_view_colours += [ (0,0,0,0), col0, col0, col1 ]
+      cv_view_colours += [ (0,0,0), col0, col0, col1 ]
    #}
 
    cv_draw_lines()
@@ -2192,7 +2563,7 @@ def draw_skeleton_helpers( obj ):
 
             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)]
+            cv_view_colours += [(0.5,0.5,0.5),(0.5,0.5,0.5)]
          #}
       #}
       elif bone.SR_data.collider == '2':#{
@@ -2220,7 +2591,7 @@ def draw_skeleton_helpers( obj ):
          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 )
 
-         colour = [0.2,0.2,0.2,1.0]
+         colour = [0.2,0.2,0.2]
          colour[major_axis] = 0.5
 
          cv_draw_halfsphere( p0, -v1, ty, tx, r, colour )
@@ -2269,19 +2640,50 @@ def cv_ent_gate( obj ):
 
    indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(7,8)]
 
+   r3d = bpy.context.area.spaces.active.region_3d
+
+   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 += [(1,1,0,1),(1,1,0,1)]
+      cv_view_colours += [cc,cc]
    #}
 
-   sw = (0.4,0.4,0.4,0.2)
+   sw = (0.4,0.4,0.4)
    if data.target != None:
       cv_draw_arrow( obj.location, data.target.location, sw )
 #}
 
+def cv_ent_volume( obj ):
+#{
+   global cv_view_verts, cv_view_colours
+
+   data = obj.SR_data.ent_volume[0]
+
+   if data.subtype == '0':#{
+      cv_draw_ucube( obj.matrix_world, (0,1,0) )
+
+      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) )
+
+      if data.target:#{
+         cv_draw_line( obj.location, data.target.location, (1,1,0) )
+      #}
+   #}
+#}
+
 def dijkstra( graph, start_node, target_node ):
 #{
    unvisited = [_ for _ in graph]
@@ -2327,62 +2729,157 @@ def dijkstra( graph, start_node, target_node ):
    return path
 #}
 
-def node_graph( route_nodes ):
+class dij_graph():
+#{
+   def __init__(_,points,graph,subsections):#{
+      _.points = points
+      _.graph = graph
+      _.subsections = subsections
+   #}
+#}
+
+def create_node_graph( curves, gates ):
 #{
+   # add endpoints of curves
    graph = {}
-   for n in route_nodes:
-      graph[n.name] = {}
+   route_points = []
+   subsections = []
+   point_count = 0
+   spline_count = 0
 
-   for i in range(len(route_nodes)-1):#{
-      for j in range(i+1, len(route_nodes)):#{
-         ni = route_nodes[i]
-         nj = route_nodes[j]
+   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
 
-         v0 = ni.location - nj.location
+         dist = round(spline.calc_length(),2)
 
-         gate = None
+         ia = point_count
+         ib = point_count+l-1
 
-         if ni.SR_data.ent_type == 'ent_gate':
-            gate = ni
+         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)))
+
+            previous = ia+i-1
+            proxima = ia+i+1
 
-         if nj.SR_data.ent_type == 'ent_gate':#{
-            if gate: continue
-            gate = nj
+            if i == 0: previous = -1
+            if i == len(spline.points)-1: proxima = -1
+
+            subsections.append((spline_count,previous,proxima))
+            point_count += 1
          #}
 
-         if gate:#{
-            v1 = gate.matrix_world.to_3x3() @ Vector((0,-1,0))
-            if gate.SR_data.ent_gate[0].target:
-               if v1.dot(v0) > 0.0: continue
-            else:
-               if v1.dot(v0) < 0.0: continue
+         spline_count += 1
+      #}
+   #}
+
+   # 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
          #}
+      #}
+   #}
+
+   # 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
+
+      graph[ gate.name ] = {}
+
+      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 = v0.magnitude
+         dist = round(v0.magnitude,2)
 
-         if dist > 25.0: continue
-         graph[route_nodes[i].name][route_nodes[j].name] = dist
-         graph[route_nodes[j].name][route_nodes[i].name] = dist
+         if dist < 10.0:#{
+            graph[ gate.name ][ ni ] = dist
+            graph[ ni ][ gate.name ] = dist
+         #}
       #}
    #}
 
-   return graph
+   return dij_graph(route_points,graph,subsections)
 #}
 
-def cv_draw_route( route, route_nodes ):
+def solve_graph( dij, start, end ):
 #{
-   pole = Vector((0.2,0.2,20))
-   hat = Vector((20,2.0,0.2))
-   cc = route.SR_data.ent_route[0].colour
+   path = dijkstra( dij.graph, end, start )
+   full = []
+
+   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]
 
-   cv_draw_ucube( route.matrix_world, cc, Vector((20,1,10)) )
-   cv_draw_ucube( route.matrix_world, cc, pole, Vector((-20,1,-10)) )
-   cv_draw_ucube( route.matrix_world, cc, pole, Vector(( 20,1,-10)) )
-   cv_draw_ucube( route.matrix_world, cc, hat, Vector((0,-1, 10)) )
-   cv_draw_ucube( route.matrix_world, cc, hat, Vector((0,-1,-10)) )
+         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
+#}
+
+def cv_draw_route( route, dij ):
+#{
+   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])
+
+   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)) )
 
    checkpoints = route.SR_data.ent_route[0].gates
-   graph = node_graph( route_nodes )
 
    for i in range(len(checkpoints)):#{
       gi = checkpoints[i].target
@@ -2398,13 +2895,17 @@ def cv_draw_route( route, route_nodes ):
       if gi==gj: continue # error?
       if not gi or not gj: continue
 
-      path = dijkstra( graph, gj.name, gi.name )
+      path = solve_graph( dij, gi.name, gj.name )
 
       if path:#{
-         for sj in range(len(path)-1):#{
-            o0 = bpy.data.objects[ path[sj] ]
-            o1 = bpy.data.objects[ path[sj+1] ]
-            cv_draw_arrow(o0.location,o1.location,cc,1.5)
+         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:#{
@@ -2431,7 +2932,8 @@ def cv_draw():
    gpu.state.depth_test_set('LESS')
    gpu.state.blend_set('NONE')
 
-   route_nodes = []
+   route_gates = []
+   route_curves = []
    routes = []
 
    for obj in bpy.context.collection.objects:#{
@@ -2444,18 +2946,30 @@ def cv_draw():
 
          if ent_type == 'ent_gate':#{
             cv_ent_gate( obj )
-            route_nodes += [obj]
+            route_gates += [obj]
+         #}
+         elif ent_type == 'ent_route_node':#{
+            if obj.type == 'CURVE':#{
+               route_curves += [obj]
+            #}
          #}
-         elif ent_type == 'ent_route_node':
-            route_nodes += [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) )
+         #}
       #}
    #}
+
+   dij = create_node_graph( route_curves, route_gates )
    
    #cv_draw_route_map( route_nodes )
    for route in routes:#{
-      cv_draw_route( route, route_nodes )
+      cv_draw_route( route, dij )
    #}
 
    cv_draw_lines()
@@ -2468,8 +2982,12 @@ classes = [ SR_INTERFACE, SR_MATERIAL_PANEL,\
             \
             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_OBJECT_ENT_ROUTE, SR_OT_ROUTE_LIST_NEW_ITEM,\
+            SR_OT_AUDIO_LIST_NEW_ITEM,SR_OT_AUDIO_LIST_DEL_ITEM,\
+            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_PROPERTIES, SR_LIGHT_PROPERTIES, SR_BONE_PROPERTIES, 
             SR_MESH_PROPERTIES, SR_MATERIAL_PROPERTIES \