--- /dev/null
+#ifndef WORLD_ENTITY_C
+#define WORLD_ENTITY_C
+
+#include "world.h"
+#include "world_load.h"
+#include "entity.h"
+
+VG_STATIC void world_gen_entities_init(void)
+{
+ world_instance *world = world_loading_instance();
+
+ /* lights */
+ for( u32 j=0; j<mdl_arrcount(&world->ent_light); j ++ ){
+ ent_light *light = mdl_arritm( &world->ent_light, j );
+
+ m4x3f to_world;
+ q_m3x3( light->transform.q, to_world );
+ v3_copy( light->transform.co, to_world[3] );
+ m4x3_invert_affine( to_world, light->inverse_world );
+
+ light->angle_sin_cos[0] = sinf( light->angle * 0.5f );
+ light->angle_sin_cos[1] = cosf( light->angle * 0.5f );
+ }
+
+ /* gates */
+ for( u32 j=0; j<mdl_arrcount(&world->ent_gate); j ++ ){
+ ent_gate *gate = mdl_arritm( &world->ent_gate, j );
+
+ if( gate->type == k_gate_type_teleport ){
+ gate_transform_update( gate );
+ }
+ }
+ vg_async_call( world_link_nonlocal_async, world, 0 );
+
+ /* water */
+ for( u32 j=0; j<mdl_arrcount(&world->ent_water); j++ ){
+ ent_water *water = mdl_arritm( &world->ent_water, j );
+ if( world->water.enabled ){
+ vg_warn( "Multiple water surfaces in level!\n" );
+ break;
+ }
+
+ world->water.enabled = 1;
+ water_set_surface( world, water->transform.co[1] );
+ }
+
+ /* volumes */
+ for( u32 j=0; j<mdl_arrcount(&world->ent_volume); j++ ){
+ ent_volume *volume = mdl_arritm( &world->ent_volume, j );
+ mdl_transform_m4x3( &volume->transform, volume->to_world );
+ m4x3_invert_full( volume->to_world, volume->to_local );
+ }
+
+ /* audio packs */
+ for( u32 j=0; j<mdl_arrcount(&world->ent_audio); j++ ){
+ ent_audio *audio = mdl_arritm( &world->ent_audio, j );
+
+ for( u32 k=0; k<audio->clip_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;
+
+ /* embedded files are fine to clear the scratch buffer, only
+ * external audio uses it */
+
+ vg_linear_clear( vg_mem.scratch );
+ void *data = vg_linear_alloc( vg_mem.scratch,
+ clip->file.pack_size );
+
+ mdl_fread_pack_file( &world->meta, &clip->file, data );
+
+ 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->heap );
+ }
+ }
+}
+
+VG_STATIC
+ent_spawn *world_find_closest_spawn( world_instance *world, v3f position )
+{
+ ent_spawn *rp = NULL, *r;
+ float min_dist = INFINITY;
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_spawn); i++ ){
+ r = mdl_arritm( &world->ent_spawn, i );
+ float d = v3_dist2( r->transform.co, position );
+
+ if( d < min_dist ){
+ min_dist = d;
+ rp = r;
+ }
+ }
+
+ if( !rp ){
+ if( mdl_arrcount(&world->ent_spawn) ){
+ vg_warn( "Invalid distances to spawns.. defaulting to first one.\n" );
+ return mdl_arritm( &world->ent_spawn, 0 );
+ }
+ else{
+ vg_error( "There are no spawns in the level!\n" );
+ }
+ }
+
+ return rp;
+}
+
+VG_STATIC
+ent_spawn *world_find_spawn_by_name( world_instance *world, const char *name )
+{
+ ent_spawn *rp = NULL, *r;
+ for( u32 i=0; i<mdl_arrcount(&world->ent_spawn); i++ ){
+ r = mdl_arritm( &world->ent_spawn, i );
+ if( !strcmp( mdl_pstr(&world->meta, r->pstr_name), name ) ){
+ rp = r;
+ break;
+ }
+ }
+
+ if( !rp )
+ vg_warn( "No spawn named '%s'\n", name );
+
+ return rp;
+}
+
+VG_STATIC void ent_volume_call( world_instance *world, ent_call *call )
+{
+ u32 index = mdl_entity_id_id( call->id );
+ ent_volume *volume = mdl_arritm( &world->ent_volume, index );
+ if( !volume->target ) return;
+
+ if( call->function == k_ent_function_trigger ){
+ call->id = volume->target;
+
+ if( volume->type == k_volume_subtype_particle ){
+ float *co = alloca( sizeof(float)*3 );
+ co[0] = vg_randf64()*2.0f-1.0f;
+ co[1] = vg_randf64()*2.0f-1.0f;
+ co[2] = vg_randf64()*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{
+ entity_call( world, call );
+ }
+ }
+}
+
+VG_STATIC void ent_audio_call( world_instance *world, ent_call *call )
+{
+ if( world->status == k_world_status_unloading ){
+ vg_warn( "cannot modify audio while unloading world\n" );
+ return;
+ }
+
+ u8 world_id = (world - world_static.worlds) + 1;
+ u32 index = mdl_entity_id_id( call->id );
+ ent_audio *audio = mdl_arritm( &world->ent_audio, index );
+
+ v3f sound_co;
+
+ if( call->function == k_ent_function_particle_spawn ){
+ v3_copy( call->data, sound_co );
+ }
+ else if( call->function == k_ent_function_trigger ){
+ v3_copy( audio->transform.co, sound_co );
+ }
+ else
+ vg_fatal_error( "ent_audio_call (invalid function id)" );
+
+ float chance = vg_randf64()*100.0f,
+ bar = 0.0f;
+
+ for( u32 i=0; i<audio->clip_count; i++ ){
+ ent_audio_clip *clip = mdl_arritm( &world->ent_audio_clip,
+ audio->clip_start+i );
+
+ float mod = world->probabilities[ audio->probability_curve ],
+ p = clip->probability * mod;
+
+ bar += p;
+
+ if( chance < bar ){
+ audio_lock();
+
+ if( audio->behaviour == k_channel_behaviour_unlimited ){
+ audio_oneshot_3d( &clip->clip, sound_co,
+ audio->transform.s[0],
+ audio->volume );
+ }
+ else if( audio->behaviour == k_channel_behaviour_discard_if_full ){
+ audio_channel *ch =
+ audio_get_group_idle_channel( audio->group,
+ audio->max_channels );
+
+ if( ch ){
+ audio_channel_init( ch, &clip->clip, audio->flags );
+ audio_channel_group( ch, audio->group );
+ audio_channel_world( ch, world_id );
+ audio_channel_set_spacial( ch, sound_co, audio->transform.s[0] );
+ audio_channel_edit_volume( ch, audio->volume, 1 );
+ ch = audio_relinquish_channel( ch );
+ }
+ }
+ else if( audio->behaviour == k_channel_behaviour_crossfade_if_full){
+ audio_channel *ch =
+ audio_get_group_idle_channel( audio->group,
+ audio->max_channels );
+
+ /* group is full */
+ if( !ch ){
+ audio_channel *existing =
+ audio_get_group_first_active_channel( audio->group );
+
+ if( existing ){
+ if( existing->source == &clip->clip ){
+ audio_unlock();
+ return;
+ }
+
+ existing->group = 0;
+ existing = audio_channel_fadeout(existing, audio->crossfade);
+ }
+
+ ch = audio_get_first_idle_channel();
+ }
+
+ if( ch ){
+ audio_channel_init( ch, &clip->clip, audio->flags );
+ audio_channel_group( ch, audio->group );
+ audio_channel_world( ch, world_id );
+ audio_channel_fadein( ch, audio->crossfade );
+ ch = audio_relinquish_channel( ch );
+ }
+ }
+
+ audio_unlock();
+ return;
+ }
+ }
+}
+
+#endif /* WORLD_ENTITY_C */