From 4b8aac300ee193cfa12011dfe0238cfe7d7ffce7 Mon Sep 17 00:00:00 2001 From: hgn Date: Sat, 1 Apr 2023 03:51:55 +0100 Subject: [PATCH] entities zones --- blender_export.py | 346 +++++++++++++++++++++++++++++++++++++++- entity.h | 73 +++++++++ maps_src/mp_gridmap.mdl | Bin 694432 -> 697368 bytes player_skate.c | 14 +- world.h | 165 ++++++++++++------- world_gen.h | 51 ++++++ world_volumes.h | 43 ++--- 7 files changed, 598 insertions(+), 94 deletions(-) diff --git a/blender_export.py b/blender_export.py index 07179e7..a367cc1 100644 --- a/blender_export.py +++ b/blender_export.py @@ -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), @@ -204,6 +234,54 @@ class ent_water(Structure): ("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' @@ -211,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 #} #} #} @@ -393,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 ) @@ -1010,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 ''}") @@ -1153,6 +1243,58 @@ def sr_compile( collection ): 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.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 + + 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) + #} #} #} @@ -1567,7 +1709,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): @@ -1579,7 +1721,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): @@ -1612,7 +1754,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 @@ -1629,6 +1771,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) @@ -1662,11 +1862,107 @@ 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 ) + 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 == '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", @@ -1675,7 +1971,9 @@ class SR_OBJECT_PROPERTIES(bpy.types.PropertyGroup): ('ent_spawn','Spawn','', 2), ('ent_route_node', 'Route Node', '', 3 ), ('ent_route', 'Route', '', 4), - ('ent_water', 'Water Surface', '', 5)], + ('ent_water', 'Water Surface', '', 5), + ('ent_volume', 'Volume', '', 6 ), + ('ent_audio', 'Audio Files', '', 7)], update=sr_on_type_change ) #} @@ -2295,6 +2593,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] @@ -2464,6 +2784,12 @@ 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':#{ + cv_draw_sphere( obj.location, obj.scale[0], (1,1,0) ) + #} #} #} @@ -2482,8 +2808,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 \ diff --git a/entity.h b/entity.h index 2c70b82..490737d 100644 --- a/entity.h +++ b/entity.h @@ -11,6 +11,32 @@ typedef struct ent_path_index ent_path_index; typedef struct ent_checkpoint ent_checkpoint; typedef struct ent_route ent_route; typedef struct ent_water ent_water; +typedef struct ent_audio_clip ent_audio_clip; +typedef struct volume_particles volume_particles; +typedef struct volume_trigger volume_trigger; +typedef struct ent_volume ent_volume; +typedef struct ent_audio ent_audio; +typedef struct ent_index ent_index; + +enum entity_alias{ + k_ent_gate = 1, + k_ent_spawn = 2, + k_ent_route_node = 3, + k_ent_route = 4, + k_ent_water = 5, + k_ent_volume = 6, + k_ent_audio = 7 +}; + +struct ent_index{ + u32 type, + index; +}; + +enum entity_function{ + k_ent_function_trigger, + k_ent_function_particle_spawn +}; struct ent_spawn{ mdl_transform transform; @@ -109,4 +135,51 @@ struct ent_water{ u32 reserved0, reserved1; }; +struct ent_audio_clip{ + union{ + mdl_file file; + audio_clip clip; + }; + + float probability; +}; + +struct volume_particles{ + u32 blank, blank2; +}; + +struct volume_trigger{ + u32 event, blank; +}; + +enum volume_subtype{ + k_volume_subtype_trigger, + k_volume_subtype_particle +}; + +struct ent_volume{ + mdl_transform transform; + m4x3f to_world, to_local; + u32 type; + + ent_index target; + + union{ + volume_trigger trigger; + volume_particles particles; + }; +}; + +struct ent_audio{ + mdl_transform transform; + u32 flags, + clip_start, + clip_count; + float volume, crossfade; + u32 channel_behaviour, + group, + probability_curve, + max_channels; +}; + #endif /* ENTITY_H */ diff --git a/maps_src/mp_gridmap.mdl b/maps_src/mp_gridmap.mdl index 726aaa877c4142926691c5b4701ff79d4f644f02..ceb3721c51f0b0173603e197c56301a2d30c137a 100644 GIT binary patch delta 13803 zcmb_@30#fY|NlJ?S1zJPnL$y*RV0bBcAwKC!;pk)OOjm65W=-Ia8zhp%7RtRcs)9=9D_PK_>~z9=!p0;zo>%j*FS1 znKUM5oJ8QzSSG6~m6d%{G}EVOCuyWES?|WFyBi4jd%8iV-oUGlwqtlS| zC37WpY<;Lpzr*O0e(%kV89xTz7&Bo!+TQm`{)L@7Xyl`GfLgvcPtE zs+)Q1a_Z$BSl4h}otiT?t_nU9aw7Qdr^gP2c-0pwX?#7CdkoC8&$=3XXJ~ZZ!A&$y6S^~swU-7hv#>3Kj*Q@Gj_g;QwIlk+HJl&JrVD;&`xu4$nCGh=| zw%pSt&E9Ra=Zl4xY!ZePJkEX3T`K=gLWjRgcs8WO#^CIsybtRAUom)D^e(R!Z2Yap z7s3CH2D-m%Ah~*F6?nCuAe$%!)_?qxxCBc5d;9 zz(c4|0Lf`Xxxn@?dC8l6)pFWbl|{1@a+I@3MRKh+suh`S_*K%%*#qWDQPsfQuf=&| z=BueZoYJc?4@JOv3V63<8edKM)j+zAly+zKu?Yg(KqU(1!~Dp#21}Fg?c9vt zr|cRa`y|25xV?_ZcNwfT*fgeiGWLqGX}efH!Jw(d>;(DLJf-Cg#n)zSSp&+d4T@(L zcgyML{ydQiYcmTQN$GW<{Zlg4fq>IgfCWsXV`B~4Pcahg zExFbQcC2|+z?)JX7f4+n*z5D#1q=qb#h|axE-^*^nMp>U$2#8IplQe&GuD>$F3i9l z8dR=erf8==ZL~bn@lB*`%nXXbXBL?==S=0>$l4eRT_$HY48UxPbAzO$*xkllu!^ND zHz@tRp}-Aetx)|E6>tsu<&ge!M1+@Wd_W4oT(W)+ed-o}5zyngjt?g1Cd`W+qM#;V z{3pc&Qqrc9hkGdD!_4-Q4!wI~`qU!Uicq+zWFKfiT42-;{mIn1>-u$?6F5 zaPRTPjTGYnX4NR!17VU$B~nqJ!aSLeyvQ{{98R-6*%G#s?3*)-d~em@fM4lca|rw^ zQsgZV8Eh8Ww`3{o0cEs=ftTjT8AlaN<9Srr5~}Cicx=4dY&!p)c?Y%W?yTu> zw8oo7vaW_)Z#G$AocvqEM+YdmHKH;ze=7fql)fN;9DFRtteVEZBegHcS6Zj%#1EXt z+ZeKa85fv4m9<6aIZ(DA^OC)sHPu{Q7W^`O<>1Tt!-cxT-s4Nrj!E&>!Q; zmea2GtUVh>*7m@yYpia59yx-ik+uW4biFa0M^jb@wtx*ccz1*td3fq#v54Y3gT+S5 z>oh$lo*xn&?4n&4Lc=_nONB!Xjw1V- zvD%c(S+t^x{xj9^^ND;sm2qhC(D8(7LZ5j4E9nDa>HO-QIu|HMa5-5eaGhNrQw!Y) zK7c}lpoNySL1=Zq{M@*sB$3~vLaBARK_1L3Rp5^NP;mUJpFI!$wGS^ObytMi1WN3R z5#D6D-W3tYZc}nMmLk99(?q;N?%i3ALTxu!Ty-Fpk0NV#3`Q2Iz68@nhPW@;GyzPl zJ;5cP?88{B{EMO*d^Bmp5I}7x6D{l+$$Nq7E=Bc%ap4}nisEmv{4wSC!Z0q*dmw&y zJC>JGnFM~h@UFPxd@Oe`D8t!Uf$1nU0xayPECRi~vaX01&c*UMr0fHU>nW@c^f*h2 zec)+E*%HJJ);{biV;v~BAA(d*VQRFRC=rEX@rgI)efvCl31zD>pvt5`dD3+1bl zr}7~N?Qqs!Q2f!MyYbp<9bZh@BiI7@^~BT07z)xr|GN~gfj K!Q$ExdsC^)etq3 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 ) - s->aud_air = audio_request_channel( &audio_board[1], flags ); + if( !s->aud_air ){ + s->aud_air = audio_get_first_idle_channel(); + if( s->aud_air ) + audio_channel_init( s->aud_air, &audio_board[1], flags ); + } - if( !s->aud_slide ) - s->aud_slide = audio_request_channel( &audio_board[2], flags ); + if( !s->aud_slide ){ + s->aud_slide = audio_get_first_idle_channel(); + if( s->aud_slide ) + audio_channel_init( s->aud_slide, &audio_board[2], flags ); + } /* brrrrrrrrrrrt sound for tiles and stuff diff --git a/world.h b/world.h index 80bf3b6..08d3ca0 100644 --- a/world.h +++ b/world.h @@ -155,7 +155,11 @@ struct world_instance ent_path_index, ent_checkpoint, ent_route, - ent_water; + ent_water, + + ent_audio_clip, + ent_audio, + ent_volume; ent_gate *rendering_gate; @@ -466,6 +470,78 @@ VG_STATIC void world_init(void) VG_MEMORY_SYSTEM ); } +typedef struct ent_call ent_call; +struct ent_call{ + ent_index ent; + u32 function; + void *data; +}; + +VG_STATIC void entity_call( world_instance *world, ent_call *call ); + +VG_STATIC void ent_volume_call( world_instance *world, ent_call *call ) +{ + ent_volume *volume = mdl_arritm( &world->ent_volume, call->ent.index ); + if( !volume->target.type ) return; + + if( call->function == k_ent_function_trigger ){ + call->ent = volume->target; + + if( volume->type == k_volume_subtype_particle ){ + v3f co; + co[0] = vg_randf()*2.0f-1.0f; + co[1] = vg_randf()*2.0f-1.0f; + co[2] = vg_randf()*2.0f-1.0f; + m4x3_mulv( volume->to_world, co, co ); + + call->function = k_ent_function_particle_spawn; + call->data = co; + + entity_call( world, call ); + } + else if( volume->type == k_volume_subtype_trigger ){ + /* TODO */ + } + } +} + +VG_STATIC void ent_audio_call( world_instance *world, ent_call *call ) +{ + ent_audio *audio = mdl_arritm( &world->ent_audio, call->ent.index ); + + if( call->function == k_ent_function_particle_spawn ){ + float chance = vg_randf()*100.0f, + bar = 0.0f; + + for( u32 i=0; iclip_count; i++ ){ + ent_audio_clip *clip = mdl_arritm( &world->ent_audio_clip, + audio->clip_start+i ); + + bar += clip->probability; + + if( chance < bar ){ + float *pos = call->data; + + audio_lock(); + audio_oneshot_3d( &clip->clip, pos, + audio->transform.s[0], + audio->volume ); + audio_unlock(); + break; + } + } + } +} + +VG_STATIC void entity_call( world_instance *world, ent_call *call ) +{ + if( call->ent.type == k_ent_volume ){ + ent_volume_call( world, call ); + } else if( call->ent.type == k_ent_audio ){ + ent_audio_call( world, call ); + } +} + VG_STATIC void world_update( world_instance *world, v3f pos ) { /* TEMP!!!!!! */ @@ -549,8 +625,7 @@ VG_STATIC void world_update( world_instance *world, v3f pos ) } sfd_update(); -#if 0 - /* TODO: Bvh */ + static float random_accum = 0.0f; random_accum += vg.time_delta; @@ -573,69 +648,48 @@ VG_STATIC void world_update( world_instance *world, v3f pos ) int in_volume = 0; - while( bh_next( world->volume_bh, &it, volume_proximity, &idx ) ) - { - struct world_volume *zone = &world->volumes[idx]; + while( bh_next( world->volume_bh, &it, volume_proximity, &idx ) ){ + ent_volume *volume = mdl_arritm( &world->ent_volume, idx ); - if( zone->node->classtype == k_classtype_volume_audio ) - { - vg_line_boxf_transformed( zone->transform, (boxf){{-1.0f,-1.0f,-1.0f}, - { 1.0f, 1.0f, 1.0f}}, - 0xff00c0ff ); -#if 0 - for( int j=0; jtype == k_volume_subtype_trigger ){ + v3f local; + m4x3_mulv( volume->to_local, pos, local ); + vg_line_boxf_transformed( volume->to_world, cube, 0xff00ff00 ); + + if( (fabsf(local[0]) <= 1.0f) && + (fabsf(local[1]) <= 1.0f) && + (fabsf(local[2]) <= 1.0f) ) { - logic_packet packet; - packet.location = zone->target_logic_brick; - packet.function = 0; + in_volume = 1; - packet.type = k_mdl_128bit_datatype_vec3; - packet.data._v4f[0] = vg_randf()*2.0f-1.0f; - packet.data._v4f[1] = vg_randf()*2.0f-1.0f; - packet.data._v4f[2] = vg_randf()*2.0f-1.0f; - m4x3_mulv( zone->transform, packet.data._v4f, packet.data._v4f ); + if( !world_global.in_volume ){ + ent_call basecall; + basecall.ent.index = idx; + basecall.ent.type = k_ent_volume; + basecall.function = k_ent_function_trigger; + basecall.data = NULL; - logic_bricks_send_packet( world, &packet ); + entity_call( world, &basecall ); + } } -#endif - continue; } + else if( volume->type == k_volume_subtype_particle ){ + vg_line_boxf_transformed( volume->to_world, cube, 0xff00c0ff ); - v3f local; - m4x3_mulv( zone->inv_transform, pos, local ); - - if( (fabsf(local[0]) <= 1.0f) && - (fabsf(local[1]) <= 1.0f) && - (fabsf(local[2]) <= 1.0f) ) - { - in_volume = 1; - - if( !world_global.in_volume ) - { -#if 0 - logic_packet packet; - packet.location = zone->target_logic_brick; - packet.function = 0; - - packet.type = k_mdl_128bit_datatype_vec3; - v3_copy( pos, packet.data._v4f ); + for( int j=0; jtransform, (boxf){{-1.0f,-1.0f,-1.0f}, - { 1.0f, 1.0f, 1.0f}}, - 0xff00ff00 ); - } - else - { - vg_line_boxf_transformed( zone->transform, (boxf){{-1.0f,-1.0f,-1.0f}, - { 1.0f, 1.0f, 1.0f}}, - 0xff0000ff ); } } -#endif + world_global.in_volume = in_volume; #if 0 if( k_debug_light_indices ) @@ -657,7 +711,6 @@ VG_STATIC void world_update( world_instance *world, v3f pos ) } } - world_global.in_volume = in_volume; #endif #if 0 diff --git a/world_gen.h b/world_gen.h index 63cb1ef..fac1fe7 100644 --- a/world_gen.h +++ b/world_gen.h @@ -791,6 +791,44 @@ VG_STATIC void world_entities_init( world_instance *world ) world->water.enabled = 1; water_set_surface( world, water->transform.co[1] ); } + + /* volumes */ + for( u32 j=0; jent_volume); j++ ){ + ent_volume *volume = mdl_arritm( &world->ent_volume, j ); + mdl_transform_m4x3( &volume->transform, volume->to_world ); + m4x3_invert_affine( volume->to_world, volume->to_local ); + } + + /* audio packs */ + for( u32 j=0; jent_audio); j++ ){ + ent_audio *audio = mdl_arritm( &world->ent_audio, j ); + + for( u32 k=0; kclip_count; k++ ){ + ent_audio_clip *clip = mdl_arritm( &world->ent_audio_clip, + audio->clip_start+k ); + + if( clip->file.pack_size ){ + u32 size = clip->file.pack_size, + offset = clip->file.pack_offset; + + /* FIXME: Ditchable asset! */ + void *data = mdl_arritm(&world->meta.pack, clip->file.pack_offset); + + clip->clip.path = NULL; + clip->clip.flags = audio->flags; + clip->clip.data = data; + clip->clip.size = size; + } + else{ + clip->clip.path = mdl_pstr( &world->meta, clip->file.pstr_path ); + clip->clip.flags = audio->flags; + clip->clip.data = NULL; + clip->clip.size = 0; + } + + audio_clip_load( &clip->clip, world_global.generic_heap ); + } + } } VG_STATIC void world_load( world_instance *world, const char *path ) @@ -804,6 +842,8 @@ VG_STATIC void world_load( world_instance *world, const char *path ) mdl_load_metadata_block( &world->meta, world_global.generic_heap ); mdl_load_animation_block( &world->meta, world_global.generic_heap ); mdl_load_mesh_block( &world->meta, world_global.generic_heap ); + + /* TODO: This should get a seperate memory area */ mdl_load_pack_block( &world->meta, world_global.generic_heap ); mdl_load_array( &world->meta, &world->ent_gate, @@ -823,6 +863,12 @@ VG_STATIC void world_load( world_instance *world, const char *path ) "ent_route", world_global.generic_heap ); mdl_load_array( &world->meta, &world->ent_water, "ent_water", world_global.generic_heap ); + mdl_load_array( &world->meta, &world->ent_audio_clip, + "ent_audio_clip", world_global.generic_heap ); + mdl_load_array( &world->meta, &world->ent_audio, + "ent_audio", world_global.generic_heap ); + mdl_load_array( &world->meta, &world->ent_volume, + "ent_volume", world_global.generic_heap ); mdl_close( &world->meta ); @@ -838,6 +884,11 @@ VG_STATIC void world_load( world_instance *world, const char *path ) #endif world_routes_ent_init( world ); world_entities_init( world ); + world->volume_bh = bh_create( world_global.generic_heap, + &bh_system_volumes, + world, + mdl_arrcount( &world->ent_volume ), + 1 ); /* main bulk */ world_generate( world ); diff --git a/world_volumes.h b/world_volumes.h index f333626..da39d26 100644 --- a/world_volumes.h +++ b/world_volumes.h @@ -3,7 +3,6 @@ #include "world.h" -#if 0 /* * BVH implementation * ---------------------------------------------------------------------------- @@ -13,35 +12,30 @@ VG_STATIC void volume_vg_expand_bound( void *user, boxf bound, u32 item_index ) { world_instance *world = user; - ent_volume *volume_array = world_ent_array( world, k_ent_volume ), - *volume = volume_array + item_index; + ent_volume *volume = mdl_arritm( &world->ent_volume, item_index ); - m4x3_expand_aabb_point( volume->transform, bound, (v3f){ 1.0f, 1.0f, 1.0f} ); - m4x3_expand_aabb_point( volume->transform, bound, (v3f){ 1.0f, 1.0f,-1.0f} ); - m4x3_expand_aabb_point( volume->transform, bound, (v3f){ 1.0f,-1.0f, 1.0f} ); - m4x3_expand_aabb_point( volume->transform, bound, (v3f){ 1.0f,-1.0f,-1.0f} ); - m4x3_expand_aabb_point( volume->transform, bound, (v3f){-1.0f, 1.0f, 1.0f} ); - m4x3_expand_aabb_point( volume->transform, bound, (v3f){-1.0f, 1.0f,-1.0f} ); - m4x3_expand_aabb_point( volume->transform, bound, (v3f){-1.0f,-1.0f, 1.0f} ); - m4x3_expand_aabb_point( volume->transform, bound, (v3f){-1.0f,-1.0f,-1.0f} ); + m4x3_expand_aabb_point( volume->to_world, bound, (v3f){ 1.0f, 1.0f, 1.0f} ); + m4x3_expand_aabb_point( volume->to_world, bound, (v3f){ 1.0f, 1.0f,-1.0f} ); + m4x3_expand_aabb_point( volume->to_world, bound, (v3f){ 1.0f,-1.0f, 1.0f} ); + m4x3_expand_aabb_point( volume->to_world, bound, (v3f){ 1.0f,-1.0f,-1.0f} ); + m4x3_expand_aabb_point( volume->to_world, bound, (v3f){-1.0f, 1.0f, 1.0f} ); + m4x3_expand_aabb_point( volume->to_world, bound, (v3f){-1.0f, 1.0f,-1.0f} ); + m4x3_expand_aabb_point( volume->to_world, bound, (v3f){-1.0f,-1.0f, 1.0f} ); + m4x3_expand_aabb_point( volume->to_world, bound, (v3f){-1.0f,-1.0f,-1.0f} ); } VG_STATIC float volume_vg_centroid( void *user, u32 item_index, int axis ) { world_instance *world = user; - - ent_volume *volume_array = world_ent_array( world, k_ent_volume ), - *volume = volume_array + item_index; - - return volume->transform[3][axis]; + ent_volume *volume = mdl_arritm( &world->ent_volume, item_index ); + return volume->to_world[3][axis]; } VG_STATIC void volume_vg_swap( void *user, u32 ia, u32 ib ) { world_instance *world = user; - ent_volume *volume_array = world_ent_array( world, k_ent_volume ), - *a = volume_array + ia, - *b = volume_array + ib, + ent_volume *a = mdl_arritm( &world->ent_volume, ia ), + *b = mdl_arritm( &world->ent_volume, ib ), temp; temp = *a; @@ -52,12 +46,10 @@ VG_STATIC void volume_vg_swap( void *user, u32 ia, u32 ib ) VG_STATIC void volume_vg_debug( void *user, u32 item_index ) { world_instance *world = user; - ent_volume *volume_array = world_ent_array( world, k_ent_volume ), - *volume = volume_array + item_index; - - vg_line_boxf_transformed( volume->transform, (boxf){{-1.0f,-1.0f,-1.0f}, - { 1.0f, 1.0f, 1.0f}}, - 0xff00ff00 ); + ent_volume *volume = mdl_arritm( &world->ent_volume, item_index ); + vg_line_boxf_transformed( volume->to_world, (boxf){{-1.0f,-1.0f,-1.0f}, + { 1.0f, 1.0f, 1.0f}}, + 0xff00ff00 ); } VG_STATIC bh_system bh_system_volumes = @@ -69,6 +61,5 @@ VG_STATIC bh_system bh_system_volumes = .item_debug = volume_vg_debug, .cast_ray = NULL }; -#endif #endif /* WORLD_VOLUMES_H */ -- 2.25.1