TurboFisto
[carveJwlIkooP6JGAAIwe30JlM.git] / blender_export.py
index 3b3f5cd9393f28fc995c555944426b0d2bdb3266..dcab66f51953ffbf08fa2b2d080b4e34328995ab 100644 (file)
@@ -16,6 +16,16 @@ 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
+}
+
 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 +185,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 +212,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 +226,62 @@ 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)]
+#}
+
 def obj_ent_type( obj ):
 #{
    if obj.type == 'ARMATURE': return 'mdl_armature'
@@ -203,14 +289,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 +452,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 +471,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 )
@@ -1005,6 +1099,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 ''}")
@@ -1078,7 +1173,7 @@ def sr_compile( collection ):
          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.pstr_name = sr_compile_string( obj_data.alias )
             route.checkpoints_start = checkpoint_count
             route.checkpoints_count = 0
 
@@ -1142,6 +1237,74 @@ def sr_compile( collection ):
             rn.co[2] = -obj.location[1]
             sr_ent_push( rn )
          #}
+         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:#{
+               target = obj_data.target
+               volume.target.type = sr_entity_alias[obj_ent_type(target)]
+               volume.target.index = sr_compile.entity_ids[ target.name ]
+            #}
+
+            sr_ent_push(volume)
+         #}
       #}
    #}
    
@@ -1516,7 +1679,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 +1719,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 +1731,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 +1764,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 +1781,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 +1872,111 @@ 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_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_type: bpy.props.EnumProperty(
       name="Type",
@@ -1663,7 +1984,10 @@ 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)],
       update=sr_on_type_change
    )
 #}
@@ -2283,6 +2607,28 @@ def cv_ent_gate( obj ):
       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]
@@ -2452,6 +2798,13 @@ def cv_draw():
             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) )
+         #}
       #}
    #}
    
@@ -2470,8 +2823,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_PROPERTIES, SR_LIGHT_PROPERTIES, SR_BONE_PROPERTIES, 
             SR_MESH_PROPERTIES, SR_MATERIAL_PROPERTIES \