the luxuries of a modern C compiler
authorhgn <hgodden00@gmail.com>
Tue, 23 May 2023 01:29:21 +0000 (02:29 +0100)
committerhgn <hgodden00@gmail.com>
Tue, 23 May 2023 01:29:21 +0000 (02:29 +0100)
47 files changed:
ent_skateshop.c
ent_skateshop.h
entity.c
entity.h
gui.h
menu.h
player.c
player.h
player_common.c
player_common.h
player_ragdoll.c
player_render.c
player_render.h
player_skate.c
player_walk.c
render.h
skaterift.c
skaterift.h
vehicle.c [new file with mode: 0644]
vehicle.h
workshop.c
workshop.h
world.c [new file with mode: 0644]
world.h
world_audio.c [new file with mode: 0644]
world_audio.h [new file with mode: 0644]
world_entity.c [new file with mode: 0644]
world_entity.h [new file with mode: 0644]
world_gate.c [new file with mode: 0644]
world_gate.h
world_gen.c [new file with mode: 0644]
world_gen.h
world_info.h
world_load.c [new file with mode: 0644]
world_load.h [new file with mode: 0644]
world_physics.c [new file with mode: 0644]
world_physics.h [new file with mode: 0644]
world_render.c [new file with mode: 0644]
world_render.h
world_routes.c [new file with mode: 0644]
world_routes.h
world_sfd.c [new file with mode: 0644]
world_sfd.h
world_volumes.c [new file with mode: 0644]
world_volumes.h
world_water.c [new file with mode: 0644]
world_water.h

index a3e12dca52986e6b39c14c12b97e75f759321348..19b868fe6b986e5f6e2570936d836ee05e63c259 100644 (file)
@@ -10,6 +10,7 @@
 #include "gui.h"
 #include "menu.h"
 #include "pointcloud.h"
+#include "highscores.h"
 
 /*
  * Checks string equality but does a hash check first
@@ -102,12 +103,6 @@ VG_STATIC void skateshop_update_viewpage(void)
    }
 }
 
-/* generic reciever */
-VG_STATIC void workshop_async_any_complete( void *data, u32 size )
-{
-   skaterift_end_op();
-}
-
 /*
  * op/subroutine: k_workshop_op_item_load
  * -----------------------------------------------------------------------------
@@ -457,7 +452,7 @@ VG_STATIC void world_scan_thread( void *_args )
                vg_str file = folder;
                vg_strcat( &file, "/" );
                vg_strcat( &file, entry->d_name );
-               if( !vg_strgood( &file ) ) break;
+               if( !vg_strgood( &file ) ) continue;
 
                char *ext = vg_strch( &file, '.' );
                if( !ext ) continue;
@@ -604,7 +599,7 @@ VG_STATIC void global_skateshop_preupdate(void)
 
    if( !global_skateshop.active ) return;
 
-   world_instance *world = get_active_world();
+   world_instance *world = world_current_instance();
 
    ent_skateshop *shop = global_skateshop.ptr_ent;
 
@@ -719,6 +714,10 @@ VG_STATIC void global_skateshop_preupdate(void)
       if( global_skateshop.world_registry_count ){
          gui_helper_action( axis_display_string(k_sraxis_mbrowse_h), "browse" );
       }
+
+      if( skaterift.async_op == k_async_op_none ){
+         gui_helper_action( button_display_string(k_srbind_maccept), "load" );
+      }
       
       int change = 0;
       if( button_down( k_srbind_mleft ) ){
@@ -736,14 +735,20 @@ VG_STATIC void global_skateshop_preupdate(void)
          }
       }
       
-      if( change && (pointcloud.anim == k_pointcloud_anim_idle) ){
+      if( change && (pointcloud.anim == k_pointcloud_anim_idle) && 
+          pointcloud.visibility == 1.0f ){
          pointcloud.anim = k_pointcloud_anim_hiding;
          pointcloud.anim_start = vg.time;
       }
 
-      if( button_down( k_srbind_maccept ) ){
+      if( (skaterift.async_op == k_async_op_none ) && 
+            button_down( k_srbind_maccept ) )
+      {
+         struct registry_world *rw = &global_skateshop.world_registry[
+            global_skateshop.selected_world_id ];
+
          vg_info( "Select world (%u)\n", global_skateshop.selected_world_id );
-         global_skateshop_exit();
+         skaterift_change_world( rw->foldername );
          return;
       }
    }
@@ -759,7 +764,7 @@ VG_STATIC void global_skateshop_preupdate(void)
 
 VG_STATIC void skateshop_render_boardshop(void)
 {
-   world_instance *world = get_active_world();
+   world_instance *world = world_current_instance();
    ent_skateshop *shop = global_skateshop.ptr_ent;
 
    u32 slot_count = vg_list_size(global_skateshop.shop_view_slots);
@@ -832,7 +837,7 @@ fade_out:;
    float scale = 0.2f,
          thickness = 0.03f;
 
-   font3d_bind( &world_global.font, &main_camera );
+   font3d_bind( &gui.font, &main_camera );
    shader_model_font_uColour( (v4f){1.0f,1.0f,1.0f,1.0f} );
 
    /* Selection counter
@@ -846,7 +851,7 @@ fade_out:;
    m4x3_mul( mrack, mlocal, mmdl );
 
    if( global_skateshop.registry_count == 0 ){
-      font3d_simple_draw( &world_global.font, 0, 
+      font3d_simple_draw( &gui.font, 0, 
                           "Nothing installed", &main_camera, mmdl );
    }
    else{
@@ -857,7 +862,7 @@ fade_out:;
       i+=highscore_intl( buf+i, global_skateshop.registry_count, 3 );
       buf[i++] = '\0';
 
-      font3d_simple_draw( &world_global.font, 0, buf, &main_camera, mmdl );
+      font3d_simple_draw( &gui.font, 0, buf, &main_camera, mmdl );
    }
 
    struct cache_board *cache_ptr = skateshop_selected_cache_if_loaded();
@@ -871,25 +876,23 @@ fade_out:;
     * ----------------------------------------------------------------- */
    m3x3_zero( mlocal );
    m3x3_setdiagonalv3( mlocal, (v3f){ scale, scale, thickness } );
-   mlocal[3][0] = -font3d_string_width( &world_global.font, 0, info->title );
+   mlocal[3][0] = -font3d_string_width( &gui.font, 0, info->title );
    mlocal[3][0] *= scale*0.5f;
    mlocal[3][1] = 0.1f;
    mlocal[3][2] = 0.0f;
    m4x3_mul( mtext, mlocal, mmdl );
-   font3d_simple_draw( &world_global.font, 0, info->title, &main_camera, mmdl );
+   font3d_simple_draw( &gui.font, 0, info->title, &main_camera, mmdl );
 
    /* Author name
     * ----------------------------------------------------------------- */
    scale *= 0.4f;
    m3x3_setdiagonalv3( mlocal, (v3f){ scale, scale, thickness } );
-   mlocal[3][0] = -font3d_string_width( &world_global.font, 0, 
-                                        info->author_name );
+   mlocal[3][0] = -font3d_string_width( &gui.font, 0, info->author_name );
    mlocal[3][0] *= scale*0.5f;
    mlocal[3][1] = 0.0f;
    mlocal[3][2] = 0.0f;
    m4x3_mul( mtext, mlocal, mmdl );
-   font3d_simple_draw( &world_global.font, 0, 
-                       info->author_name, &main_camera, mmdl );
+   font3d_simple_draw( &gui.font, 0, info->author_name, &main_camera, mmdl );
 }
 
 VG_STATIC void skateshop_render_charshop(void)
@@ -898,7 +901,7 @@ VG_STATIC void skateshop_render_charshop(void)
 
 VG_STATIC void skateshop_render_worldshop(void)
 {
-   world_instance *world = get_active_world();
+   world_instance *world = world_current_instance();
 
    ent_skateshop *shop = global_skateshop.ptr_ent;
    ent_marker *mark_display = mdl_arritm( &world->ent_marker,
@@ -936,29 +939,33 @@ VG_STATIC void skateshop_render_worldshop(void)
       vg_strcat( &info, "No worlds installed" );
    }
 
+   if( skaterift.async_op == k_async_op_world_loading ||
+       skaterift.async_op == k_async_op_world_preloading ){
+      vg_strcat( &info, "Loading..." );
+   }
+
    m4x3f mtext,mlocal,mtextmdl;
    mdl_transform_m4x3( &mark_info->transform, mtext );
 
-   font3d_bind( &world_global.font, &main_camera );
+   font3d_bind( &gui.font, &main_camera );
    shader_model_font_uColour( (v4f){1.0f,1.0f,1.0f,1.0f} );
 
    float scale = 0.2f, thickness = 0.015f, scale1 = 0.08f;
    m3x3_zero( mlocal );
    m3x3_setdiagonalv3( mlocal, (v3f){ scale, scale, thickness } );
-   mlocal[3][0] = -font3d_string_width( &world_global.font, 0, buftext );
+   mlocal[3][0] = -font3d_string_width( &gui.font, 0, buftext );
    mlocal[3][0] *= scale*0.5f;
    mlocal[3][1] = 0.1f;
    mlocal[3][2] = 0.0f;
    m4x3_mul( mtext, mlocal, mtextmdl );
-   font3d_simple_draw( &world_global.font, 0, buftext, &main_camera, mtextmdl );
+   font3d_simple_draw( &gui.font, 0, buftext, &main_camera, mtextmdl );
 
    m3x3_setdiagonalv3( mlocal, (v3f){ scale1, scale1, thickness } );
-   mlocal[3][0] = -font3d_string_width( &world_global.font, 0, bufsubtext );
+   mlocal[3][0] = -font3d_string_width( &gui.font, 0, bufsubtext );
    mlocal[3][0] *= scale1*0.5f;
    mlocal[3][1] = -scale1*0.3f;
    m4x3_mul( mtext, mlocal, mtextmdl );
-   font3d_simple_draw( &world_global.font, 0, bufsubtext, 
-                       &main_camera, mtextmdl );
+   font3d_simple_draw( &gui.font, 0, bufsubtext, &main_camera, mtextmdl );
 
    /* pointcloud */
    m4x3f mmdl;
index f73b875d069004144d3123fd18a01fd97a3b9426..93ebbec64f7933927b2b001acaeac1ab274ee7fb 100644 (file)
@@ -81,6 +81,7 @@ struct{
 }
 static global_skateshop;
 
+VG_STATIC void global_skateshop_exit(void);
 VG_STATIC void watch_cache_board( struct cache_board *ptr );
 VG_STATIC void unwatch_cache_board( struct cache_board *ptr );
 
index 1d91c2491a9032181a53189503bf5e30f158d515..1e85e565ecd89ce8ceafbe0316998e1c7b185e51 100644 (file)
--- a/entity.c
+++ b/entity.c
@@ -1,8 +1,9 @@
 #ifndef ENTITY_C
 #define ENTITY_C
 
-#include "entity.h"
 #include "world.h"
+#include "entity.h"
+#include "world_entity.h"
 
 #include "ent_skateshop.c"
 
index 998557edfa3de1f84c13f5a9ee9477c768136be2..1de6f7026b62379c7f80092ecc6550849e5723c7 100644 (file)
--- a/entity.h
+++ b/entity.h
@@ -383,5 +383,6 @@ struct ent_call{
 };
 
 #include "world.h"
+VG_STATIC void entity_call( world_instance *world, ent_call *call );
 
 #endif /* ENTITY_H */
diff --git a/gui.h b/gui.h
index 2ec0a5070afb4640cdf97d3fbef2e8178a9c896e..99345c1e431eb3fe16e5c30af625f8348c59e820 100644 (file)
--- a/gui.h
+++ b/gui.h
@@ -10,7 +10,8 @@ struct{
    helpers[4];
    u32 helper_count;
 
-   float factive;
+   f32 factive;
+   font3d font;
 }
 static gui;
 
@@ -51,8 +52,7 @@ void gui_draw(void)
       render_fsquad1();
    }
 
-   font3d *font = &world_global.font;
-   font3d_bind( font, &ortho );
+   font3d_bind( &gui.font, &ortho );
 
    float dy    = ft/0.79f,
          scale = dy*0x1p-4f*0.75f;
@@ -73,7 +73,7 @@ void gui_draw(void)
       shader_model_font_uColour( (v4f){1.0f,1.0f,1.0f,1.0f} );
 
       struct font3d_render render;
-      font3d_begin( font, 2, &ortho, mmdl, &render );
+      font3d_begin( &gui.font, 2, &ortho, mmdl, &render );
 
       render.u8pch = (u8*)helper->bindstr;
       font3d_draw( &render );
@@ -103,4 +103,9 @@ void gui_helper_action( const char *bindstr, const char *text )
    helper->text = text;
 }
 
+VG_STATIC void gui_init(void)
+{
+   font3d_load( &gui.font, "models/rs_font.mdl", vg_mem.rtmemory );
+}
+
 #endif /* GUI_H */
diff --git a/menu.h b/menu.h
index 0707e7344b30686a9fd5f70bdbd8335f73f9c77c..dd2cf686d8b3cf286f29afeca822998fd2212240 100644 (file)
--- a/menu.h
+++ b/menu.h
@@ -7,6 +7,8 @@
 #include "player.h"
 #include "conf.h"
 #include "shaders/model_menu.h"
+#include "audio.h"
+#include "input.h"
 
 #define MENU_STACK_SIZE 8
 
index 0339a8d889f87c0faa08bf6b2443cf2bba9e783b..145a9285c79c636ece7c70b32c4d947725a213c7 100644 (file)
--- a/player.c
+++ b/player.c
@@ -5,6 +5,8 @@
 #include "camera.h"
 #include "player_model.h"
 #include "input.h"
+#include "world.h"
+#include "audio.h"
 
 VG_STATIC int localplayer_cmd_respawn( int argc, const char *argv[] )
 {
@@ -130,9 +132,9 @@ void player__pre_update( player_instance *player )
    }
 
    if( button_down( k_srbind_reset ) && !player->immobile ){
-      double delta = world_global.time - world_global.last_use;
+      f64 delta = world_static.time - world_static.last_use;
 
-      if( (delta <= RESET_MAX_TIME) && (world_global.last_use != 0.0) ){
+      if( (delta <= RESET_MAX_TIME) && (world_static.last_use != 0.0) ){
          player->rewinding = 1;
          player->rewind_sound_wait = 1;
          player->rewind_time = (double)player->rewind_length - 0.0001;
@@ -219,11 +221,11 @@ void player__post_update( player_instance *player )
 PLAYER_API
 void player__pass_gate( player_instance *player, ent_gate *gate )
 {
-   world_routes_fracture( get_active_world(), gate, 
+   world_routes_fracture( world_current_instance(), gate, 
                           player->rb.co, player->rb.v );
 
    player->gate_waiting = gate;
-   world_routes_activate_entry_gate( get_active_world(), gate );
+   world_routes_activate_entry_gate( world_current_instance(), gate );
 
    m4x3_mulv( gate->transport, player->tpv_lpf, player->tpv_lpf );
    m3x3_mulv( gate->transport, player->cam_velocity_smooth, 
@@ -250,9 +252,9 @@ void player__pass_gate( player_instance *player, ent_gate *gate )
    player_save_rewind_frame( player );
 
    if( gate->type == k_gate_type_nonlocel )
-      world_global.active_world = gate->target;
+      world_static.active_world = gate->target;
 
-   world_global.in_volume = 0;
+   world_volumes.inside = 0;
 
    audio_lock();
    audio_oneshot( &audio_gate_pass, 1.0f, 0.0f );
@@ -346,4 +348,13 @@ PLAYER_API void player__kill( player_instance *player )
    
 }
 
+/* implementation */
+#include "player_common.c"
+#include "player_walk.c"
+#include "player_skate.c"
+#include "player_dead.c"
+#include "player_drive.c"
+#include "player_render.c"
+#include "player_ragdoll.c"
+
 #endif /* PLAYER_C */
index 0911e8fc03380d776d84bd27e0d4759b30080476..b52201b7b314009f07395d87e8c7c516cbbe0f8f 100644 (file)
--- a/player.h
+++ b/player.h
@@ -228,15 +228,7 @@ PLAYER_API void player__im_gui( player_instance *player );
 PLAYER_API void player__spawn( player_instance *player, ent_spawn *rp );
 PLAYER_API void player__kill( player_instance *player );
 
-/* implementation */
-
-#include "player.c"
-#include "player_common.c"
-#include "player_walk.c"
-#include "player_skate.c"
-#include "player_dead.c"
-#include "player_drive.c"
-#include "player_render.c"
-#include "player_ragdoll.c"
+VG_STATIC int localplayer_cmd_respawn( int argc, const char *argv[] );
+VG_STATIC void player_apply_transport_to_cam( m4x3f transport );
 
 #endif /* PLAYER_H */
index ee597d0271e4ba0c2cc3d65e310d62a6eb6da0ca..22c26a280524f8d75f5c30bfa64d76fbfe93664d 100644 (file)
@@ -49,7 +49,7 @@ VG_STATIC void player_camera_portal_correction( player_instance *player )
          vg_success( "Plane cleared\n" );
          player_apply_transport_to_cam( player->gate_waiting->transport );
          player->gate_waiting = NULL;
-         player->viewable_world = get_active_world();
+         player->viewable_world = world_current_instance();
       }
       else{
          /* de-transform camera and player back */
@@ -63,8 +63,6 @@ VG_STATIC void player_camera_portal_correction( player_instance *player )
    }
 }
 
-static v3f TEMP_TPV_EXTRA;
-
 VG_STATIC void player__cam_iterate( player_instance *player )
 {
    struct player_avatar *av = player->playeravatar;
index 6c825d800dec12a96256b23e8f7cf1caa3bd7fdd..5cf441123d42072ede60180de9293333d02f6896 100644 (file)
@@ -3,8 +3,11 @@
 
 #include "player_api.h"
 
+static v3f TEMP_TPV_EXTRA; /* TODO: what? */
+
 VG_STATIC void player_look( player_instance *player, v3f angles );
 VG_STATIC void player__cam_iterate( player_instance *player );
 VG_STATIC void player_vector_angles( v3f angles, v3f v, float C, float k );
+struct player_board *player_get_player_board( struct player_instance *player );
 
 #endif /* PLAYER_COMMON_H */
index 7b5308816d228d173df193b93f2569c9ea80b44a..aabf6f9fe37eefdb1e545b61b93f8777a1654a53 100644 (file)
@@ -269,7 +269,7 @@ VG_STATIC void player_debug_ragdoll(void)
  */
 VG_STATIC void player_ragdoll_iter( struct player_ragdoll *rd )
 {
-   world_instance *world = get_active_world();
+   world_instance *world = world_current_instance();
 
    int run_sim = 0;
    ragdoll_frame ++;
index 8f2521538ca9719b7c83b93dccf3a27553f49e0c..011d0415edacf62857cb81e9b83a767005c951d5 100644 (file)
@@ -6,6 +6,7 @@
 #include "camera.h"
 #include "player_model.h"
 #include "ent_skateshop.h"
+#include "audio.h"
 
 #include "shaders/model_character_view.h"
 #include "shaders/model_board_view.h"
@@ -174,7 +175,7 @@ VG_STATIC void player__pre_render( player_instance *player )
       v3_zero( vp1 );
    }
 
-   struct ub_world_lighting *ubo = &get_active_world()->ub_lighting;
+   struct ub_world_lighting *ubo = &world_current_instance()->ub_lighting;
    m4x3_mulv( av->sk.final_mtx[ av->id_board ], vp0, ubo->g_board_0 );
    m4x3_mulv( av->sk.final_mtx[ av->id_board ], vp1, ubo->g_board_1 );
 
@@ -194,11 +195,11 @@ VG_STATIC void player__pre_render( player_instance *player )
          player->rewind_length = 1;
          player->rewind_total_length = 0.0f;
          player->rewind_accum = 0.0f;
-         world_global.sky_target_rate = 1.0;
-         world_global.time = world_global.last_use;
+         world_render.sky_target_rate = 1.0;
+         world_static.time = world_static.last_use;
       }
       else{
-         world_global.sky_target_rate = -100.0;
+         world_render.sky_target_rate = -100.0;
 
          float budget         = vg.time_delta,
                overall_length = player->rewind_length;
@@ -282,11 +283,6 @@ VG_STATIC void player__pre_render( player_instance *player )
    player__cam_iterate( player );
 }
 
-enum board_shader{
-   k_board_shader_player,
-   k_board_shader_entity
-};
-
 VG_STATIC void render_board( camera *cam, world_instance *world,
                              struct player_board *board, m4x3f root,
                              enum board_shader shader )
@@ -423,7 +419,7 @@ PLAYER_API void player__render( camera *cam, player_instance *player )
    inverse[2] = cam->farz-cam->nearz;
    shader_model_character_view_uInverseRatioMain( inverse );
 
-   world_instance *world = get_active_world();
+   world_instance *world = world_current_instance();
    world_link_lighting_ub( world, _shader_model_character_view.id );
    world_bind_position_texture( world, _shader_model_character_view.id,
                               _uniform_model_character_view_g_world_depth, 2 );
index e9d17bb6c90c2e3817df489a1bc423f2abe36cb8..f6c8194373c0d0abfc8b44f7881babef9225fbfd 100644 (file)
@@ -3,6 +3,8 @@
 
 #include "model.h"
 #include "skeleton.h"
+#include "camera.h"
+#include "world.h"
 
 struct player_avatar
 {
@@ -56,4 +58,15 @@ struct player_board
                board;
 };
 
+enum board_shader{
+   k_board_shader_player,
+   k_board_shader_entity
+};
+
+VG_STATIC void player_board_load( struct player_board *mdl, const char *path );
+VG_STATIC void player_board_unload( struct player_board *mdl );
+VG_STATIC void render_board( camera *cam, world_instance *world,
+                             struct player_board *board, m4x3f root,
+                             enum board_shader shader );
+
 #endif /* PLAYER_RENDER_H */
index e208a49b6cf99aef08e6518060e3842e5e6a5b72..f2a1cd0feffda829eed9ed87d7cee6c8a899f7ed 100644 (file)
@@ -55,7 +55,7 @@ VG_STATIC int skate_collide_smooth( player_instance *player,
                                     m4x3f mtx, rb_sphere *sphere,
                                     rb_ct *man )
 {
-   world_instance *world = get_active_world();
+   world_instance *world = world_current_instance();
 
    int len = 0;
    len = rb_sphere__scene( mtx, sphere, NULL, &world->rb_geo.inf.scene, man );
@@ -92,7 +92,7 @@ VG_STATIC int skate_grind_scansq( player_instance *player,
                                   v3f pos, v3f dir, float r,
                                   struct grind_info *inf )
 {
-   world_instance *world = get_active_world();
+   world_instance *world = world_current_instance();
 
    v4f plane;
    v3_copy( dir, plane );
@@ -348,7 +348,7 @@ VG_STATIC int create_jumps_to_hit_target( player_instance *player,
 VG_STATIC 
 void player__approximate_best_trajectory( player_instance *player )
 {
-   world_instance *world0 = get_active_world();
+   world_instance *world0 = world_current_instance();
 
    struct player_skate *s = &player->_skate;
    float k_trace_delta = k_rb_delta * 10.0f;
@@ -502,7 +502,7 @@ void player__approximate_best_trajectory( player_instance *player )
                m3x3_mul( gate->transport, basis, basis );
 
                if( gate->type == k_gate_type_nonlocel ){
-                  trace_world = &world_global.worlds[ gate->target ];
+                  trace_world = &world_static.worlds[ gate->target ];
                }
             }
          }
@@ -1320,7 +1320,7 @@ int skate_compute_surface_alignment( player_instance *player,
                                      v3f surface_normal, v3f axel_dir )
 {
    struct player_skate *s = &player->_skate;
-   world_instance *world = get_active_world();
+   world_instance *world = world_current_instance();
 
    v3f truck, left, right;
    m4x3_mulv( player->rb.to_world, ra, truck );
@@ -1533,7 +1533,7 @@ VG_STATIC int skate_point_visible( v3f origin, v3f target )
    v3_muls( dir, 1.0f/ray.dist, dir );
    ray.dist -= 0.025f;
 
-   if( ray_world( get_active_world(), origin, dir, &ray ) )
+   if( ray_world( world_current_instance(), origin, dir, &ray ) )
       return 0;
 
    return 1;
@@ -2075,7 +2075,7 @@ VG_STATIC enum skate_activity skate_availible_grind( player_instance *player )
 VG_STATIC void player__skate_update( player_instance *player )
 {
    struct player_skate *s = &player->_skate;
-   world_instance *world = get_active_world();
+   world_instance *world = world_current_instance();
 
    v3_copy( player->rb.co, s->state.prev_pos );
    s->state.activity_prev = s->state.activity;
index c3b6c2eba2418dc50ec546a1cea3ddbb0c55bbd1..10d00947a2701a8b67e024c58603511cd8cdfd9a 100644 (file)
@@ -120,7 +120,7 @@ VG_STATIC void player_walk_drop_in_overhang_transform( player_instance *player,
 VG_STATIC int player_walk_scan_for_drop_in( player_instance *player )
 {
    struct player_walk *w = &player->_walk;
-   world_instance *world = get_active_world();
+   world_instance *world = world_current_instance();
 
    v3f dir, center;
    q_mulv( player->rb.q, (v3f){0.0f,0.0f,1.0f}, dir );
@@ -390,7 +390,7 @@ VG_STATIC void player__walk_update( player_instance *player )
    joystick_state( k_srjoystick_steer, steer );
 
    w->move_speed = v2_length( steer );
-   world_instance *world = get_active_world();
+   world_instance *world = world_current_instance();
 
    /* 
     * Collision detection
index 541f047e7396562fd1b74a5c3daf8b4d75205a05..d626b7a0a013e07af77c64dfaac33d727237bca1 100644 (file)
--- a/render.h
+++ b/render.h
 #include "shaders/blitblur.h"
 #include "shaders/blitcolour.h"
 
-#if 0
-#include "shaders/standard.h"
-#include "shaders/vblend.h"
-#endif
-
 #define WORKSHOP_PREVIEW_WIDTH  504
 #define WORKSHOP_PREVIEW_HEIGHT 336
-
-VG_STATIC void render_water_texture( world_instance *world, camera *cam,
-                                     int layer_depth );
-VG_STATIC void render_water_surface( world_instance *world, camera *cam );
-VG_STATIC void render_world( world_instance *world, camera *cam,
-                             int layer_depth );
-VG_STATIC void render_world_depth( world_instance *world, camera *cam );
-
 #ifndef RENDER_H
 #define RENDER_H
 
index 6f0364a9db7970728743a4ab2f1e68274eeca3cc..d22846ff0fe65d57ff7b6626ec4c8b7b3477498c 100644 (file)
 #define SR_NETWORKED
 #define VG_DEVWINDOW
 
+/* 
+ *     system headers
+ * --------------------- */
+
 #include "common.h"
 #include "conf.h"
 #include "steam.h"
 #include "render.h"
 #include "audio.h"
+
 #include "world.h"
+
 #include "font.h"
 #include "player.h"
 
-#include "entity.c"
-#include "workshop.c"
-
 #include "network.h"
 #include "menu.h"
 #include "vehicle.h"
 #include "pointcloud.h"
 
+/*    unity build
+ * ----------------- */
+
+#include "world.c"
+#include "player.c"
+#include "vehicle.c"
+#include "entity.c"
+#include "workshop.c"
+
 static struct player_avatar localplayer_avatar;
 static struct player_model  localplayer_models[3];
 
-
 int main( int argc, char *argv[] )
 {
    vg_mem.use_libc_malloc = 0;
@@ -95,7 +106,7 @@ void temp_update_playermodel(void){
 
 VG_STATIC void async_skaterift_complete( void *payload, u32 size )
 {
-   localplayer.viewable_world = get_active_world();
+   localplayer.viewable_world = world_current_instance();
    localplayer_cmd_respawn( 1, (const char *[]){ "start" } );
 
    skaterift_end_op();
@@ -111,8 +122,7 @@ VG_STATIC void vg_load(void)
    vg_loader_step( world_init, NULL );
    vg_loader_step( vehicle_init, NULL );
    vg_loader_step( font3d_init, NULL );
-
-   font3d_load( &world_global.font, "models/rs_font.mdl", vg_mem.rtmemory );
+   vg_loader_step( gui_init, NULL );
 
    vg_loader_step( player_init, NULL );
    vg_loader_step( player_ragdoll_init, NULL );
@@ -135,8 +145,13 @@ VG_STATIC void vg_load(void)
    vg_loader_step( audio_init, audio_free );
 
    /* 'systems' are completely loaded now */
-   /* load home/permanent world */
-   world_load( 0, "maps/mp_spawn/main.mdl" );
+   
+   /* load home/permanent world manually */
+   strcpy( world_loader.name, "mp_spawn" );
+   world_loader.generate_point_cloud = 1;
+   world_loader.world_index = 0;
+   world_loader.location = k_world_load_type_local;
+   world_load_mdl( "maps/mp_spawn/main.mdl" );
 
    vg_console_load_autos();
    menu_link();
@@ -167,8 +182,8 @@ VG_STATIC void vg_update(void)
    player__pre_update( &localplayer );
    global_skateshop_preupdate();
 
-   world_update( get_active_world(), localplayer.rb.co );
-   audio_ambient_sprites_update( get_active_world(), localplayer.rb.co );
+   world_update( world_current_instance(), localplayer.rb.co );
+   audio_ambient_sprites_update( world_current_instance(), localplayer.rb.co );
    //gui_helper_action( localplayer.input_use, "\x7f Hello \x1f""A \x1e\x84" );
 }
 
@@ -176,7 +191,7 @@ VG_STATIC void vg_update_fixed(void)
 {
    if( skaterift.async_op == k_async_op_clientloading ) return;
 
-   world_routes_fixedupdate( get_active_world() );
+   world_routes_fixedupdate( world_current_instance() );
    player__update( &localplayer );
    vehicle_update_fixed();
 }
@@ -293,9 +308,9 @@ VG_STATIC void render_scene(void)
       return;
    }
 
-   for( u32 i=0; i<vg_list_size(world_global.worlds); i++ ){
-      if( world_global.worlds[i].status == k_world_status_loaded ){
-         world_prerender( &world_global.worlds[i] );
+   for( u32 i=0; i<vg_list_size(world_static.worlds); i++ ){
+      if( world_static.worlds[i].status == k_world_status_loaded ){
+         world_prerender( &world_static.worlds[i] );
       }
    }
 
@@ -395,83 +410,12 @@ VG_STATIC void vg_gui(void)
 #if 0
    player__im_gui( &localplayer );
 #endif
-   world_instance *world = get_active_world();
+   world_instance *world = world_current_instance();
 
    workshop_form_gui();
    render_view_framebuffer_ui();
 }
 
-static void async_skaterift_world_loaded( void *payload, u32 size )
-{
-   skaterift_end_op();
-}
-
-static void skaterift_world_changer_thread( void *data )
-{
-   const char *path = data;
-   world_load( 1, world_global.load_target );
-   vg_async_call( async_skaterift_world_loaded, NULL, 0 );
-}
-
-/* holding pattern before we can start loading the new world, since we might be
- * waiting for audio to stop */
-static void skaterift_change_world_preupdate(void)
-{
-   for( u32 i=1; i<vg_list_size(world_global.worlds); i++ ){
-      world_instance *inst = &world_global.worlds[i];
-      
-      if( inst->status == k_world_status_unloading ){
-         if( world_freeable( inst ) ){
-            world_free( inst );
-         }
-         return;
-      }
-   }
-
-   vg_info( "worlds cleared, begining load\n" );
-   skaterift_shift_op( k_async_op_world_loading );
-
-   /* finally can start the loader */
-   vg_loader_start( skaterift_world_changer_thread, NULL );
-}
-
-/* places all loaded worlds into unloading state */
-static void skaterift_change_world( const char *world_path )
-{
-   vg_info( "switching to %s\n", world_path );
-
-   if( world_global.active_world != 0 ){
-      vg_error( "Cannot change worlds while in non-root world\n" );
-   }
-   else{
-      skaterift_begin_op( k_async_op_world_preloading );
-
-      vg_linear_clear( vg_mem.scratch );
-      world_global.load_target = vg_linear_alloc( vg_mem.scratch, 1024 );
-      vg_strncpy( world_path, world_global.load_target, 
-                  1024, k_strncpy_overflow_fatal );
-      
-      vg_info( "unloading old worlds\n" );
-      world_unlink_nonlocal( &world_global.worlds[0] );
-      
-      for( u32 i=1; i<vg_list_size(world_global.worlds); i++ ){
-         world_instance *inst = &world_global.worlds[i];
-
-         if( inst->status == k_world_status_loaded ){
-            inst->status = k_world_status_unloading;
-            world_fadeout_audio( inst );
-         }
-      }
-   }
-}
-
-static int skaterift_change_world_command( int argc, const char *argv[] )
-{
-   if( argc == 1 )
-      skaterift_change_world( argv[0] );
-
-   return 0;
-}
 
 #else
 
index 5d250a1fe7e0978afcdd278bde4f436823065d95..ca31f237817bef194a66b383a6266c2840f51858 100644 (file)
@@ -2,6 +2,7 @@
 #define SKATERIFT_H
 
 #include "common.h"
+#include "world.h"
 
 struct{
    enum async_operation{
diff --git a/vehicle.c b/vehicle.c
new file mode 100644 (file)
index 0000000..ad3e00d
--- /dev/null
+++ b/vehicle.c
@@ -0,0 +1,273 @@
+#ifndef VEHICLE_C
+#define VEHICLE_C
+
+#include "vehicle.h"
+
+VG_STATIC int spawn_car( int argc, const char *argv[] )
+{
+   v3f ra, rb, rx;
+   v3_copy( main_camera.pos, ra );
+   v3_muladds( ra, main_camera.transform[2], -10.0f, rb );
+
+   float t;
+   if( spherecast_world( world_current_instance(), ra, rb, 
+                         gzoomer.obj.inf.sphere.radius, &t, rx ) != -1 )
+   {
+      v3_lerp( ra, rb, t, gzoomer.obj.rb.co );
+      gzoomer.obj.rb.co[1] += 4.0f;
+      q_axis_angle( gzoomer.obj.rb.q, (v3f){1.0f,0.0f,0.0f}, 0.001f );
+      v3_zero( gzoomer.obj.rb.v );
+      v3_zero( gzoomer.obj.rb.w );
+      
+      rb_update_transform( &gzoomer.obj.rb );
+      gzoomer.alive = 1;
+
+      vg_success( "Spawned car\n" );
+   }
+   else{
+      vg_error( "Can't spawn here\n" );
+   }
+
+   return 0;
+}
+
+VG_STATIC void vehicle_init(void)
+{
+   q_identity( gzoomer.obj.rb.q );
+   v3_zero( gzoomer.obj.rb.w );
+   v3_zero( gzoomer.obj.rb.v );
+   v3_zero( gzoomer.obj.rb.co );
+   rb_init_object( &gzoomer.obj );
+
+   VG_VAR_F32( k_car_spring,        flags=VG_VAR_PERSISTENT );
+   VG_VAR_F32( k_car_spring_damp,   flags=VG_VAR_PERSISTENT );
+   VG_VAR_F32( k_car_spring_length, flags=VG_VAR_PERSISTENT );
+   VG_VAR_F32( k_car_wheel_radius,  flags=VG_VAR_PERSISTENT );
+   VG_VAR_F32( k_car_friction_lat,  flags=VG_VAR_PERSISTENT );
+   VG_VAR_F32( k_car_friction_roll, flags=VG_VAR_PERSISTENT );
+   VG_VAR_F32( k_car_drive_force,   flags=VG_VAR_PERSISTENT );
+   VG_VAR_F32( k_car_air_resistance,flags=VG_VAR_PERSISTENT );
+   VG_VAR_F32( k_car_downforce,     flags=VG_VAR_PERSISTENT );
+
+   VG_VAR_I32( gzoomer.inside );
+
+   vg_console_reg_cmd( "spawn_car", spawn_car, NULL );
+
+   v3_copy((v3f){ -1.0f, -0.25f, -1.5f }, gzoomer.wheels_local[0] );
+   v3_copy((v3f){  1.0f, -0.25f, -1.5f }, gzoomer.wheels_local[1] );
+   v3_copy((v3f){ -1.0f, -0.25f,  1.5f }, gzoomer.wheels_local[2] );
+   v3_copy((v3f){  1.0f, -0.25f,  1.5f }, gzoomer.wheels_local[3] );
+}
+
+VG_STATIC void vehicle_wheel_force( int index )
+{
+   v3f pa, pb, n;
+   m4x3_mulv( gzoomer.obj.rb.to_world, gzoomer.wheels_local[index], pa );
+   v3_muladds( pa, gzoomer.obj.rb.to_world[1], -k_car_spring_length, pb );
+
+
+#if 1
+   float t;
+   if( spherecast_world( world_current_instance(), pa, pb, 
+                         k_car_wheel_radius, &t, n ) == -1 )
+   { t = 1.0f;
+   }
+
+#else
+
+   v3f dir;
+   v3_muls( gzoomer.rb.up, -1.0f, dir );
+   
+   ray_hit hit;
+   hit.dist = k_car_spring_length;
+   ray_world( pa, dir, &hit );
+
+   float t = hit.dist / k_car_spring_length;
+
+#endif
+
+   v3f pc;
+   v3_lerp( pa, pb, t, pc );
+
+   m4x3f mtx;
+   m3x3_copy( gzoomer.obj.rb.to_world, mtx );
+   v3_copy( pc, mtx[3] );
+   debug_sphere( mtx, k_car_wheel_radius, VG__BLACK );
+   vg_line( pa, pc, VG__WHITE );
+   v3_copy( pc, gzoomer.wheels[index] );
+
+   if( t < 1.0f ){
+      /* spring force */
+      float Fv = (1.0f-t) * k_car_spring*k_rb_delta;
+      
+      v3f delta;
+      v3_sub( pa, gzoomer.obj.rb.co, delta );
+
+      v3f rv;
+      v3_cross( gzoomer.obj.rb.w, delta, rv );
+      v3_add( gzoomer.obj.rb.v, rv, rv );
+
+      Fv += v3_dot( rv, gzoomer.obj.rb.to_world[1] ) 
+               * -k_car_spring_damp*k_rb_delta;
+
+      /* scale by normal incident */
+      Fv *= v3_dot( n, gzoomer.obj.rb.to_world[1] );
+
+      v3f F;
+      v3_muls( gzoomer.obj.rb.to_world[1], Fv, F );
+
+      rb_linear_impulse( &gzoomer.obj.rb, delta, F );
+
+      /* friction vectors
+       * -------------------------------------------------------------*/
+      v3f tx, ty;
+      
+      if( index <= 1 )
+         v3_cross( gzoomer.steerv, n, tx );
+      else
+         v3_cross( n, gzoomer.obj.rb.to_world[2], tx );
+      v3_cross( tx, n, ty );
+
+      v3_copy( tx, gzoomer.tangent_vectors[ index ][0] );
+      v3_copy( ty, gzoomer.tangent_vectors[ index ][1] );
+
+      gzoomer.normal_forces[ index ] = Fv;
+      gzoomer.tangent_forces[ index ][0] = 0.0f;
+      gzoomer.tangent_forces[ index ][1] = 0.0f;
+
+      /* orient inverse inertia tensors */
+      v3f raW;
+      m3x3_mulv( gzoomer.obj.rb.to_world, gzoomer.wheels_local[index], raW );
+
+      v3f raCtx, raCtxI, raCty, raCtyI;
+      v3_cross( tx, raW, raCtx );
+      v3_cross( ty, raW, raCty );
+      m3x3_mulv( gzoomer.obj.rb.iIw, raCtx, raCtxI );
+      m3x3_mulv( gzoomer.obj.rb.iIw, raCty, raCtyI );
+
+      gzoomer.tangent_mass[index][0] = gzoomer.obj.rb.inv_mass;
+      gzoomer.tangent_mass[index][0] += v3_dot( raCtx, raCtxI );
+      gzoomer.tangent_mass[index][0] = 1.0f/gzoomer.tangent_mass[index][0];
+      
+      gzoomer.tangent_mass[index][1] = gzoomer.obj.rb.inv_mass;
+      gzoomer.tangent_mass[index][1] += v3_dot( raCty, raCtyI );
+      gzoomer.tangent_mass[index][1] = 1.0f/gzoomer.tangent_mass[index][1];
+
+      /* apply drive force */
+      if( index >= 2 ){
+         v3_muls( ty, -gzoomer.drive * k_car_drive_force * k_rb_delta, F );
+         rb_linear_impulse( &gzoomer.obj.rb, raW, F );
+      }
+   }
+   else{
+      gzoomer.normal_forces[ index ] = 0.0f;
+      gzoomer.tangent_forces[ index ][0] = 0.0f;
+      gzoomer.tangent_forces[ index ][1] = 0.0f;
+   }
+}
+
+VG_STATIC void vehicle_solve_friction(void)
+{
+   rigidbody *rb = &gzoomer.obj.rb;
+   for( int i=0; i<4; i++ ){
+      v3f raW;
+      m3x3_mulv( rb->to_world, gzoomer.wheels_local[i], raW );
+
+      v3f rv;
+      v3_cross( rb->w, raW, rv );
+      v3_add( rb->v, rv, rv );
+
+      float     fx = k_car_friction_lat * gzoomer.normal_forces[i],
+                fy = k_car_friction_roll * gzoomer.normal_forces[i],
+               vtx = v3_dot( rv, gzoomer.tangent_vectors[i][0] ),
+               vty = v3_dot( rv, gzoomer.tangent_vectors[i][1] ),
+           lambdax = gzoomer.tangent_mass[i][0] * -vtx,
+           lambday = gzoomer.tangent_mass[i][1] * -vty;
+      
+      float tempx = gzoomer.tangent_forces[i][0],
+            tempy = gzoomer.tangent_forces[i][1];
+      gzoomer.tangent_forces[i][0] = vg_clampf( tempx + lambdax, -fx, fx );
+      gzoomer.tangent_forces[i][1] = vg_clampf( tempy + lambday, -fy, fy );
+      lambdax = gzoomer.tangent_forces[i][0] - tempx;
+      lambday = gzoomer.tangent_forces[i][1] - tempy;
+
+      v3f impulsex, impulsey;
+      v3_muls( gzoomer.tangent_vectors[i][0], lambdax, impulsex );
+      v3_muls( gzoomer.tangent_vectors[i][1], lambday, impulsey );
+      rb_linear_impulse( rb, raW, impulsex );
+      rb_linear_impulse( rb, raW, impulsey );
+   }
+}
+
+VG_STATIC void vehicle_update_fixed(void)
+{
+   if( !gzoomer.alive )
+      return;
+
+   rigidbody *rb = &gzoomer.obj.rb;
+
+   v3_muls( rb->to_world[2], -cosf(gzoomer.steer), gzoomer.steerv );
+   v3_muladds( gzoomer.steerv, rb->to_world[0], 
+               sinf(gzoomer.steer), gzoomer.steerv );
+
+   /* apply air resistance */
+   v3f Fair, Fdown;
+
+   v3_muls( rb->v, -k_car_air_resistance, Fair );
+   v3_muls( rb->to_world[1], -fabsf(v3_dot( rb->v, rb->to_world[2] )) *
+                                 k_car_downforce, Fdown );
+
+   v3_muladds( rb->v, Fair,  k_rb_delta, rb->v );
+   v3_muladds( rb->v, Fdown, k_rb_delta, rb->v );
+   
+   for( int i=0; i<4; i++ )
+      vehicle_wheel_force( i );
+
+   rb_ct manifold[64];
+   int len = rb_sphere__scene( rb->to_world, &gzoomer.obj.inf.sphere, NULL, 
+                               &world_current_instance()->rb_geo.inf.scene, 
+                               manifold );
+   for( int j=0; j<len; j++ ){
+      manifold[j].rba = rb;
+      manifold[j].rbb = &world_current_instance()->rb_geo.rb;
+   }
+   rb_manifold_filter_coplanar( manifold, len, 0.05f );
+
+   if( len > 1 ){
+      rb_manifold_filter_backface( manifold, len );
+      rb_manifold_filter_joint_edges( manifold, len, 0.05f );
+      rb_manifold_filter_pairs( manifold, len, 0.05f );
+   }
+   len = rb_manifold_apply_filtered( manifold, len );
+
+   rb_presolve_contacts( manifold, len );
+   for( int i=0; i<8; i++ ){
+      rb_solve_contacts( manifold, len );
+      vehicle_solve_friction();
+   }
+
+   rb_iter( rb );
+   rb_update_transform( rb );
+}
+
+VG_STATIC void vehicle_update_post(void)
+{
+   if( !gzoomer.alive )
+      return;
+
+   rb_object_debug( &gzoomer.obj, VG__WHITE );
+
+   /* draw friction vectors */
+   v3f p0, px, py;
+
+   for( int i=0; i<4; i++ ){
+      v3_copy( gzoomer.wheels[i], p0 );
+      v3_muladds( p0, gzoomer.tangent_vectors[i][0], 0.5f, px );
+      v3_muladds( p0, gzoomer.tangent_vectors[i][1], 0.5f, py );
+
+      vg_line( p0, px, VG__RED );
+      vg_line( p0, py, VG__GREEN );
+   }
+}
+
+#endif /* VEHICLE_H */
index c2429b90ae12ec6b1c18684a295e1f8d6b273034..b1f5a2855565332c03e167b93bafe1c03a8b6e11 100644 (file)
--- a/vehicle.h
+++ b/vehicle.h
@@ -1,11 +1,11 @@
 #ifndef VEHICLE_H
 #define VEHICLE_H
 
-#define VG_GAME
-#include "vg/vg.h"
+#include "skaterift.h"
 #include "rigidbody.h"
-#include "world.h"
 #include "player.h"
+#include "world.h"
+#include "world_physics.h"
 
 VG_STATIC float k_car_spring = 1.0f,
                 k_car_spring_damp = 0.001f,
@@ -18,7 +18,7 @@ VG_STATIC float k_car_spring = 1.0f,
                 k_car_downforce      = 0.5f;
 
 typedef struct drivable_vehicle drivable_vehicle;
-VG_STATIC struct drivable_vehicle
+struct drivable_vehicle
 {
    int alive, inside;
    rb_object obj;
@@ -35,276 +35,16 @@ VG_STATIC struct drivable_vehicle
    v3f   tangent_vectors[4][2];
    v3f   wheels_local[4];
 }
-gzoomer =
+static gzoomer =
 {
    .obj = { .type = k_rb_shape_sphere, .inf.sphere.radius = 1.0f }
 };
 
-VG_STATIC int spawn_car( int argc, const char *argv[] )
-{
-   v3f ra, rb, rx;
-   v3_copy( main_camera.pos, ra );
-   v3_muladds( ra, main_camera.transform[2], -10.0f, rb );
-
-   float t;
-   if( spherecast_world( get_active_world(), ra, rb, 
-                         gzoomer.obj.inf.sphere.radius, &t, rx ) != -1 )
-   {
-      v3_lerp( ra, rb, t, gzoomer.obj.rb.co );
-      gzoomer.obj.rb.co[1] += 4.0f;
-      q_axis_angle( gzoomer.obj.rb.q, (v3f){1.0f,0.0f,0.0f}, 0.001f );
-      v3_zero( gzoomer.obj.rb.v );
-      v3_zero( gzoomer.obj.rb.w );
-      
-      rb_update_transform( &gzoomer.obj.rb );
-      gzoomer.alive = 1;
-
-      vg_success( "Spawned car\n" );
-   }
-   else{
-      vg_error( "Can't spawn here\n" );
-   }
-
-   return 0;
-}
-
-VG_STATIC void vehicle_init(void)
-{
-   q_identity( gzoomer.obj.rb.q );
-   v3_zero( gzoomer.obj.rb.w );
-   v3_zero( gzoomer.obj.rb.v );
-   v3_zero( gzoomer.obj.rb.co );
-   rb_init_object( &gzoomer.obj );
-
-   VG_VAR_F32( k_car_spring,        flags=VG_VAR_PERSISTENT );
-   VG_VAR_F32( k_car_spring_damp,   flags=VG_VAR_PERSISTENT );
-   VG_VAR_F32( k_car_spring_length, flags=VG_VAR_PERSISTENT );
-   VG_VAR_F32( k_car_wheel_radius,  flags=VG_VAR_PERSISTENT );
-   VG_VAR_F32( k_car_friction_lat,  flags=VG_VAR_PERSISTENT );
-   VG_VAR_F32( k_car_friction_roll, flags=VG_VAR_PERSISTENT );
-   VG_VAR_F32( k_car_drive_force,   flags=VG_VAR_PERSISTENT );
-   VG_VAR_F32( k_car_air_resistance,flags=VG_VAR_PERSISTENT );
-   VG_VAR_F32( k_car_downforce,     flags=VG_VAR_PERSISTENT );
-
-   VG_VAR_I32( gzoomer.inside );
-
-   vg_console_reg_cmd( "spawn_car", spawn_car, NULL );
-
-   v3_copy((v3f){ -1.0f, -0.25f, -1.5f }, gzoomer.wheels_local[0] );
-   v3_copy((v3f){  1.0f, -0.25f, -1.5f }, gzoomer.wheels_local[1] );
-   v3_copy((v3f){ -1.0f, -0.25f,  1.5f }, gzoomer.wheels_local[2] );
-   v3_copy((v3f){  1.0f, -0.25f,  1.5f }, gzoomer.wheels_local[3] );
-}
-
-VG_STATIC void vehicle_wheel_force( int index )
-{
-   v3f pa, pb, n;
-   m4x3_mulv( gzoomer.obj.rb.to_world, gzoomer.wheels_local[index], pa );
-   v3_muladds( pa, gzoomer.obj.rb.to_world[1], -k_car_spring_length, pb );
-
-
-#if 1
-   float t;
-   if( spherecast_world( get_active_world(), pa, pb, 
-                         k_car_wheel_radius, &t, n ) == -1 )
-   { t = 1.0f;
-   }
-
-#else
-
-   v3f dir;
-   v3_muls( gzoomer.rb.up, -1.0f, dir );
-   
-   ray_hit hit;
-   hit.dist = k_car_spring_length;
-   ray_world( pa, dir, &hit );
-
-   float t = hit.dist / k_car_spring_length;
-
-#endif
-
-   v3f pc;
-   v3_lerp( pa, pb, t, pc );
-
-   m4x3f mtx;
-   m3x3_copy( gzoomer.obj.rb.to_world, mtx );
-   v3_copy( pc, mtx[3] );
-   debug_sphere( mtx, k_car_wheel_radius, VG__BLACK );
-   vg_line( pa, pc, VG__WHITE );
-   v3_copy( pc, gzoomer.wheels[index] );
-
-   if( t < 1.0f ){
-      /* spring force */
-      float Fv = (1.0f-t) * k_car_spring*k_rb_delta;
-      
-      v3f delta;
-      v3_sub( pa, gzoomer.obj.rb.co, delta );
-
-      v3f rv;
-      v3_cross( gzoomer.obj.rb.w, delta, rv );
-      v3_add( gzoomer.obj.rb.v, rv, rv );
-
-      Fv += v3_dot( rv, gzoomer.obj.rb.to_world[1] ) 
-               * -k_car_spring_damp*k_rb_delta;
-
-      /* scale by normal incident */
-      Fv *= v3_dot( n, gzoomer.obj.rb.to_world[1] );
-
-      v3f F;
-      v3_muls( gzoomer.obj.rb.to_world[1], Fv, F );
-
-      rb_linear_impulse( &gzoomer.obj.rb, delta, F );
-
-      /* friction vectors
-       * -------------------------------------------------------------*/
-      v3f tx, ty;
-      
-      if( index <= 1 )
-         v3_cross( gzoomer.steerv, n, tx );
-      else
-         v3_cross( n, gzoomer.obj.rb.to_world[2], tx );
-      v3_cross( tx, n, ty );
-
-      v3_copy( tx, gzoomer.tangent_vectors[ index ][0] );
-      v3_copy( ty, gzoomer.tangent_vectors[ index ][1] );
-
-      gzoomer.normal_forces[ index ] = Fv;
-      gzoomer.tangent_forces[ index ][0] = 0.0f;
-      gzoomer.tangent_forces[ index ][1] = 0.0f;
-
-      /* orient inverse inertia tensors */
-      v3f raW;
-      m3x3_mulv( gzoomer.obj.rb.to_world, gzoomer.wheels_local[index], raW );
-
-      v3f raCtx, raCtxI, raCty, raCtyI;
-      v3_cross( tx, raW, raCtx );
-      v3_cross( ty, raW, raCty );
-      m3x3_mulv( gzoomer.obj.rb.iIw, raCtx, raCtxI );
-      m3x3_mulv( gzoomer.obj.rb.iIw, raCty, raCtyI );
-
-      gzoomer.tangent_mass[index][0] = gzoomer.obj.rb.inv_mass;
-      gzoomer.tangent_mass[index][0] += v3_dot( raCtx, raCtxI );
-      gzoomer.tangent_mass[index][0] = 1.0f/gzoomer.tangent_mass[index][0];
-      
-      gzoomer.tangent_mass[index][1] = gzoomer.obj.rb.inv_mass;
-      gzoomer.tangent_mass[index][1] += v3_dot( raCty, raCtyI );
-      gzoomer.tangent_mass[index][1] = 1.0f/gzoomer.tangent_mass[index][1];
-
-      /* apply drive force */
-      if( index >= 2 ){
-         v3_muls( ty, -gzoomer.drive * k_car_drive_force * k_rb_delta, F );
-         rb_linear_impulse( &gzoomer.obj.rb, raW, F );
-      }
-   }
-   else{
-      gzoomer.normal_forces[ index ] = 0.0f;
-      gzoomer.tangent_forces[ index ][0] = 0.0f;
-      gzoomer.tangent_forces[ index ][1] = 0.0f;
-   }
-}
-
-VG_STATIC void vehicle_solve_friction(void)
-{
-   rigidbody *rb = &gzoomer.obj.rb;
-   for( int i=0; i<4; i++ ){
-      v3f raW;
-      m3x3_mulv( rb->to_world, gzoomer.wheels_local[i], raW );
-
-      v3f rv;
-      v3_cross( rb->w, raW, rv );
-      v3_add( rb->v, rv, rv );
-
-      float     fx = k_car_friction_lat * gzoomer.normal_forces[i],
-                fy = k_car_friction_roll * gzoomer.normal_forces[i],
-               vtx = v3_dot( rv, gzoomer.tangent_vectors[i][0] ),
-               vty = v3_dot( rv, gzoomer.tangent_vectors[i][1] ),
-           lambdax = gzoomer.tangent_mass[i][0] * -vtx,
-           lambday = gzoomer.tangent_mass[i][1] * -vty;
-      
-      float tempx = gzoomer.tangent_forces[i][0],
-            tempy = gzoomer.tangent_forces[i][1];
-      gzoomer.tangent_forces[i][0] = vg_clampf( tempx + lambdax, -fx, fx );
-      gzoomer.tangent_forces[i][1] = vg_clampf( tempy + lambday, -fy, fy );
-      lambdax = gzoomer.tangent_forces[i][0] - tempx;
-      lambday = gzoomer.tangent_forces[i][1] - tempy;
-
-      v3f impulsex, impulsey;
-      v3_muls( gzoomer.tangent_vectors[i][0], lambdax, impulsex );
-      v3_muls( gzoomer.tangent_vectors[i][1], lambday, impulsey );
-      rb_linear_impulse( rb, raW, impulsex );
-      rb_linear_impulse( rb, raW, impulsey );
-   }
-}
-
-VG_STATIC void vehicle_update_fixed(void)
-{
-   if( !gzoomer.alive )
-      return;
-
-   rigidbody *rb = &gzoomer.obj.rb;
-
-   v3_muls( rb->to_world[2], -cosf(gzoomer.steer), gzoomer.steerv );
-   v3_muladds( gzoomer.steerv, rb->to_world[0], 
-               sinf(gzoomer.steer), gzoomer.steerv );
-
-   /* apply air resistance */
-   v3f Fair, Fdown;
-
-   v3_muls( rb->v, -k_car_air_resistance, Fair );
-   v3_muls( rb->to_world[1], -fabsf(v3_dot( rb->v, rb->to_world[2] )) *
-                                 k_car_downforce, Fdown );
-
-   v3_muladds( rb->v, Fair,  k_rb_delta, rb->v );
-   v3_muladds( rb->v, Fdown, k_rb_delta, rb->v );
-   
-   for( int i=0; i<4; i++ )
-      vehicle_wheel_force( i );
-
-   rb_ct manifold[64];
-   int len = rb_sphere__scene( rb->to_world, &gzoomer.obj.inf.sphere, 
-                               NULL, &get_active_world()->rb_geo.inf.scene, 
-                               manifold );
-   for( int j=0; j<len; j++ ){
-      manifold[j].rba = rb;
-      manifold[j].rbb = &get_active_world()->rb_geo.rb;
-   }
-   rb_manifold_filter_coplanar( manifold, len, 0.05f );
-
-   if( len > 1 ){
-      rb_manifold_filter_backface( manifold, len );
-      rb_manifold_filter_joint_edges( manifold, len, 0.05f );
-      rb_manifold_filter_pairs( manifold, len, 0.05f );
-   }
-   len = rb_manifold_apply_filtered( manifold, len );
-
-   rb_presolve_contacts( manifold, len );
-   for( int i=0; i<8; i++ ){
-      rb_solve_contacts( manifold, len );
-      vehicle_solve_friction();
-   }
-
-   rb_iter( rb );
-   rb_update_transform( rb );
-}
-
-VG_STATIC void vehicle_update_post(void)
-{
-   if( !gzoomer.alive )
-      return;
-
-   rb_object_debug( &gzoomer.obj, VG__WHITE );
-
-   /* draw friction vectors */
-   v3f p0, px, py;
-
-   for( int i=0; i<4; i++ ){
-      v3_copy( gzoomer.wheels[i], p0 );
-      v3_muladds( p0, gzoomer.tangent_vectors[i][0], 0.5f, px );
-      v3_muladds( p0, gzoomer.tangent_vectors[i][1], 0.5f, py );
-
-      vg_line( p0, px, VG__RED );
-      vg_line( p0, py, VG__GREEN );
-   }
-}
+VG_STATIC int spawn_car( int argc, const char *argv[] );
+VG_STATIC void vehicle_init(void);
+VG_STATIC void vehicle_wheel_force( int index );
+VG_STATIC void vehicle_solve_friction(void);
+VG_STATIC void vehicle_update_fixed(void);
+VG_STATIC void vehicle_update_post(void);
 
 #endif /* VEHICLE_H */
index d5eb820d34b6ebe790d7c0907713d554ad959fa1..4d84fa082e0d14384f2ea044bde4cb1331699ea4 100644 (file)
@@ -9,6 +9,9 @@
 #include "vg/vg_steam_auth.h"
 #include "vg/vg_steam_ugc.h"
 #include "vg/vg_steam_friends.h"
+#include "conf.h"
+#include "steam.h"
+#include "highscores.h"
 
 #define WORKSHOP_VIEW_PER_PAGE 15
 
@@ -889,7 +892,7 @@ VG_STATIC void workshop_init(void)
 
 VG_STATIC void workshop_find_preview_entity(void)
 {
-   workshop_form.view_world = get_active_world();
+   workshop_form.view_world = world_current_instance();
 
    if( mdl_arrcount( &workshop_form.view_world->ent_swspreview ) ){
       workshop_form.ptr_ent = 
index 9029ed09d12d0bfddff000aae8edc869dcb6a67f..b7b8a2614db9019b53d4072d2adbf18684f14ed0 100644 (file)
@@ -49,4 +49,10 @@ VG_STATIC void async_workshop_get_installed_files( void *data, u32 len );
 VG_STATIC void workshop_load_metadata( const char *path,
                                        struct workshop_file_info *info );
 
+/* generic reciever */
+VG_STATIC void workshop_async_any_complete( void *data, u32 size )
+{
+   skaterift_end_op();
+}
+
 #endif /* WORKSHOP_H */
diff --git a/world.c b/world.c
new file mode 100644 (file)
index 0000000..209de3e
--- /dev/null
+++ b/world.c
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#ifndef WORLD_C
+#define WORLD_C
+
+#include "world.h"
+#include "network.h"
+
+static world_instance *world_current_instance(void){
+   return &world_static.worlds[ world_static.active_world ];
+}
+
+static void world_init(void)
+{
+   VG_VAR_F32( k_day_length );
+   VG_VAR_I32( k_debug_light_indices );
+   VG_VAR_I32( k_debug_light_complexity );
+   VG_VAR_I32( k_light_preview );
+
+   world_render.sky_rate = 1.0;
+   world_render.sky_target_rate = 1.0;
+
+   shader_scene_standard_register();
+   shader_scene_standard_alphatest_register();
+   shader_scene_vertex_blend_register();
+   shader_scene_terrain_register();
+   shader_scene_depth_register();
+   shader_scene_position_register();
+
+   shader_model_sky_register();
+
+   vg_info( "Loading world resources\n" );
+   
+   vg_linear_clear( vg_mem.scratch );
+
+   mdl_context msky;
+   mdl_open( &msky, "models/rs_skydome.mdl", vg_mem.scratch );
+   mdl_load_metadata_block( &msky, vg_mem.scratch );
+   mdl_async_load_glmesh( &msky, &world_render.skydome );
+   mdl_close( &msky );
+
+   /* Other systems */
+   vg_info( "Loading other world systems\n" );
+
+   vg_loader_step( world_render_init, NULL );
+   vg_loader_step( world_sfd_init, NULL );
+   vg_loader_step( world_water_init, NULL );
+   vg_loader_step( world_gates_init, NULL );
+   vg_loader_step( world_routes_init, NULL );
+
+   /* Allocate dynamic world memory arena */
+   u32 max_size = 76*1024*1024;
+   world_static.heap = vg_create_linear_allocator( vg_mem.rtmemory, max_size,
+                                                   VG_MEMORY_SYSTEM );
+}
+
+#include "world_entity.c"
+#include "world_gate.c"
+#include "world_gen.c"
+#include "world_load.c"
+#include "world_physics.c"
+#include "world_render.c"
+#include "world_sfd.c"
+#include "world_volumes.c"
+#include "world_water.c"
+#include "world_audio.c"
+#include "world_routes.c"
+
+VG_STATIC void world_update( world_instance *world, v3f pos )
+{
+   world_render.sky_time += world_render.sky_rate * vg.time_delta;
+   world_render.sky_rate = vg_lerp( world_render.sky_rate, 
+                                    world_render.sky_target_rate, 
+                                    vg.time_delta * 5.0 );
+
+   world_routes_update_timer_texts( world );
+   world_routes_update( world );
+   //world_routes_debug( world );
+   
+   /* ---- traffic -------- */
+
+   for( u32 i=0; i<mdl_arrcount( &world->ent_traffic ); i++ ){
+      ent_traffic *traffic = mdl_arritm( &world->ent_traffic, i );
+      
+      u32 i1 = traffic->index,
+          i0,
+          i2 = i1+1;
+
+      if( i1 == 0 ) i0 = traffic->node_count-1;
+      else i0 = i1-1;
+
+      if( i2 >= traffic->node_count ) i2 = 0;
+
+      i0 += traffic->start_node;
+      i1 += traffic->start_node;
+      i2 += traffic->start_node;
+      
+      v3f h[3];
+
+      ent_route_node *rn0 = mdl_arritm( &world->ent_route_node, i0 ),
+                     *rn1 = mdl_arritm( &world->ent_route_node, i1 ),
+                     *rn2 = mdl_arritm( &world->ent_route_node, i2 );
+
+      v3_copy( rn1->co, h[1] );
+      v3_lerp( rn0->co, rn1->co, 0.5f, h[0] );
+      v3_lerp( rn1->co, rn2->co, 0.5f, h[2] );
+
+      float const k_sample_dist = 0.0025f;
+      v3f pc, pd;
+      eval_bezier3( h[0], h[1], h[2], traffic->t, pc );
+      eval_bezier3( h[0], h[1], h[2], traffic->t+k_sample_dist, pd );
+
+      v3f v0;
+      v3_sub( pd, pc, v0 );
+      float length = vg_maxf( 0.0001f, v3_length( v0 ) );
+      v3_muls( v0, 1.0f/length, v0 );
+
+      float mod = k_sample_dist / length;
+
+      traffic->t += traffic->speed * vg.time_delta * mod;
+
+      if( traffic->t > 1.0f ){
+         traffic->t -= 1.0f;
+
+         if( traffic->t > 1.0f ) traffic->t = 0.0f;
+
+         traffic->index ++;
+
+         if( traffic->index >= traffic->node_count ) 
+            traffic->index = 0;
+      }
+
+      v3_copy( pc, traffic->transform.co );
+
+      float a = atan2f( -v0[0], v0[2] );
+      q_axis_angle( traffic->transform.q, (v3f){0.0f,1.0f,0.0f}, -a );
+
+      vg_line_pt3( traffic->transform.co, 0.3f, VG__BLUE );
+   }
+
+   /* ---- SFD ------------ */
+   
+   if( mdl_arrcount( &world->ent_route ) ){
+      u32 closest = 0;
+      float min_dist = INFINITY;
+
+      for( u32 i=0; i<mdl_arrcount( &world->ent_route ); i++ ){
+         ent_route *route = mdl_arritm( &world->ent_route, i );
+         float dist = v3_dist2( route->board_transform[3], pos );
+
+         if( dist < min_dist ){
+            min_dist = dist;
+            closest = i;
+         }
+      }
+
+      if( (world_sfd.active_route_board != closest) || network_scores_updated )
+      {
+         network_scores_updated = 0;
+         world_sfd.active_route_board = closest;
+
+         ent_route *route = mdl_arritm( &world->ent_route, closest );
+         u32 id = route->official_track_id;
+
+         if( id != 0xffffffff ){
+            struct netmsg_board *local_board = 
+               &scoreboard_client_data.boards[id];
+
+            for( int i=0; i<13; i++ ){
+               sfd_encode( i, &local_board->data[27*i] );
+            }
+         }else{
+            sfd_encode( 0, mdl_pstr( &world->meta, route->pstr_name ) );
+            sfd_encode( 1, "No data" );
+         }
+      }
+   }
+   sfd_update();
+
+   static float random_accum = 0.0f;
+   random_accum += vg.time_delta;
+
+   u32 random_ticks = 0;
+
+   while( random_accum > 0.1f ){
+      random_accum -= 0.1f;
+      random_ticks ++;
+   }
+
+   float radius = 25.0f;
+   boxf volume_proximity;
+   v3_add( pos, (v3f){ radius, radius, radius }, volume_proximity[1] );
+   v3_sub( pos, (v3f){ radius, radius, radius }, volume_proximity[0] );
+
+   bh_iter it;
+   bh_iter_init_box( 0, &it, volume_proximity );
+   i32 idx;
+
+   int in_volume = 0;
+
+   while( bh_next( world->volume_bh, &it, &idx ) ){
+      ent_volume *volume = mdl_arritm( &world->ent_volume, idx );
+
+      boxf cube = {{-1.0f,-1.0f,-1.0f},{1.0f,1.0f,1.0f}};
+      
+      if( volume->type == k_volume_subtype_trigger ){
+         v3f local;
+         m4x3_mulv( volume->to_local, pos, local );
+
+         if( (fabsf(local[0]) <= 1.0f) &&
+             (fabsf(local[1]) <= 1.0f) &&
+             (fabsf(local[2]) <= 1.0f) )
+         {
+            in_volume = 1;
+            vg_line_boxf_transformed( volume->to_world, cube, 0xff00ff00 );
+
+            if( !world_static.in_volume ){
+               ent_call basecall;
+               basecall.function = k_ent_function_trigger;
+               basecall.id = mdl_entity_id( k_ent_volume, idx );
+               basecall.data = NULL;
+
+               entity_call( world, &basecall );
+            }
+         }
+         else
+            vg_line_boxf_transformed( volume->to_world, cube, 0xff0000ff );
+      }
+      else if( volume->type == k_volume_subtype_particle ){
+         vg_line_boxf_transformed( volume->to_world, cube, 0xff00c0ff );
+
+         for( int j=0; j<random_ticks; j++ ){
+            ent_call basecall;
+            basecall.id = mdl_entity_id( k_ent_volume, idx );
+            basecall.data = NULL;
+
+            entity_call( world, &basecall );
+         }
+      }
+   }
+   world_static.in_volume = in_volume;
+
+#if 0
+   if( k_debug_light_indices )
+   {
+      for( int i=0; i<world->light_count; i++ ){
+         struct world_light *light = &world->lights[i];
+         struct classtype_world_light *inf = light->inf;
+
+         u32 colour = 0xff000000;
+         u8 r = inf->colour[0] * 255.0f,
+            g = inf->colour[1] * 255.0f,
+            b = inf->colour[2] * 255.0f;
+
+         colour |= r;
+         colour |= g << 8;
+         colour |= b << 16;
+
+         vg_line_pt3( light->node->co, 0.25f, colour );
+      }
+   }
+
+#endif
+}
+
+#endif /* WORLD_C */
diff --git a/world.h b/world.h
index 98d7efbe611fdab0930a5e880f5bbf2a40b4accd..d6f80412bb97f00336614b124198b905d2fcd058 100644 (file)
--- a/world.h
+++ b/world.h
@@ -1,62 +1,46 @@
 /*
- * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
  */
 
-#include "common.h"
-
 #ifndef WORLD_H
 #define WORLD_H
 
-typedef struct world_instance world_instance;
+#include "render.h"
 
-#include "vg/vg_loader.h"
+/* types
+ */
 
-#include "network.h"
-#include "network_msg.h"
-#include "scene.h"
-#include "render.h"
-#include "rigidbody.h"
-#include "bvh.h"
-#include "model.h"
-#include "entity.h"
-#include "font.h"
-
-#include "shaders/scene_standard.h"
-#include "shaders/scene_standard_alphatest.h"
-#include "shaders/scene_vertex_blend.h"
-#include "shaders/scene_terrain.h"
-#include "shaders/scene_depth.h"
-#include "shaders/scene_position.h"
-
-#include "shaders/model_sky.h"
-
-enum { k_max_ui_segments = 8 };
-
-enum { k_max_ui_elements = k_max_ui_segments };
-enum { k_max_element_verts = 10 };
-enum { k_max_element_indices = 20 };
-
-enum { k_route_ui_max_verts = k_max_ui_elements*k_max_element_verts };
-enum { k_route_ui_max_indices = k_max_ui_elements*k_max_element_indices };
-
-enum logic_type
-{
-   k_logic_type_relay         = 1,
-   k_logic_type_chance        = 2,
-   k_logic_type_achievement   = 3
+enum world_geo_type{
+   k_world_geo_type_solid = 0,
+   k_world_geo_type_nonsolid = 1,
+   k_world_geo_type_water = 2
 };
 
-enum geo_type
-{
-   k_geo_type_solid = 0,
-   k_geo_type_nonsolid = 1,
-   k_geo_type_water = 2
-};
+typedef struct world_instance world_instance;
 
-static const float k_light_cube_size = 8.0f;
+/* submodule headers */
+#include "world_entity.h"
+#include "world_gate.h"
+#include "world_gen.h"
+#include "world_info.h"
+#include "world_load.h"
+#include "world_physics.h"
+#include "world_render.h"
+#include "world_sfd.h"
+#include "world_volumes.h"
+#include "world_water.h"
+#include "world_audio.h"
+#include "world_routes.h"
 
-struct world_instance {
+/* console variables */
+
+static float k_day_length            = 30.0f; /* minutes */
+static int   k_debug_light_indices   = 0,
+             k_debug_light_complexity= 0,
+             k_light_preview         = 0;
 
+
+struct world_instance {
    /* Fixed items
     * -------------------------------------------------------
     */
@@ -134,8 +118,6 @@ struct world_instance {
     *
     *                  the following arrays index somewhere into this linear 
     *                  allocator
-    *
-    * (world_gen.h)
     * --------------------------------------------------------------------------
     */
 
@@ -199,791 +181,24 @@ struct world_instance {
    rb_object rb_geo;
 };
 
-struct world_global{
+struct world_static {
    /*
     * Allocated as system memory
     * --------------------------------------------------------------------------
     */
    void *heap;
-   char *load_target;
-
-   /* rendering */
-   glmesh skydome;
-   glmesh mesh_gate;
-   mdl_submesh sm_gate_surface,
-               sm_gate_marker[4];
-
-   double sky_time, sky_rate, sky_target_rate;
 
    u32 current_run_version;
    double time, rewind_from, rewind_to, last_use;
 
-   /* water rendering */
-   struct{
-      struct framebuffer fbreflect, fbdepth;
-   }
-   water;
-
-   /* split flap display */
-   struct{
-      glmesh mesh_base, mesh_display;
-      mdl_submesh sm_base;
-      u32 active_route_board;
-      scene_context scene;
-
-      u32 w, h;
-      float *buffer;
-   }
-   sfd;
-
-   v3f render_gate_pos;
    int in_volume;
 
    world_instance worlds[4];
    u32            active_world;
-
-   /* text particles */
-   font3d font;
-
-   struct timer_text{
-      char text[8];
-      m4x3f transform;
-      ent_gate *gate;
-      ent_route *route;
-   }
-   timer_texts[4];
-   u32 timer_text_count;
-
-   struct text_particle{
-      rb_object obj;
-      m4x3f mlocal;
-      ent_glyph *glyph;
-      v4f colour;
-
-      m4x3f mdl;
-   }
-   text_particles[6*4];
-   u32 text_particle_count;
-}
-static world_global;
-VG_STATIC void entity_call( world_instance *world, ent_call *call );
-
-VG_STATIC world_instance *get_active_world( void )
-{
-   return &world_global.worlds[ world_global.active_world ];
 }
+static world_static;
 
-/*
- * API
- */
-
-VG_STATIC 
-int ray_hit_is_ramp( world_instance *world, ray_hit *hit );
-
-VG_STATIC 
-struct world_surface *ray_hit_surface( world_instance *world, ray_hit *hit );
-
-VG_STATIC 
-void ray_world_get_tri( world_instance *world, ray_hit *hit, v3f tri[3] );
-
-VG_STATIC 
-int ray_world( world_instance *world, v3f pos, v3f dir, ray_hit *hit );
-
-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;
-}
-
-/*
- * Submodules
- */
-
-VG_STATIC float
-   k_day_length            = 30.0f; /* minutes */
-
-VG_STATIC int   k_debug_light_indices = 0,
-                k_debug_light_complexity = 0,
-                k_light_preview = 0;
-
-#include "world_routes.h"
-#include "world_sfd.h"
-#include "world_render.h"
-#include "world_water.h"
-#include "world_volumes.h"
-#include "world_gen.h"
-#include "world_gate.h"
-
-/* 
- * -----------------------------------------------------------------------------
- *                                    Events
- * -----------------------------------------------------------------------------
- */
-
-VG_STATIC int world_stop_sound( int argc, const char *argv[] )
-{
-   world_instance *world = get_active_world();
-   return 0;
-}
-
-VG_STATIC void world_init(void)
-{
-   VG_VAR_F32( k_day_length );
-   VG_VAR_I32( k_debug_light_indices );
-   VG_VAR_I32( k_debug_light_complexity );
-   VG_VAR_I32( k_light_preview );
-
-   world_global.sky_rate = 1.0;
-   world_global.sky_target_rate = 1.0;
-
-   shader_scene_standard_register();
-   shader_scene_standard_alphatest_register();
-   shader_scene_vertex_blend_register();
-   shader_scene_terrain_register();
-   shader_scene_depth_register();
-   shader_scene_position_register();
-
-   shader_model_sky_register();
-
-   vg_info( "Loading world resources\n" );
-   
-   vg_linear_clear( vg_mem.scratch );
-
-   mdl_context msky;
-   mdl_open( &msky, "models/rs_skydome.mdl", vg_mem.scratch );
-   mdl_load_metadata_block( &msky, vg_mem.scratch );
-   mdl_async_load_glmesh( &msky, &world_global.skydome );
-   mdl_close( &msky );
-
-   /* Other systems */
-   vg_info( "Loading other world systems\n" );
-
-   vg_loader_step( world_render_init, NULL );
-   vg_loader_step( world_sfd_init, NULL );
-   vg_loader_step( world_water_init, NULL );
-   vg_loader_step( world_gates_init, NULL );
-   vg_loader_step( world_routes_init, NULL );
-
-   /* Allocate dynamic world memory arena */
-   u32 max_size = 76*1024*1024;
-   world_global.heap = vg_create_linear_allocator( vg_mem.rtmemory, max_size,
-                                                   VG_MEMORY_SYSTEM );
-}
-
-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_global.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;
-      }
-   }
-}
-
-/* finds any active playing in world and fades them out, we can only do this 
- * while unloading */
-VG_STATIC void world_fadeout_audio( world_instance *world )
-{
-   if( world->status != k_world_status_unloading ){
-      vg_fatal_error( "World status must be set to 'unloading', to fadeout"
-                      " audio.\n" );
-   }
-
-   u8 world_id = (world - world_global.worlds) + 1;
-
-   audio_lock();
-   for( u32 i=0; i<AUDIO_CHANNELS; i++ ){
-      audio_channel *ch = &vg_audio.channels[i];
-
-      if( ch->allocated && (ch->world_id == world_id) ){
-         ch = audio_channel_fadeout( ch, 1.0f );
-      }
-   }
-   audio_unlock();
-}
-
-VG_STATIC void world_update( world_instance *world, v3f pos )
-{
-   world_global.sky_time += world_global.sky_rate * vg.time_delta;
-   world_global.sky_rate = vg_lerp( world_global.sky_rate, 
-                                    world_global.sky_target_rate, 
-                                    vg.time_delta * 5.0 );
-
-   world_routes_update_timer_texts( world );
-   world_routes_update( world );
-   //world_routes_debug( world );
-   
-   /* ---- traffic -------- */
-
-   for( u32 i=0; i<mdl_arrcount( &world->ent_traffic ); i++ ){
-      ent_traffic *traffic = mdl_arritm( &world->ent_traffic, i );
-      
-      u32 i1 = traffic->index,
-          i0,
-          i2 = i1+1;
-
-      if( i1 == 0 ) i0 = traffic->node_count-1;
-      else i0 = i1-1;
-
-      if( i2 >= traffic->node_count ) i2 = 0;
-
-      i0 += traffic->start_node;
-      i1 += traffic->start_node;
-      i2 += traffic->start_node;
-      
-      v3f h[3];
-
-      ent_route_node *rn0 = mdl_arritm( &world->ent_route_node, i0 ),
-                     *rn1 = mdl_arritm( &world->ent_route_node, i1 ),
-                     *rn2 = mdl_arritm( &world->ent_route_node, i2 );
-
-      v3_copy( rn1->co, h[1] );
-      v3_lerp( rn0->co, rn1->co, 0.5f, h[0] );
-      v3_lerp( rn1->co, rn2->co, 0.5f, h[2] );
-
-      float const k_sample_dist = 0.0025f;
-      v3f pc, pd;
-      eval_bezier3( h[0], h[1], h[2], traffic->t, pc );
-      eval_bezier3( h[0], h[1], h[2], traffic->t+k_sample_dist, pd );
-
-      v3f v0;
-      v3_sub( pd, pc, v0 );
-      float length = vg_maxf( 0.0001f, v3_length( v0 ) );
-      v3_muls( v0, 1.0f/length, v0 );
-
-      float mod = k_sample_dist / length;
-
-      traffic->t += traffic->speed * vg.time_delta * mod;
-
-      if( traffic->t > 1.0f ){
-         traffic->t -= 1.0f;
-
-         if( traffic->t > 1.0f ) traffic->t = 0.0f;
-
-         traffic->index ++;
-
-         if( traffic->index >= traffic->node_count ) 
-            traffic->index = 0;
-      }
-
-      v3_copy( pc, traffic->transform.co );
-
-      float a = atan2f( -v0[0], v0[2] );
-      q_axis_angle( traffic->transform.q, (v3f){0.0f,1.0f,0.0f}, -a );
-
-      vg_line_pt3( traffic->transform.co, 0.3f, VG__BLUE );
-   }
-
-   /* ---- SFD ------------ */
-   
-   if( mdl_arrcount( &world->ent_route ) ){
-      u32 closest = 0;
-      float min_dist = INFINITY;
-
-      for( u32 i=0; i<mdl_arrcount( &world->ent_route ); i++ ){
-         ent_route *route = mdl_arritm( &world->ent_route, i );
-         float dist = v3_dist2( route->board_transform[3], pos );
-
-         if( dist < min_dist ){
-            min_dist = dist;
-            closest = i;
-         }
-      }
-
-      if( (world_global.sfd.active_route_board != closest) 
-          || network_scores_updated )
-      {
-         network_scores_updated = 0;
-         world_global.sfd.active_route_board = closest;
-
-         ent_route *route = mdl_arritm( &world->ent_route, closest );
-         u32 id = route->official_track_id;
-
-         if( id != 0xffffffff ){
-            struct netmsg_board *local_board = 
-               &scoreboard_client_data.boards[id];
-
-            for( int i=0; i<13; i++ ){
-               sfd_encode( i, &local_board->data[27*i] );
-            }
-         }else{
-            sfd_encode( 0, mdl_pstr( &world->meta, route->pstr_name ) );
-            sfd_encode( 1, "No data" );
-         }
-      }
-   }
-   sfd_update();
-
-   static float random_accum = 0.0f;
-   random_accum += vg.time_delta;
-
-   u32 random_ticks = 0;
-
-   while( random_accum > 0.1f ){
-      random_accum -= 0.1f;
-      random_ticks ++;
-   }
-
-   float radius = 25.0f;
-   boxf volume_proximity;
-   v3_add( pos, (v3f){ radius, radius, radius }, volume_proximity[1] );
-   v3_sub( pos, (v3f){ radius, radius, radius }, volume_proximity[0] );
-
-   bh_iter it;
-   bh_iter_init_box( 0, &it, volume_proximity );
-   i32 idx;
-
-   int in_volume = 0;
-
-   while( bh_next( world->volume_bh, &it, &idx ) ){
-      ent_volume *volume = mdl_arritm( &world->ent_volume, idx );
-
-      boxf cube = {{-1.0f,-1.0f,-1.0f},{1.0f,1.0f,1.0f}};
-      
-      if( volume->type == k_volume_subtype_trigger ){
-         v3f local;
-         m4x3_mulv( volume->to_local, pos, local );
-
-         if( (fabsf(local[0]) <= 1.0f) &&
-             (fabsf(local[1]) <= 1.0f) &&
-             (fabsf(local[2]) <= 1.0f) )
-         {
-            in_volume = 1;
-            vg_line_boxf_transformed( volume->to_world, cube, 0xff00ff00 );
-
-            if( !world_global.in_volume ){
-               ent_call basecall;
-               basecall.function = k_ent_function_trigger;
-               basecall.id = mdl_entity_id( k_ent_volume, idx );
-               basecall.data = NULL;
-
-               entity_call( world, &basecall );
-            }
-         }
-         else
-            vg_line_boxf_transformed( volume->to_world, cube, 0xff0000ff );
-      }
-      else if( volume->type == k_volume_subtype_particle ){
-         vg_line_boxf_transformed( volume->to_world, cube, 0xff00c0ff );
-
-         for( int j=0; j<random_ticks; j++ ){
-            ent_call basecall;
-            basecall.id = mdl_entity_id( k_ent_volume, idx );
-            basecall.data = NULL;
-
-            entity_call( world, &basecall );
-         }
-      }
-   }
-   world_global.in_volume = in_volume;
-
-#if 0
-   if( k_debug_light_indices )
-   {
-      for( int i=0; i<world->light_count; i++ ){
-         struct world_light *light = &world->lights[i];
-         struct classtype_world_light *inf = light->inf;
-
-         u32 colour = 0xff000000;
-         u8 r = inf->colour[0] * 255.0f,
-            g = inf->colour[1] * 255.0f,
-            b = inf->colour[2] * 255.0f;
-
-         colour |= r;
-         colour |= g << 8;
-         colour |= b << 16;
-
-         vg_line_pt3( light->node->co, 0.25f, colour );
-      }
-   }
-
-#endif
-}
-
-/* 
- * -----------------------------------------------------------------------------
- *                              API implementation
- * -----------------------------------------------------------------------------
- */
-
-VG_STATIC void ray_world_get_tri( world_instance *world,
-                                  ray_hit *hit, v3f tri[3] )
-{
-   for( int i=0; i<3; i++ )
-      v3_copy( world->scene_geo.arrvertices[ hit->tri[i] ].co, tri[i] );
-}
-
-VG_STATIC int ray_world( world_instance *world,
-                         v3f pos, v3f dir, ray_hit *hit )
-{
-   return scene_raycast( &world->scene_geo, world->geo_bh, pos, dir, hit );
-}
-
-/*
- * Cast a sphere from a to b and see what time it hits
- */
-VG_STATIC int spherecast_world( world_instance *world,
-                                v3f pa, v3f pb, float r, float *t, v3f n )
-{
-   boxf region;
-   box_init_inf( region );
-   box_addpt( region, pa );
-   box_addpt( region, pb );
-   
-   v3_add( (v3f){ r, r, r}, region[1], region[1] );
-   v3_add( (v3f){-r,-r,-r}, region[0], region[0] );
-
-   v3f dir;
-   v3_sub( pb, pa, dir );
-
-   v3f dir_inv;
-   dir_inv[0] = 1.0f/dir[0];
-   dir_inv[1] = 1.0f/dir[1];
-   dir_inv[2] = 1.0f/dir[2];
-
-   int hit = -1;
-   float min_t = 1.0f;
-
-   bh_iter it;
-   bh_iter_init_box( 0, &it, region );
-   i32 idx;
-   while( bh_next( world->geo_bh, &it, &idx ) ){
-      u32 *ptri = &world->scene_geo.arrindices[ idx*3 ];
-      v3f tri[3];
-
-      boxf box;
-      box_init_inf( box );
-
-      for( int j=0; j<3; j++ ){
-         v3_copy( world->scene_geo.arrvertices[ptri[j]].co, tri[j] );
-         box_addpt( box, tri[j] );
-      }
-
-      v3_add( (v3f){ r, r, r}, box[1], box[1] );
-      v3_add( (v3f){-r,-r,-r}, box[0], box[0] );
-
-      if( !ray_aabb1( box, pa, dir_inv, 1.0f ) )
-         continue;
-      
-      float t;
-      v3f n1;
-      if( spherecast_triangle( tri, pa, dir, r, &t, n1 ) ){
-         if( t < min_t ){
-            min_t = t;
-            hit = idx;
-            v3_copy( n1, n );
-         }
-      }
-   }
-
-   *t = min_t;
-   return hit;
-}
-
-VG_STATIC 
-struct world_surface *world_tri_index_surface( world_instance *world, 
-                                                 u32 index )
-{
-   for( int i=1; i<world->surface_count; i++ ){
-      struct world_surface *surf = &world->surfaces[i];
-
-      if( (index >= surf->sm_geo.vertex_start) &&
-          (index  < surf->sm_geo.vertex_start+surf->sm_geo.vertex_count ) )
-      {
-         return surf;
-      }
-   }
-
-   return &world->surfaces[0];
-}
-
-VG_STATIC struct world_surface *world_contact_surface( world_instance *world,
-                                                         rb_ct *ct )
-{
-   return world_tri_index_surface( world, ct->element_id );
-}
-
-VG_STATIC struct world_surface *ray_hit_surface( world_instance *world,
-                                                 ray_hit *hit )
-{
-   return world_tri_index_surface( world, hit->tri[0] );
-}
-
-/* 
- * -----------------------------------------------------------------------------
- *                              Audio sampling
- * -----------------------------------------------------------------------------
- */
-
-VG_STATIC 
-enum audio_sprite_type world_audio_sample_sprite_kandom(v3f origin, v3f output);
-VG_STATIC void world_audio_sample_distances( v3f co, int *index, float *value );
-
-#include "audio.h"
-
-/*
- * Trace out a random point, near the player to try and determine water areas
- */
-VG_STATIC 
-enum audio_sprite_type world_audio_sample_sprite_random(v3f origin, v3f output)
-{
-   v3f chance = { (vg_randf64()-0.5f) * 30.0f, 
-                  8.0f,
-                  (vg_randf64()-0.5f) * 30.0f };
-   
-   v3f pos;
-   v3_add( chance, origin, pos );
-
-   ray_hit contact;
-   contact.dist = vg_minf( 16.0f, pos[1] );
-
-   world_instance *world = get_active_world();
-   
-   if( ray_world( world, pos, (v3f){0.0f,-1.0f,0.0f}, &contact ) ){
-      struct world_surface *mat = ray_hit_surface( world, &contact );
-
-      if( mat->info.surface_prop == k_surface_prop_grass){
-         v3_copy( contact.pos, output );
-         return k_audio_sprite_type_grass;
-      }
-      else{
-         return k_audio_sprite_type_none;
-      }
-   }
-
-   output[0] = pos[0];
-   output[1] = 0.0f;
-   output[2] = pos[2];
-
-   float dist = fabsf(output[1] - origin[1]);
-   
-   if( world->water.enabled && dist<=40.0f )
-      return k_audio_sprite_type_water;
-   else
-      return k_audio_sprite_type_none;
-}
-
-VG_STATIC void world_audio_sample_distances( v3f co, int *index, float *value )
-{
-   float inr3 = 0.57735027,
-         inr2 = 0.70710678118;
-
-   v3f sample_directions[] = {
-      { -1.0f,  0.0f,  0.0f },
-      {  1.0f,  0.0f,  0.0f },
-      {  0.0f,  0.0f,  1.0f },
-      {  0.0f,  0.0f, -1.0f },
-      {  0.0f,  1.0f,  0.0f },
-      {  0.0f, -1.0f,  0.0f },
-      { -inr3,  inr3,  inr3 },
-      {  inr3,  inr3,  inr3 },
-      { -inr3,  inr3, -inr3 },
-      {  inr3,  inr3, -inr3 },
-      { -inr2,  0.0f,  inr2 },
-      {  inr2,  0.0f,  inr2 },
-      { -inr2,  0.0f, -inr2 },
-      {  inr2,  0.0f, -inr2 },
-   };
-
-   static int si = 0;
-   static float distances[16];
-
-   ray_hit ray;
-   ray.dist = 5.0f;
-
-   v3f rc, rd, ro;
-   v3_copy( sample_directions[ si ], rd );
-   v3_add( co, (v3f){0.0f,1.5f,0.0f}, ro );
-   v3_copy( ro, rc );
-
-   float dist = 200.0f;
-
-   for( int i=0; i<10; i++ ){
-      if( ray_world( get_active_world(), rc, rd, &ray ) ){
-         dist = (float)i*5.0f + ray.dist;
-         break;
-      }
-      else{
-         v3_muladds( rc, rd, ray.dist, rc );
-      }
-   }
-
-   distances[si] = dist;
-
-   if( vg_lines.draw ){
-      for( int i=0; i<14; i++ ){
-         if( distances[i] != 200.0f ){
-            u32 colours[] = { VG__RED, VG__BLUE, VG__GREEN,
-                              VG__CYAN, VG__YELOW, VG__PINK,
-                              VG__WHITE };
-
-            u32 colour = colours[i%7];
-
-            v3f p1;
-            v3_muladds( ro, sample_directions[i], distances[i], p1 );
-            vg_line( ro, p1, colour );
-            vg_line_pt3( p1, 0.1f, colour );
-         }
-      }
-   }
-
-   *index = si;
-   *value = dist;
-
-   si ++;
-   if( si >= 14 )
-      si = 0;
-}
+static void world_init(void);
+static world_instance *world_current_instance(void);
 
 #endif /* WORLD_H */
diff --git a/world_audio.c b/world_audio.c
new file mode 100644 (file)
index 0000000..facbcc4
--- /dev/null
@@ -0,0 +1,143 @@
+#ifndef WORLD_AUDIO_C
+#define WORLD_AUDIO_C
+
+#include "audio.h"
+#include "world_audio.h"
+
+/* finds any active playing in world and fades them out, we can only do this 
+ * while unloading */
+VG_STATIC void world_fadeout_audio( world_instance *world )
+{
+   if( world->status != k_world_status_unloading ){
+      vg_fatal_error( "World status must be set to 'unloading', to fadeout"
+                      " audio.\n" );
+   }
+
+   u8 world_id = (world - world_static.worlds) + 1;
+
+   audio_lock();
+   for( u32 i=0; i<AUDIO_CHANNELS; i++ ){
+      audio_channel *ch = &vg_audio.channels[i];
+
+      if( ch->allocated && (ch->world_id == world_id) ){
+         ch = audio_channel_fadeout( ch, 1.0f );
+      }
+   }
+   audio_unlock();
+}
+
+/*
+ * Trace out a random point, near the player to try and determine water areas
+ */
+VG_STATIC 
+enum audio_sprite_type world_audio_sample_sprite_random(v3f origin, v3f output)
+{
+   v3f chance = { (vg_randf64()-0.5f) * 30.0f, 
+                  8.0f,
+                  (vg_randf64()-0.5f) * 30.0f };
+   
+   v3f pos;
+   v3_add( chance, origin, pos );
+
+   ray_hit contact;
+   contact.dist = vg_minf( 16.0f, pos[1] );
+
+   world_instance *world = world_current_instance();
+   
+   if( ray_world( world, pos, (v3f){0.0f,-1.0f,0.0f}, &contact ) ){
+      struct world_surface *mat = ray_hit_surface( world, &contact );
+
+      if( mat->info.surface_prop == k_surface_prop_grass){
+         v3_copy( contact.pos, output );
+         return k_audio_sprite_type_grass;
+      }
+      else{
+         return k_audio_sprite_type_none;
+      }
+   }
+
+   output[0] = pos[0];
+   output[1] = 0.0f;
+   output[2] = pos[2];
+
+   float dist = fabsf(output[1] - origin[1]);
+   
+   if( world->water.enabled && dist<=40.0f )
+      return k_audio_sprite_type_water;
+   else
+      return k_audio_sprite_type_none;
+}
+
+VG_STATIC void world_audio_sample_distances( v3f co, int *index, float *value )
+{
+   float inr3 = 0.57735027,
+         inr2 = 0.70710678118;
+
+   v3f sample_directions[] = {
+      { -1.0f,  0.0f,  0.0f },
+      {  1.0f,  0.0f,  0.0f },
+      {  0.0f,  0.0f,  1.0f },
+      {  0.0f,  0.0f, -1.0f },
+      {  0.0f,  1.0f,  0.0f },
+      {  0.0f, -1.0f,  0.0f },
+      { -inr3,  inr3,  inr3 },
+      {  inr3,  inr3,  inr3 },
+      { -inr3,  inr3, -inr3 },
+      {  inr3,  inr3, -inr3 },
+      { -inr2,  0.0f,  inr2 },
+      {  inr2,  0.0f,  inr2 },
+      { -inr2,  0.0f, -inr2 },
+      {  inr2,  0.0f, -inr2 },
+   };
+
+   static int si = 0;
+   static float distances[16];
+
+   ray_hit ray;
+   ray.dist = 5.0f;
+
+   v3f rc, rd, ro;
+   v3_copy( sample_directions[ si ], rd );
+   v3_add( co, (v3f){0.0f,1.5f,0.0f}, ro );
+   v3_copy( ro, rc );
+
+   float dist = 200.0f;
+
+   for( int i=0; i<10; i++ ){
+      if( ray_world( world_current_instance(), rc, rd, &ray ) ){
+         dist = (float)i*5.0f + ray.dist;
+         break;
+      }
+      else{
+         v3_muladds( rc, rd, ray.dist, rc );
+      }
+   }
+
+   distances[si] = dist;
+
+   if( vg_lines.draw ){
+      for( int i=0; i<14; i++ ){
+         if( distances[i] != 200.0f ){
+            u32 colours[] = { VG__RED, VG__BLUE, VG__GREEN,
+                              VG__CYAN, VG__YELOW, VG__PINK,
+                              VG__WHITE };
+
+            u32 colour = colours[i%7];
+
+            v3f p1;
+            v3_muladds( ro, sample_directions[i], distances[i], p1 );
+            vg_line( ro, p1, colour );
+            vg_line_pt3( p1, 0.1f, colour );
+         }
+      }
+   }
+
+   *index = si;
+   *value = dist;
+
+   si ++;
+   if( si >= 14 )
+      si = 0;
+}
+
+#endif /* WORLD_AUDIO_C */
diff --git a/world_audio.h b/world_audio.h
new file mode 100644 (file)
index 0000000..285dc23
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef WORLD_AUDIO_H
+#define WORLD_AUDIO_H
+
+#include "world.h"
+
+VG_STATIC void world_fadeout_audio( world_instance *world );
+VG_STATIC void world_audio_sample_distances( v3f co, int *index, float *value );
+VG_STATIC enum audio_sprite_type world_audio_sample_sprite_random( v3f origin, 
+                                                                   v3f output );
+
+#endif /* WORLD_AUDIO_H */
diff --git a/world_entity.c b/world_entity.c
new file mode 100644 (file)
index 0000000..38bf86f
--- /dev/null
@@ -0,0 +1,259 @@
+#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 */
diff --git a/world_entity.h b/world_entity.h
new file mode 100644 (file)
index 0000000..807cd97
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef WORLD_ENTITY_H
+#define WORLD_ENTITY_H
+
+#include "world.h"
+#include "entity.h"
+
+VG_STATIC void world_gen_entities_init(void);
+VG_STATIC ent_spawn *world_find_spawn_by_name( world_instance *world, 
+                                               const char *name );
+VG_STATIC ent_spawn *world_find_closest_spawn( world_instance *world, 
+                                               v3f position );
+
+VG_STATIC void ent_volume_call( world_instance *world, ent_call *call );
+VG_STATIC void ent_audio_call( world_instance *world, ent_call *call );
+
+#endif /* WORLD_ENTITY_H */
diff --git a/world_gate.c b/world_gate.c
new file mode 100644 (file)
index 0000000..7a5360a
--- /dev/null
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#ifndef WORLD_GATE_C
+#define WORLD_GATE_C
+
+#include "world.h"
+#include "world_gate.h"
+
+#include "skaterift.h"
+#include "common.h"
+#include "model.h"
+#include "entity.h"
+#include "render.h"
+#include "camera.h"
+
+#include "world_water.h"
+
+/*
+ * Update the transform matrices for gate
+ */
+VG_STATIC void gate_transform_update( ent_gate *gate )
+{
+   m4x3f to_local, recv_to_world;
+
+   q_m3x3( gate->q[0], gate->to_world );
+   v3_copy( gate->co[0], gate->to_world[3] );
+   
+   m4x3_invert_affine( gate->to_world, to_local );
+
+   q_m3x3( gate->q[1], recv_to_world );
+   v3_copy( gate->co[1], recv_to_world[3] );
+   m4x3_mul( recv_to_world, to_local, gate->transport );
+
+   m3x3_scale( gate->to_world, (v3f){ gate->dimensions[0], 
+                                      gate->dimensions[1], 1.0f } );
+}
+
+VG_STATIC void world_gates_init(void)
+{
+   vg_info( "world_gates_init\n" );
+
+   shader_model_gate_register();
+
+   vg_linear_clear( vg_mem.scratch );
+
+   mdl_context mgate;
+   mdl_open( &mgate, "models/rs_gate.mdl", vg_mem.scratch );
+   mdl_load_metadata_block( &mgate, vg_mem.scratch );
+
+   mdl_mesh *surface = mdl_find_mesh( &mgate, "rs_gate" );
+   mdl_submesh *sm = mdl_arritm(&mgate.submeshs,surface->submesh_start);
+   world_gates.sm_surface = *sm;
+
+   const char *names[] = { "rs_gate_marker", "rs_gate_marker.001", 
+                           "rs_gate_marker.002", "rs_gate_marker.003" };
+
+   for( int i=0; i<4; i++ ){
+      mdl_mesh *marker = mdl_find_mesh( &mgate, names[i] );
+      sm = mdl_arritm( &mgate.submeshs, marker->submesh_start );
+      world_gates.sm_marker[i] = *sm;
+   }
+
+   mdl_async_load_glmesh( &mgate, &world_gates.mesh );
+   mdl_close( &mgate );
+}
+
+/*
+ * Render the view through a gate
+ */
+VG_STATIC int render_gate( world_instance *world_inside,
+                           ent_gate *gate, camera *cam, int layer_depth )
+{
+   v3f viewdir, gatedir;
+   m3x3_mulv( cam->transform, (v3f){0.0f,0.0f,-1.0f}, viewdir );
+   q_mulv( gate->q[0], (v3f){0.0f,0.0f,-1.0f}, gatedir );
+
+   v3f v0;
+   v3_sub( cam->pos, gate->co[0], v0 );
+
+   float dist = v3_dot(v0, gatedir);
+
+   /* Hard cutoff */
+   if( dist > 3.0f )
+      return 0;
+
+   if( v3_dist( cam->pos, gate->co[0] ) > 100.0f )
+      return 0;
+
+   {
+      v3f a,b,c,d;
+
+      m4x3_mulv( gate->to_world, (v3f){-1.0f,-1.0f,0.0f}, a );
+      m4x3_mulv( gate->to_world, (v3f){ 1.0f,-1.0f,0.0f}, b );
+      m4x3_mulv( gate->to_world, (v3f){ 1.0f, 1.0f,0.0f}, c );
+      m4x3_mulv( gate->to_world, (v3f){-1.0f, 1.0f,0.0f}, d );
+
+      vg_line( a,b, 0xffffa000 );
+      vg_line( b,c, 0xffffa000 );
+      vg_line( c,d, 0xffffa000 );
+      vg_line( d,a, 0xffffa000 );
+
+      vg_line2( gate->co[0], gate->co[1], 0xff0000ff, 0x00000000 );
+   }
+
+   /* update gate camera */
+   gate_camera.fov = cam->fov;
+   gate_camera.nearz = 0.1f;
+   gate_camera.farz  = 2000.0f;
+
+   m4x3_mul( gate->transport, cam->transform, gate_camera.transform );
+   camera_update_view( &gate_camera );
+   camera_update_projection( &gate_camera );
+
+   /* Add special clipping plane to projection */
+   v4f surface;
+   q_mulv( gate->q[1], (v3f){0.0f,0.0f,-1.0f}, surface );
+   surface[3] = v3_dot( surface, gate->co[1] );
+   
+   m4x3_mulp( gate_camera.transform_inverse, surface, surface );
+   surface[3] = -fabsf(surface[3]);
+
+   if( dist < -0.5f )
+      m4x4_clip_projection( gate_camera.mtx.p, surface );
+
+   /* Ready to draw with new camrea */
+   camera_finalize( &gate_camera );
+
+   vg_line_pt3( gate_camera.transform[3], 0.3f, 0xff00ff00 );
+   {
+      shader_model_gate_use();
+      shader_model_gate_uPv( cam->mtx.pv );
+      shader_model_gate_uMdl( gate->to_world );
+      shader_model_gate_uCam( cam->pos );
+      shader_model_gate_uColour( (v4f){0.0f,1.0f,0.0f,0.0f} );
+      shader_model_gate_uTime( vg.time*0.25f );
+      shader_model_gate_uInvRes( (v2f){
+            1.0f / (float)vg.window_x,
+            1.0f / (float)vg.window_y });
+
+      glEnable( GL_STENCIL_TEST );
+      glStencilOp( GL_KEEP, GL_KEEP, GL_REPLACE );  
+      glStencilFunc( GL_ALWAYS, 1, 0xFF ); 
+      glStencilMask( 0xFF );
+
+      mesh_bind( &world_gates.mesh );
+      mdl_draw_submesh( &world_gates.sm_surface );
+
+      glClear( GL_DEPTH_BUFFER_BIT );
+      glStencilFunc( GL_EQUAL, 1, 0xFF );
+      glStencilMask( 0x00 ); 
+   }
+
+   render_world( world_inside, &gate_camera, layer_depth );
+
+   {
+      glDisable( GL_STENCIL_TEST );
+
+      render_water_texture( world_inside, &gate_camera, layer_depth );
+      render_fb_bind( gpipeline.fb_main, 1 );
+
+      glEnable( GL_STENCIL_TEST );
+
+      render_water_surface( world_inside, &gate_camera );
+
+      glStencilMask( 0xFF );
+      glStencilFunc( GL_ALWAYS, 1, 0xFF );
+      glDisable( GL_STENCIL_TEST );
+   }
+
+   return 1;
+}
+
+/*
+ * Intersect the plane of a gate with a line segment, plane coordinate result 
+ * stored in 'where'
+ */
+VG_STATIC int gate_intersect_plane( ent_gate *gate, 
+                                    v3f pos, v3f last, v2f where )
+{
+   v4f surface;
+   q_mulv( gate->q[0], (v3f){0.0f,0.0f,-1.0f}, surface );
+   surface[3] = v3_dot( surface, gate->co[0] );
+
+   v3f v0, c, delta, p0;
+   v3_sub( pos, last, v0 );
+   float l = v3_length( v0 );
+
+   if( l == 0.0f )
+      return 0;
+
+   v3_divs( v0, l, v0 );
+
+   v3_muls( surface, surface[3], c );
+   v3_sub( c, last, delta );
+
+   float d = v3_dot( surface, v0 );
+
+   if( d > 0.00001f ){
+      float t = v3_dot(delta, surface) / d;
+      if( t >= 0.0f && t <= l ){
+         v3f local, rel;
+         v3_muladds( last, v0, t, local );
+         v3_sub( gate->co[0], local, rel );
+
+         where[0] = v3_dot( rel, gate->to_world[0] );
+         where[1] = v3_dot( rel, gate->to_world[1] );
+
+         where[0] /= v3_dot( gate->to_world[0], gate->to_world[0] );
+         where[1] /= v3_dot( gate->to_world[1], gate->to_world[1] );
+
+         return 1;
+      }
+   }
+
+   return 0;
+}
+
+/*
+ * Intersect specific gate
+ */
+VG_STATIC int gate_intersect( ent_gate *gate, v3f pos, v3f last )
+{
+   v2f xy;
+
+   if( gate_intersect_plane( gate, pos, last, xy ) ){
+      if( fabsf(xy[0]) <= 1.0f && fabsf(xy[1]) <= 1.0f ){
+         return 1;
+      }
+   }
+
+   return 0;
+}
+
+/* 
+ * Intersect all gates in the world
+ */
+VG_STATIC ent_gate *world_intersect_gates( world_instance *world,
+                                           v3f pos, v3f last )
+{
+   for( u32 i=0; i<mdl_arrcount(&world->ent_gate); i++ ){
+      ent_gate *gate = mdl_arritm( &world->ent_gate, i );
+      if( gate->type == k_gate_type_unlinked ||
+          gate->type == k_gate_type_nonlocal_unlinked )
+         continue;
+
+      if( gate->type == k_gate_type_nonlocel ){
+         if( skaterift.async_op == k_async_op_world_loading ||
+             skaterift.async_op == k_async_op_world_preloading ){
+            continue;
+         }
+      }
+
+      if( gate_intersect( gate, pos, last ) ){
+         return gate;
+      }
+   }
+
+   return NULL;
+}
+
+/* 
+ * detatches any nonlocal gates 
+ */
+VG_STATIC void world_unlink_nonlocal( world_instance *world )
+{
+   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_nonlocel ){
+         gate->type = k_gate_type_nonlocal_unlinked;
+      }
+   }
+}
+
+/* 
+ * attatches nonlocal gates, to be called from main thread ONLY! 
+ */
+VG_STATIC void world_link_nonlocal_async( void *payload, u32 size )
+{
+   world_instance *world = payload;
+   u32 world_id = world - world_static.worlds;
+
+   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_nonlocal_unlinked ){
+         const char *key = mdl_pstr( &world->meta, gate->key );
+         vg_info( "key: %s\n", key );
+
+         for( u32 i=0; i<vg_list_size(world_static.worlds); i++ ){
+            world_instance *other = &world_static.worlds[i];
+            if( other == world ) continue;
+            if( other->status != k_world_status_loaded ) continue;
+            vg_info( "Checking world %u for key matches\n", i );
+
+            for( u32 j=0; j<mdl_arrcount( &other->ent_gate ); j++ ){
+               ent_gate *gate2 = mdl_arritm( &other->ent_gate, j );
+               if( gate2->type != k_gate_type_nonlocal_unlinked ) continue;
+
+               const char *key2 = mdl_pstr( &other->meta, gate2->key );
+               vg_info( " key2: %s\n", key2 );
+
+               if( strcmp( key, key2 ) ) continue;
+
+               vg_success( "Non-local matching pair '%s' found. (%u:%u)\n",
+                            key, world_id, i );
+                  
+               gate->type = k_gate_type_nonlocel;
+               gate2->type = k_gate_type_nonlocel;
+               gate->target = i;
+               gate2->target = world_id;
+
+               v3_copy( gate->co[0], gate2->co[1] );
+               v3_copy( gate2->co[0], gate->co[1] );
+               v4_copy( gate->q[0], gate2->q[1] );
+               v4_copy( gate2->q[0], gate->q[1] );
+
+               v4f qflip;
+               q_axis_angle( qflip, (v3f){0.0f,1.0f,0.0f}, VG_PIf );
+               q_mul( gate->q[0], qflip, gate->q[0] );
+               q_mul( gate->q[1], qflip, gate->q[1] );
+
+               gate_transform_update( gate );
+               gate_transform_update( gate2 );
+
+               goto matched;
+            }
+         }
+matched:;
+      }
+   }
+}
+
+#endif /* WORLD_GATE_C */
index b409c51d0d482f65ced1193aa8108a88ea8ecaf3..4defb75ac198ee07d5fe398d75af61a6f6292f4e 100644 (file)
 /*
- * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
  */
 
 #ifndef WORLD_GATE_H
 #define WORLD_GATE_H
 
-#include "skaterift.h"
-#include "common.h"
-#include "model.h"
-#include "entity.h"
-#include "render.h"
-#include "camera.h"
-
+#include "world.h"
 #include "shaders/model_gate.h"
-#include "world_water.h"
-
-VG_STATIC void gate_transform_update( ent_gate *gate )
-{
-   m4x3f to_local, recv_to_world;
-
-   q_m3x3( gate->q[0], gate->to_world );
-   v3_copy( gate->co[0], gate->to_world[3] );
-   
-   m4x3_invert_affine( gate->to_world, to_local );
-
-   q_m3x3( gate->q[1], recv_to_world );
-   v3_copy( gate->co[1], recv_to_world[3] );
-   m4x3_mul( recv_to_world, to_local, gate->transport );
 
-   m3x3_scale( gate->to_world, (v3f){ gate->dimensions[0], 
-                                      gate->dimensions[1], 1.0f } );
-}
-
-VG_STATIC void world_gates_init(void)
-{
-   vg_info( "world_gates_init\n" );
-
-   shader_model_gate_register();
-
-   vg_linear_clear( vg_mem.scratch );
-
-   mdl_context mgate;
-   mdl_open( &mgate, "models/rs_gate.mdl", vg_mem.scratch );
-   mdl_load_metadata_block( &mgate, vg_mem.scratch );
-
-   mdl_mesh *surface = mdl_find_mesh( &mgate, "rs_gate" );
-   mdl_submesh *sm = mdl_arritm(&mgate.submeshs,surface->submesh_start);
-   world_global.sm_gate_surface = *sm;
-
-   const char *names[] = { "rs_gate_marker", "rs_gate_marker.001", 
-                           "rs_gate_marker.002", "rs_gate_marker.003" };
-
-   for( int i=0; i<4; i++ ){
-      mdl_mesh *marker = mdl_find_mesh( &mgate, names[i] );
-      sm = mdl_arritm( &mgate.submeshs, marker->submesh_start );
-      world_global.sm_gate_marker[i] = *sm;
-   }
-
-   mdl_async_load_glmesh( &mgate, &world_global.mesh_gate );
-   mdl_close( &mgate );
+struct world_gates{
+   glmesh mesh;
+   mdl_submesh sm_surface, sm_marker[4];
 }
+static world_gates;
 
+VG_STATIC void world_gates_init(void);
+VG_STATIC void gate_transform_update( ent_gate *gate );
+VG_STATIC void world_link_nonlocal_async( void *payload, u32 size );
+VG_STATIC void world_unlink_nonlocal( world_instance *world );
 VG_STATIC int render_gate( world_instance *world_inside,
-                           ent_gate *gate, camera *cam, int layer_depth )
-{
-   v3f viewdir, gatedir;
-   m3x3_mulv( cam->transform, (v3f){0.0f,0.0f,-1.0f}, viewdir );
-   q_mulv( gate->q[0], (v3f){0.0f,0.0f,-1.0f}, gatedir );
-
-   v3f v0;
-   v3_sub( cam->pos, gate->co[0], v0 );
-
-   float dist = v3_dot(v0, gatedir);
-
-   /* Hard cutoff */
-   if( dist > 3.0f )
-      return 0;
-
-   if( v3_dist( cam->pos, gate->co[0] ) > 100.0f )
-      return 0;
-
-   {
-      v3f a,b,c,d;
-
-      m4x3_mulv( gate->to_world, (v3f){-1.0f,-1.0f,0.0f}, a );
-      m4x3_mulv( gate->to_world, (v3f){ 1.0f,-1.0f,0.0f}, b );
-      m4x3_mulv( gate->to_world, (v3f){ 1.0f, 1.0f,0.0f}, c );
-      m4x3_mulv( gate->to_world, (v3f){-1.0f, 1.0f,0.0f}, d );
-
-      vg_line( a,b, 0xffffa000 );
-      vg_line( b,c, 0xffffa000 );
-      vg_line( c,d, 0xffffa000 );
-      vg_line( d,a, 0xffffa000 );
-
-      vg_line2( gate->co[0], gate->co[1], 0xff0000ff, 0x00000000 );
-   }
-
-   /* update gate camera */
-   gate_camera.fov = cam->fov;
-   gate_camera.nearz = 0.1f;
-   gate_camera.farz  = 2000.0f;
-
-   m4x3_mul( gate->transport, cam->transform, gate_camera.transform );
-   camera_update_view( &gate_camera );
-   camera_update_projection( &gate_camera );
-
-   /* Add special clipping plane to projection */
-   v4f surface;
-   q_mulv( gate->q[1], (v3f){0.0f,0.0f,-1.0f}, surface );
-   surface[3] = v3_dot( surface, gate->co[1] );
-   
-   m4x3_mulp( gate_camera.transform_inverse, surface, surface );
-   surface[3] = -fabsf(surface[3]);
-
-   if( dist < -0.5f )
-      m4x4_clip_projection( gate_camera.mtx.p, surface );
-
-   /* Ready to draw with new camrea */
-   camera_finalize( &gate_camera );
-
-   vg_line_pt3( gate_camera.transform[3], 0.3f, 0xff00ff00 );
-   {
-      shader_model_gate_use();
-      shader_model_gate_uPv( cam->mtx.pv );
-      shader_model_gate_uMdl( gate->to_world );
-      shader_model_gate_uCam( cam->pos );
-      shader_model_gate_uColour( (v4f){0.0f,1.0f,0.0f,0.0f} );
-      shader_model_gate_uTime( vg.time*0.25f );
-      shader_model_gate_uInvRes( (v2f){
-            1.0f / (float)vg.window_x,
-            1.0f / (float)vg.window_y });
-
-      glEnable( GL_STENCIL_TEST );
-      glStencilOp( GL_KEEP, GL_KEEP, GL_REPLACE );  
-      glStencilFunc( GL_ALWAYS, 1, 0xFF ); 
-      glStencilMask( 0xFF );
-
-      mesh_bind( &world_global.mesh_gate );
-      mdl_draw_submesh( &world_global.sm_gate_surface );
+                           ent_gate *gate, camera *cam, int layer_depth );
 
-      glClear( GL_DEPTH_BUFFER_BIT );
-      glStencilFunc( GL_EQUAL, 1, 0xFF );
-      glStencilMask( 0x00 ); 
-   }
-
-   render_world( world_inside, &gate_camera, layer_depth );
-
-   {
-      glDisable( GL_STENCIL_TEST );
-
-      render_water_texture( world_inside, &gate_camera, layer_depth );
-      render_fb_bind( gpipeline.fb_main, 1 );
-
-      glEnable( GL_STENCIL_TEST );
-
-      render_water_surface( world_inside, &gate_camera );
-
-      glStencilMask( 0xFF );
-      glStencilFunc( GL_ALWAYS, 1, 0xFF );
-      glDisable( GL_STENCIL_TEST );
-   }
-
-   return 1;
-}
-
-VG_STATIC int gate_intersect_plane( ent_gate *gate, 
-                                    v3f pos, v3f last, v2f where )
-{
-   v4f surface;
-   q_mulv( gate->q[0], (v3f){0.0f,0.0f,-1.0f}, surface );
-   surface[3] = v3_dot( surface, gate->co[0] );
-
-   v3f v0, c, delta, p0;
-   v3_sub( pos, last, v0 );
-   float l = v3_length( v0 );
-
-   if( l == 0.0f )
-      return 0;
-
-   v3_divs( v0, l, v0 );
-
-   v3_muls( surface, surface[3], c );
-   v3_sub( c, last, delta );
-
-   float d = v3_dot( surface, v0 );
-
-   if( d > 0.00001f ){
-      float t = v3_dot(delta, surface) / d;
-      if( t >= 0.0f && t <= l ){
-         v3f local, rel;
-         v3_muladds( last, v0, t, local );
-         v3_sub( gate->co[0], local, rel );
-
-         where[0] = v3_dot( rel, gate->to_world[0] );
-         where[1] = v3_dot( rel, gate->to_world[1] );
-
-         where[0] /= v3_dot( gate->to_world[0], gate->to_world[0] );
-         where[1] /= v3_dot( gate->to_world[1], gate->to_world[1] );
-
-         return 1;
-      }
-   }
-
-   return 0;
-}
-
-VG_STATIC int gate_intersect( ent_gate *gate, v3f pos, v3f last )
-{
-   v2f xy;
-
-   if( gate_intersect_plane( gate, pos, last, xy ) ){
-      if( fabsf(xy[0]) <= 1.0f && fabsf(xy[1]) <= 1.0f ){
-         return 1;
-      }
-   }
-
-   return 0;
-}
-
-/* 
- * Intersect all gates in the world
- */
+VG_STATIC int gate_intersect( ent_gate *gate, v3f pos, v3f last );
 VG_STATIC ent_gate *world_intersect_gates( world_instance *world,
-                                           v3f pos, v3f last )
-{
-   for( u32 i=0; i<mdl_arrcount(&world->ent_gate); i++ ){
-      ent_gate *gate = mdl_arritm( &world->ent_gate, i );
-      if( gate->type == k_gate_type_unlinked ||
-          gate->type == k_gate_type_nonlocal_unlinked )
-         continue;
+                                           v3f pos, v3f last );
 
-      if( gate->type == k_gate_type_nonlocel ){
-         if( skaterift.async_op == k_async_op_world_loading ||
-             skaterift.async_op == k_async_op_world_preloading ){
-            continue;
-         }
-      }
-
-      if( gate_intersect( gate, pos, last ) ){
-         return gate;
-      }
-   }
-
-   return NULL;
-}
 
 #endif /* WORLD_GATE_H */
diff --git a/world_gen.c b/world_gen.c
new file mode 100644 (file)
index 0000000..799059a
--- /dev/null
@@ -0,0 +1,628 @@
+/*
+ * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ *
+ * World generation/population. Different to regular loading, since it needs to
+ * create geometry, apply procedural stuff and save that image to files etc.
+ */
+
+#ifndef WORLD_GEN_C
+#define WORLD_GEN_C
+
+#include "world.h"
+#include "world_gen.h"
+#include "world_load.h"
+#include "world_volumes.h"
+#include "world_gate.h"
+
+/*
+ * Add all triangles from the model, which match the material ID
+ * applies affine transform to the model
+ */
+VG_STATIC void world_add_all_if_material( m4x3f transform, scene_context *scene, 
+                                          mdl_context *mdl, u32 id )
+{
+   for( u32 i=0; i<mdl_arrcount(&mdl->meshs); i++ ){
+      mdl_mesh *mesh = mdl_arritm( &mdl->meshs, i );
+
+      for( u32 j=0; j<mesh->submesh_count; j++ ){
+         mdl_submesh *sm = mdl_arritm( &mdl->submeshs, mesh->submesh_start+j );
+         if( sm->material_id == id ){
+            m4x3f transform2;
+            mdl_transform_m4x3( &mesh->transform, transform2 );
+            m4x3_mul( transform, transform2, transform2 );
+
+            scene_add_mdl_submesh( scene, mdl, sm, transform2 );
+         }
+      }
+   }
+}
+
+/*
+ * Adds a small blob shape to the world at a raycast location. This is for the
+ * grass sprites
+ *
+ *   /''''\
+ *  /      \
+ * |        |
+ * |________|
+ */
+VG_STATIC void world_gen_add_blob( scene_context *scene, ray_hit *hit )
+{
+   world_instance *world = world_loading_instance();
+   m4x3f transform;
+   v4f qsurface, qrandom;
+   v3f axis;
+
+   v3_cross( (v3f){0.0f,1.0f,0.0f}, hit->normal, axis );
+
+   float angle = v3_dot(hit->normal,(v3f){0.0f,1.0f,0.0f});
+   q_axis_angle( qsurface, axis, angle );
+   q_axis_angle( qrandom, (v3f){0.0f,1.0f,0.0f}, vg_randf64()*VG_TAUf );
+   q_mul( qsurface, qrandom, qsurface );
+   q_m3x3( qsurface, transform );
+   v3_copy( hit->pos, transform[3] );
+
+   scene_vert verts[] = 
+   {
+      { .co = { -1.00f, 0.0f, 0.0f } },
+      { .co = {  1.00f, 0.0f, 0.0f } },
+      { .co = { -1.00f, 1.2f, 0.0f } },
+      { .co = {  1.00f, 1.2f, 0.0f } },
+      { .co = { -0.25f, 2.0f, 0.0f } },
+      { .co = {  0.25f, 2.0f, 0.0f } }
+   };
+
+   const u32 indices[] = { 0,1,3, 0,3,2, 2,3,5, 2,5,4 };
+
+   if( scene->vertex_count + vg_list_size(verts) > scene->max_vertices )
+      vg_fatal_error( "Scene vertex buffer overflow" );
+
+   if( scene->indice_count + vg_list_size(indices) > scene->max_indices )
+      vg_fatal_error( "Scene index buffer overflow" );
+
+   scene_vert *dst_verts = &scene->arrvertices[ scene->vertex_count ];
+   u32 *dst_indices      = &scene->arrindices [ scene->indice_count ];
+
+   scene_vert *ref       = &world->scene_geo.arrvertices[ hit->tri[0] ];
+
+   for( u32 i=0; i<vg_list_size(verts); i++ )
+   {
+      scene_vert *pvert = &dst_verts[ i ],
+                 *src   = &verts[ i ];
+
+      m4x3_mulv( transform, src->co, pvert->co );
+      scene_vert_pack_norm( pvert, transform[1] );
+
+      v2_copy( ref->uv, pvert->uv );
+   }
+
+   for( u32 i=0; i<vg_list_size(indices); i++ )
+      dst_indices[i] = indices[i] + scene->vertex_count;
+
+   scene->vertex_count += vg_list_size(verts);
+   scene->indice_count += vg_list_size(indices);
+}
+
+/* 
+ * Sprinkle foliage models over the map on terrain material 
+ */
+VG_STATIC void world_apply_procedural_foliage( scene_context *scene,
+                                               struct world_surface *mat )
+{
+   if( vg.quality_profile == k_quality_profile_low )
+      return;
+
+   world_instance *world = world_loading_instance();
+   vg_info( "Applying foliage (%u)\n", mat->info.pstr_name );
+
+   v3f volume;
+   v3_sub( world->scene_geo.bbx[1], world->scene_geo.bbx[0], volume );
+   volume[1] = 1.0f;
+
+   int count = 0;
+
+   float area = volume[0]*volume[2];
+   u32 particles = 0.08f * area;
+
+   /* TODO: Quasirandom? */
+   vg_info( "Map area: %f. Max particles: %u\n", area, particles );
+
+   for( u32 i=0; i<particles; i++ ){
+      v3f pos;
+      v3_mul( volume, (v3f){ vg_randf64(), 1000.0f, vg_randf64() }, pos );
+      pos[1] = 1000.0f;
+      v3_add( pos, world->scene_geo.bbx[0], pos );
+      
+      ray_hit hit;
+      hit.dist = INFINITY;
+
+      if( ray_world( world, pos, (v3f){0.0f,-1.0f,0.0f}, &hit )){
+         struct world_surface *m1 = ray_hit_surface( world, &hit );
+         if((hit.normal[1] > 0.8f) && (m1 == mat) && (hit.pos[1] > 0.0f+10.0f)){
+            world_gen_add_blob( scene, &hit );
+            count ++;
+         }
+      }
+   }
+
+   vg_info( "%d foliage models added\n", count );
+}
+
+/*
+ * Create the main meshes for the world
+ */
+VG_STATIC void world_gen_generate_meshes(void)
+{
+   /* 
+    * Compile meshes into the world scenes
+    */
+   world_instance *world = world_loading_instance();
+   scene_init( &world->scene_geo, 320000, 1200000 );
+   u32 buf_size = scene_mem_required( &world->scene_geo );
+   u8 *buffer = vg_linear_alloc( world->heap, buf_size );
+   scene_supply_buffer( &world->scene_geo, buffer );
+
+   m4x3f midentity;
+   m4x3_identity( midentity );
+
+   /*
+    * Generate scene: collidable geometry
+    * ----------------------------------------------------------------
+    */
+
+   vg_info( "Generating collidable geometry\n" );
+   
+   for( u32 i=0; i<world->surface_count; i++ ){
+      struct world_surface *surf = &world->surfaces[ i ];
+
+      if( surf->info.flags & k_material_flag_collision )
+         world_add_all_if_material( midentity, &world->scene_geo, 
+                                    &world->meta, i );
+
+      scene_copy_slice( &world->scene_geo, &surf->sm_geo );
+   }
+
+   /* compress that bad boy */
+   u32 new_vert_max = world->scene_geo.vertex_count,
+       new_vert_size = vg_align8(new_vert_max*sizeof(scene_vert)),
+       new_indice_len = world->scene_geo.indice_count*sizeof(u32);
+
+   u32 *src_indices = world->scene_geo.arrindices,
+       *dst_indices = (u32 *)(buffer + new_vert_size);
+
+   memmove( dst_indices, src_indices, new_indice_len );
+
+   world->scene_geo.max_indices = world->scene_geo.indice_count;
+   world->scene_geo.max_vertices = world->scene_geo.vertex_count;
+   buf_size = scene_mem_required( &world->scene_geo );
+
+   buffer = vg_linear_resize( world->heap, buffer, buf_size );
+
+   world->scene_geo.arrvertices = (scene_vert *)(buffer);
+   world->scene_geo.arrindices = (u32 *)(buffer + new_vert_size);
+
+   scene_upload_async( &world->scene_geo, &world->mesh_geo );
+
+   /* need send off the memory to the gpu before we can create the bvh. */
+   vg_async_stall();
+   vg_info( "creating bvh\n" );
+
+   /* setup spacial mapping and rigidbody */
+   world->geo_bh = scene_bh_create( world->heap, &world->scene_geo );
+
+   v3_zero( world->rb_geo.rb.co );
+   v3_zero( world->rb_geo.rb.v );
+   q_identity( world->rb_geo.rb.q );
+   v3_zero( world->rb_geo.rb.w );
+
+   world->rb_geo.type = k_rb_shape_scene;
+   world->rb_geo.inf.scene.bh_scene = world->geo_bh;
+   rb_init_object( &world->rb_geo );
+
+   /*
+    * Generate scene: non-collidable geometry
+    * ----------------------------------------------------------------
+    */
+   vg_info( "Generating non-collidable geometry\n" );
+
+   vg_async_item *call = scene_alloc_async( &world->scene_no_collide,
+                                            &world->mesh_no_collide,
+                                            200000, 500000 );
+
+   for( u32 i=0; i<world->surface_count; i++ ){
+      struct world_surface *surf = &world->surfaces[ i ];
+
+      if( !(surf->info.flags & k_material_flag_collision) ){
+         world_add_all_if_material( midentity, 
+                                    &world->scene_no_collide, &world->meta, i );
+      }
+
+      if( surf->info.flags & k_material_flag_grow_grass )
+         world_apply_procedural_foliage( &world->scene_no_collide, surf );
+
+      scene_copy_slice( &world->scene_no_collide, &surf->sm_no_collide );
+   }
+
+   for( u32 i=0; i<mdl_arrcount( &world->ent_traffic ); i++ ){
+      ent_traffic *vehc = mdl_arritm( &world->ent_traffic, i );
+
+      for( u32 j=0; j<vehc->submesh_count; j++ ){
+         mdl_submesh *sm = mdl_arritm( &world->meta.submeshs, 
+                                       vehc->submesh_start+j );
+
+         if( sm->flags & k_submesh_flag_consumed ){
+            continue;
+         }
+
+         m4x3f identity;
+         m4x3_identity( identity );
+         scene_add_mdl_submesh( &world->scene_no_collide, 
+                                &world->meta, sm, identity );
+
+         scene_copy_slice( &world->scene_no_collide, sm );
+         sm->flags |= k_submesh_flag_consumed;
+      }
+   }
+
+   vg_async_dispatch( call, async_scene_upload );
+}
+
+/* signed distance function for cone */
+static f32 fsd_cone_infinite( v3f p, v2f c )
+{
+   v2f q = { v2_length( (v2f){ p[0], p[2] } ), -p[1] };
+   float s = vg_maxf( 0.0f, v2_dot( q, c ) );
+
+   v2f v0;
+   v2_muls( c, s, v0 );
+   v2_sub( q, v0, v0 );
+
+   float d = v2_length( v0 );
+   return d * ((q[0]*c[1]-q[1]*c[0]<0.0f)?-1.0f:1.0f);
+}
+
+struct light_indices_upload_info{
+   world_instance *world;
+   v3i count;
+
+   void *data;
+};
+
+/*
+ * Async reciever to buffer light index data 
+ */
+VG_STATIC void async_upload_light_indices( void *payload, u32 size )
+{
+   struct light_indices_upload_info *info = payload;
+
+   glGenTextures( 1, &info->world->tex_light_cubes );
+   glBindTexture( GL_TEXTURE_3D, info->world->tex_light_cubes );
+   glTexImage3D( GL_TEXTURE_3D, 0, GL_RG32UI,
+                 info->count[0], info->count[1], info->count[2],
+                 0, GL_RG_INTEGER, GL_UNSIGNED_INT, info->data );
+   glTexParameteri( GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
+   glTexParameteri( GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
+}
+
+/*
+ * Computes light indices for world
+ */
+VG_STATIC void world_gen_compute_light_indices(void)
+{
+   /* light cubes */
+   world_instance *world = world_loading_instance();
+   v3f cubes_min, cubes_max;
+   v3_muls( world->scene_geo.bbx[0], 1.0f/k_world_light_cube_size, cubes_min );
+   v3_muls( world->scene_geo.bbx[1], 1.0f/k_world_light_cube_size, cubes_max );
+
+   v3_sub( cubes_min, (v3f){ 0.5f, 0.5f, 0.5f }, cubes_min );
+   v3_add( cubes_max, (v3f){ 0.5f, 0.5f, 0.5f }, cubes_max );
+
+   v3_floor( cubes_min, cubes_min );
+   v3_floor( cubes_max, cubes_max );
+
+   v3i icubes_min, icubes_max;
+
+   for( int i=0; i<3; i++ ){
+      icubes_min[i] = cubes_min[i];
+      icubes_max[i] = cubes_max[i];
+   }
+
+   v3f cube_size;
+
+   v3i icubes_count;
+   v3i_sub( icubes_max, icubes_min, icubes_count );
+   
+   for( int i=0; i<3; i++ ){
+      int clamped_count = VG_MIN( 128, icubes_count[i]+1 );
+      float clamped_max = icubes_min[i] + clamped_count,
+                    max = icubes_min[i] + icubes_count[i]+1;
+
+      icubes_count[i] = clamped_count;
+      cube_size[i] = (max / clamped_max) * k_world_light_cube_size;
+      cubes_max[i] = clamped_max;
+   }
+
+   v3_mul( cubes_min, cube_size, cubes_min );
+   v3_mul( cubes_max, cube_size, cubes_max );
+
+   for( int i=0; i<3; i++ ){
+      float range = cubes_max[i]-cubes_min[i];
+      world->ub_lighting.g_cube_inv_range[i] = 1.0f / range;
+      world->ub_lighting.g_cube_inv_range[i] *= (float)icubes_count[i];
+
+      vg_info( "cubes[%d]: %d\n", i, icubes_count[i] );
+   }
+
+   int total_cubes = icubes_count[0]*icubes_count[1]*icubes_count[2];
+
+   u32 data_size = vg_align8(total_cubes*sizeof(u32)*2),
+       hdr_size  = vg_align8(sizeof(struct light_indices_upload_info));
+
+   vg_async_item *call = vg_async_alloc( data_size + hdr_size );
+   struct light_indices_upload_info *info = call->payload;
+   info->data = ((u8*)call->payload) + hdr_size;
+   info->world = world;
+   u32 *cubes_index = info->data;
+
+   for( int i=0; i<3; i++ )
+      info->count[i] = icubes_count[i];
+                                       
+   vg_info( "Computing light cubes (%d) [%f %f %f] -> [%f %f %f]\n", 
+             total_cubes, cubes_min[0], -cubes_min[2], cubes_min[1],
+                          cubes_max[0], -cubes_max[2], cubes_max[1] );
+   v3_copy( cubes_min, world->ub_lighting.g_cube_min );
+
+   float bound_radius = v3_length( cube_size );
+
+   for( int iz = 0; iz<icubes_count[2]; iz ++ ){
+      for( int iy = 0; iy<icubes_count[1]; iy++ ){
+         for( int ix = 0; ix<icubes_count[0]; ix++ ){
+            boxf bbx;
+            v3_div( (v3f){ ix, iy, iz }, world->ub_lighting.g_cube_inv_range, 
+                  bbx[0] );
+            v3_div( (v3f){ ix+1, iy+1, iz+1 }, 
+                  world->ub_lighting.g_cube_inv_range, 
+                  bbx[1] );
+
+            v3_add( bbx[0], world->ub_lighting.g_cube_min, bbx[0] );
+            v3_add( bbx[1], world->ub_lighting.g_cube_min, bbx[1] );
+
+            v3f center;
+            v3_add( bbx[0], bbx[1], center );
+            v3_muls( center, 0.5f, center );
+            
+            u32 indices[6] = { 0, 0, 0, 0, 0, 0 };
+            u32 count = 0;
+
+            float influences[6] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
+            const int N = vg_list_size( influences );
+
+            for( u32 j=0; j<mdl_arrcount(&world->ent_light); j ++ ){
+               ent_light *light = mdl_arritm( &world->ent_light, j );
+               v3f closest;
+               closest_point_aabb( light->transform.co, bbx, closest );
+
+               float dist = v3_dist( closest, light->transform.co ),
+                     influence = 1.0f/(dist+1.0f);
+
+               if( dist > light->range )
+                  continue;
+
+               if( light->type == k_light_type_spot){
+                  v3f local;
+                  m4x3_mulv( light->inverse_world, center, local );
+
+                  float r = fsd_cone_infinite( local, light->angle_sin_cos );
+
+                  if( r > bound_radius )
+                     continue;
+               }
+
+               int best_pos = N;
+               for( int k=best_pos-1; k>=0; k -- )
+                  if( influence > influences[k] )
+                     best_pos = k;
+
+               if( best_pos < N ){
+                  for( int k=N-1; k>best_pos; k -- ){
+                     influences[k] = influences[k-1];
+                     indices[k] = indices[k-1];
+                  }
+
+                  influences[best_pos] = influence;
+                  indices[best_pos] = j;
+               }
+            }
+
+            for( int j=0; j<N; j++ )
+               if( influences[j] > 0.0f )
+                  count ++;
+
+            int base_index = iz * (icubes_count[0]*icubes_count[1]) +
+                             iy * (icubes_count[0]) +
+                             ix;
+
+            int lower_count = VG_MIN( 3, count );
+            u32 packed_index_lower = lower_count;
+            packed_index_lower |= indices[0]<<2;
+            packed_index_lower |= indices[1]<<12;
+            packed_index_lower |= indices[2]<<22;
+
+            int upper_count = VG_MAX( 0, count - lower_count );
+            u32 packed_index_upper = upper_count;
+            packed_index_upper |= indices[3]<<2;
+            packed_index_upper |= indices[4]<<12;
+            packed_index_upper |= indices[5]<<22;
+
+            cubes_index[ base_index*2 + 0 ] = packed_index_lower;
+            cubes_index[ base_index*2 + 1 ] = packed_index_upper;
+         }
+      }
+   }
+
+   vg_async_dispatch( call, async_upload_light_indices );
+}
+
+/*
+ * Rendering pass needed to complete the world
+ */
+VG_STATIC void async_world_postprocess_render( void *payload, u32 _size )
+{
+   /* create scene lighting buffer */
+   world_instance *world = world_loading_instance();
+
+   u32 size = VG_MAX(mdl_arrcount(&world->ent_light),1) * sizeof(float)*12;
+   vg_info( "Upload %ubytes (lighting)\n", size );
+
+   glGenBuffers( 1, &world->tbo_light_entities );
+   glBindBuffer( GL_TEXTURE_BUFFER, world->tbo_light_entities );
+   glBufferData( GL_TEXTURE_BUFFER, size, NULL, GL_DYNAMIC_DRAW );
+   
+   /* buffer layout
+    *  
+    *  colour               position                direction (spots)
+    * | .   .   .   .     | .   .   .   .         | .   .   .   .  |
+    * | Re  Ge  Be  Night | Xco Yco Zco Range     | Dx  Dy  Dz  Da |
+    *
+    */
+
+   v4f *light_dst = glMapBuffer( GL_TEXTURE_BUFFER, GL_WRITE_ONLY );
+   for( u32 i=0; i<mdl_arrcount(&world->ent_light); i++ ){
+      ent_light *light = mdl_arritm( &world->ent_light, i );
+
+      /* colour  + night */
+      v3_muls( light->colour, light->colour[3] * 2.0f, light_dst[i*3+0] );
+      light_dst[i*3+0][3] = 2.0f;
+
+      if( !light->daytime ){
+         u32 hash = (i * 29986577u) & 0xffu;
+         float switch_on = hash;
+               switch_on *= (1.0f/255.0f);
+
+         light_dst[i*3+0][3] = 0.44f + switch_on * 0.015f;
+      }
+      
+      /* position + 1/range^2 */
+      v3_copy( light->transform.co, light_dst[i*3+1] );
+      light_dst[i*3+1][3] = 1.0f/(light->range*light->range);
+
+      /* direction + angle */
+      q_mulv( light->transform.q, (v3f){0.0f,-1.0f,0.0f}, light_dst[i*3+2]);
+      light_dst[i*3+2][3] = cosf( light->angle );
+   }
+
+   glUnmapBuffer( GL_TEXTURE_BUFFER );
+
+   glGenTextures( 1, &world->tex_light_entities );
+   glBindTexture( GL_TEXTURE_BUFFER, world->tex_light_entities );
+   glTexBuffer( GL_TEXTURE_BUFFER, GL_RGBA32F, world->tbo_light_entities );
+
+   /* Upload lighting uniform buffer */
+   if( world->water.enabled )
+      v4_copy( world->water.plane, world->ub_lighting.g_water_plane );
+
+   v4f info_vec;
+   v3f *bounds = world->scene_geo.bbx;
+
+   info_vec[0] = bounds[0][0];
+   info_vec[1] = bounds[0][2];
+   info_vec[2] = 1.0f/ (bounds[1][0]-bounds[0][0]);
+   info_vec[3] = 1.0f/ (bounds[1][2]-bounds[0][2]);
+   v4_copy( info_vec, world->ub_lighting.g_depth_bounds );
+
+   /* 
+    * Rendering the depth map
+    */
+   camera ortho;
+
+   v3f extent;
+   v3_sub( world->scene_geo.bbx[1], world->scene_geo.bbx[0], extent );
+
+   float fl = world->scene_geo.bbx[0][0],
+         fr = world->scene_geo.bbx[1][0],
+         fb = world->scene_geo.bbx[0][2],
+         ft = world->scene_geo.bbx[1][2],
+         rl = 1.0f / (fr-fl),
+         tb = 1.0f / (ft-fb);
+
+   m4x4_zero( ortho.mtx.p );
+   ortho.mtx.p[0][0] = 2.0f * rl;
+   ortho.mtx.p[2][1] = 2.0f * tb;
+   ortho.mtx.p[3][0] = (fr + fl) * -rl;
+   ortho.mtx.p[3][1] = (ft + fb) * -tb;
+   ortho.mtx.p[3][3] = 1.0f;
+   m4x3_identity( ortho.transform );
+   camera_update_view( &ortho );
+   camera_finalize( &ortho );
+
+   glDisable(GL_DEPTH_TEST);
+   glDisable(GL_BLEND);
+   glDisable(GL_CULL_FACE);
+   render_fb_bind( &world->heightmap, 0 );
+   shader_blitcolour_use();
+   shader_blitcolour_uColour( (v4f){-9999.0f,-9999.0f,-9999.0f,-9999.0f} );
+   render_fsquad();
+
+   glEnable(GL_BLEND);
+   glBlendFunc(GL_ONE, GL_ONE);
+   glBlendEquation(GL_MAX);
+
+   render_world_position( world, &ortho );
+   glDisable(GL_BLEND);
+   glEnable(GL_DEPTH_TEST);
+   glBindFramebuffer( GL_FRAMEBUFFER, 0 );
+
+   /* upload full buffer */
+   glBindBuffer( GL_UNIFORM_BUFFER, world->ubo_lighting );
+   glBufferSubData( GL_UNIFORM_BUFFER, 0, 
+                    sizeof(struct ub_world_lighting), &world->ub_lighting );
+}
+
+/* Loads textures from the pack file */
+VG_STATIC void world_gen_load_surfaces(void)
+{
+   world_instance *world = world_loading_instance();
+   vg_info( "Loading textures\n" );
+   world->texture_count = 0;
+
+   world->texture_count = world->meta.textures.count+1;
+   world->textures = vg_linear_alloc( world->heap,
+                              vg_align8(sizeof(GLuint)*world->texture_count) );
+
+   vg_tex2d_replace_with_error( &world->textures[0] );
+
+   for( u32 i=0; i<mdl_arrcount(&world->meta.textures); i++ ){
+      mdl_texture *tex = mdl_arritm( &world->meta.textures, i );
+
+      if( !tex->file.pack_size ){
+         vg_fatal_error( "World models must have packed textures!" );
+      }
+
+      vg_linear_clear( vg_mem.scratch );
+      void *src_data = vg_linear_alloc( vg_mem.scratch, 
+                                        tex->file.pack_size );
+      mdl_fread_pack_file( &world->meta, &tex->file, src_data );
+
+      vg_tex2d_load_qoi_async( src_data, tex->file.pack_size,
+                               VG_TEX2D_NEAREST|VG_TEX2D_REPEAT,
+                               &world->textures[i+1] );
+   }
+
+   vg_info( "Loading materials\n" );
+
+   world->surface_count = world->meta.materials.count+1;
+   world->surfaces = vg_linear_alloc( world->heap,
+               vg_align8(sizeof(struct world_surface)*world->surface_count) );
+
+   /* error material */
+   struct world_surface *errmat = &world->surfaces[0];
+   memset( errmat, 0, sizeof(struct world_surface) );
+                       
+   for( u32 i=0; i<mdl_arrcount(&world->meta.materials); i++ ){
+      world->surfaces[i+1].info = 
+         *(mdl_material *)mdl_arritm( &world->meta.materials, i );
+   }
+}
+
+#endif /* WORLD_GEN_C */
index b5b06e54b3783c3212e1e5bdce1dfb8de533de7f..90c01e34da09b314bd55f12a1dfa10540a6cea03 100644 (file)
@@ -1,5 +1,8 @@
 /*
- * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ *
+ * World generation/population. Different to regular loading, since it needs to
+ * create geometry, apply procedural stuff and save that image to files etc.
  */
 
 #ifndef WORLD_GEN_H
 
 #include "world.h"
 
-VG_STATIC void world_load( u32 index, const char *path );
-
-VG_STATIC void world_add_all_if_material( m4x3f transform, scene_context *scene, 
-                                          mdl_context *mdl, u32 id )
-{
-   for( u32 i=0; i<mdl_arrcount(&mdl->meshs); i++ ){
-      mdl_mesh *mesh = mdl_arritm( &mdl->meshs, i );
-
-      for( u32 j=0; j<mesh->submesh_count; j++ ){
-         mdl_submesh *sm = mdl_arritm( &mdl->submeshs, mesh->submesh_start+j );
-         if( sm->material_id == id ){
-            m4x3f transform2;
-            mdl_transform_m4x3( &mesh->transform, transform2 );
-            m4x3_mul( transform, transform2, transform2 );
-
-            scene_add_mdl_submesh( scene, mdl, sm, transform2 );
-         }
-      }
-   }
-}
-
-VG_STATIC void world_add_blob( world_instance *world,
-                               scene_context *scene, ray_hit *hit )
-{
-   m4x3f transform;
-   v4f qsurface, qrandom;
-   v3f axis;
-
-   v3_cross( (v3f){0.0f,1.0f,0.0f}, hit->normal, axis );
-
-   float angle = v3_dot(hit->normal,(v3f){0.0f,1.0f,0.0f});
-   q_axis_angle( qsurface, axis, angle );
-   q_axis_angle( qrandom, (v3f){0.0f,1.0f,0.0f}, vg_randf64()*VG_TAUf );
-   q_mul( qsurface, qrandom, qsurface );
-   q_m3x3( qsurface, transform );
-   v3_copy( hit->pos, transform[3] );
-
-   scene_vert verts[] = 
-   {
-      { .co = { -1.00f, 0.0f, 0.0f } },
-      { .co = {  1.00f, 0.0f, 0.0f } },
-      { .co = { -1.00f, 1.2f, 0.0f } },
-      { .co = {  1.00f, 1.2f, 0.0f } },
-      { .co = { -0.25f, 2.0f, 0.0f } },
-      { .co = {  0.25f, 2.0f, 0.0f } }
-   };
-
-   const u32 indices[] = { 0,1,3, 0,3,2, 2,3,5, 2,5,4 };
-
-   if( scene->vertex_count + vg_list_size(verts) > scene->max_vertices )
-      vg_fatal_error( "Scene vertex buffer overflow" );
-
-   if( scene->indice_count + vg_list_size(indices) > scene->max_indices )
-      vg_fatal_error( "Scene index buffer overflow" );
-
-   scene_vert *dst_verts = &scene->arrvertices[ scene->vertex_count ];
-   u32 *dst_indices      = &scene->arrindices [ scene->indice_count ];
-
-   scene_vert *ref       = &world->scene_geo.arrvertices[ hit->tri[0] ];
-
-   for( u32 i=0; i<vg_list_size(verts); i++ )
-   {
-      scene_vert *pvert = &dst_verts[ i ],
-                 *src   = &verts[ i ];
-
-      m4x3_mulv( transform, src->co, pvert->co );
-      scene_vert_pack_norm( pvert, transform[1] );
-
-      v2_copy( ref->uv, pvert->uv );
-   }
-
-   for( u32 i=0; i<vg_list_size(indices); i++ )
-      dst_indices[i] = indices[i] + scene->vertex_count;
-
-   scene->vertex_count += vg_list_size(verts);
-   scene->indice_count += vg_list_size(indices);
-}
-
-/* Sprinkle foliage models over the map on terrain material */
-VG_STATIC void world_apply_procedural_foliage( world_instance *world,
-                                               scene_context *scene,
-                                               struct world_surface *mat )
-{
-   if( vg.quality_profile == k_quality_profile_low )
-      return;
-
-   vg_info( "Applying foliage (%u)\n", mat->info.pstr_name );
-
-   v3f volume;
-   v3_sub( world->scene_geo.bbx[1], world->scene_geo.bbx[0], volume );
-   volume[1] = 1.0f;
-
-   int count = 0;
-
-   float area = volume[0]*volume[2];
-   u32 particles = 0.08f * area;
-
-   /* TODO: Quasirandom? */
-   vg_info( "Map area: %f. Max particles: %u\n", area, particles );
-
-   for( u32 i=0; i<particles; i++ ){
-      v3f pos;
-      v3_mul( volume, (v3f){ vg_randf64(), 1000.0f, vg_randf64() }, pos );
-      pos[1] = 1000.0f;
-      v3_add( pos, world->scene_geo.bbx[0], pos );
-      
-      ray_hit hit;
-      hit.dist = INFINITY;
-
-      if( ray_world( world, pos, (v3f){0.0f,-1.0f,0.0f}, &hit )){
-         struct world_surface *m1 = ray_hit_surface( world, &hit );
-         if((hit.normal[1] > 0.8f) && (m1 == mat) && (hit.pos[1] > 0.0f+10.0f)){
-            world_add_blob( world, scene, &hit );
-            count ++;
-         }
-      }
-   }
-
-   vg_info( "%d foliage models added\n", count );
-}
-
-VG_STATIC void world_generate( world_instance *world )
-{
-   /* 
-    * Compile meshes into the world scenes
-    */
-   scene_init( &world->scene_geo, 320000, 1200000 );
-   u32 buf_size = scene_mem_required( &world->scene_geo );
-   u8 *buffer = vg_linear_alloc( world->heap, buf_size );
-   scene_supply_buffer( &world->scene_geo, buffer );
-
-   m4x3f midentity;
-   m4x3_identity( midentity );
-
-   /*
-    * Generate scene: collidable geometry
-    * ----------------------------------------------------------------
-    */
-
-   vg_info( "Generating collidable geometry\n" );
-   
-   for( u32 i=0; i<world->surface_count; i++ ){
-      struct world_surface *surf = &world->surfaces[ i ];
-
-      if( surf->info.flags & k_material_flag_collision )
-         world_add_all_if_material( midentity, &world->scene_geo, 
-                                    &world->meta, i );
-
-      scene_copy_slice( &world->scene_geo, &surf->sm_geo );
-   }
-
-   /* compress that bad boy */
-   u32 new_vert_max = world->scene_geo.vertex_count,
-       new_vert_size = vg_align8(new_vert_max*sizeof(scene_vert)),
-       new_indice_len = world->scene_geo.indice_count*sizeof(u32);
-
-   u32 *src_indices = world->scene_geo.arrindices,
-       *dst_indices = (u32 *)(buffer + new_vert_size);
-
-   memmove( dst_indices, src_indices, new_indice_len );
-
-   world->scene_geo.max_indices = world->scene_geo.indice_count;
-   world->scene_geo.max_vertices = world->scene_geo.vertex_count;
-   buf_size = scene_mem_required( &world->scene_geo );
-
-   buffer = vg_linear_resize( world->heap, buffer, buf_size );
-
-   world->scene_geo.arrvertices = (scene_vert *)(buffer);
-   world->scene_geo.arrindices = (u32 *)(buffer + new_vert_size);
-
-   scene_upload_async( &world->scene_geo, &world->mesh_geo );
-
-   /* need send off the memory to the gpu before we can create the bvh. */
-   vg_async_stall();
-   vg_info( "creating bvh\n" );
-
-   /* setup spacial mapping and rigidbody */
-   world->geo_bh = scene_bh_create( world->heap, &world->scene_geo );
-
-   v3_zero( world->rb_geo.rb.co );
-   v3_zero( world->rb_geo.rb.v );
-   q_identity( world->rb_geo.rb.q );
-   v3_zero( world->rb_geo.rb.w );
-
-   world->rb_geo.type = k_rb_shape_scene;
-   world->rb_geo.inf.scene.bh_scene = world->geo_bh;
-   rb_init_object( &world->rb_geo );
-
-   /*
-    * Generate scene: non-collidable geometry
-    * ----------------------------------------------------------------
-    */
-   vg_info( "Generating non-collidable geometry\n" );
-
-   vg_async_item *call = scene_alloc_async( &world->scene_no_collide,
-                                            &world->mesh_no_collide,
-                                            200000, 500000 );
-
-   for( u32 i=0; i<world->surface_count; i++ ){
-      struct world_surface *surf = &world->surfaces[ i ];
-
-      if( !(surf->info.flags & k_material_flag_collision) ){
-         world_add_all_if_material( midentity, 
-                                    &world->scene_no_collide, &world->meta, i );
-      }
-
-      if( surf->info.flags & k_material_flag_grow_grass )
-         world_apply_procedural_foliage( world, 
-                                         &world->scene_no_collide, surf );
-
-      scene_copy_slice( &world->scene_no_collide, &surf->sm_no_collide );
-   }
-
-   for( u32 i=0; i<mdl_arrcount( &world->ent_traffic ); i++ ){
-      ent_traffic *vehc = mdl_arritm( &world->ent_traffic, i );
-
-      for( u32 j=0; j<vehc->submesh_count; j++ ){
-         mdl_submesh *sm = mdl_arritm( &world->meta.submeshs, 
-                                       vehc->submesh_start+j );
-
-         if( sm->flags & k_submesh_flag_consumed ){
-            continue;
-         }
-
-         m4x3f identity;
-         m4x3_identity( identity );
-         scene_add_mdl_submesh( &world->scene_no_collide, 
-                                &world->meta, sm, identity );
-
-         scene_copy_slice( &world->scene_no_collide, sm );
-         sm->flags |= k_submesh_flag_consumed;
-      }
-   }
-
-   vg_async_dispatch( call, async_scene_upload );
-}
-
-float fsd_cone_infinite( v3f p, v2f c )
-{
-   v2f q = { v2_length( (v2f){ p[0], p[2] } ), -p[1] };
-   float s = vg_maxf( 0.0f, v2_dot( q, c ) );
-
-   v2f v0;
-   v2_muls( c, s, v0 );
-   v2_sub( q, v0, v0 );
-
-   float d = v2_length( v0 );
-   return d * ((q[0]*c[1]-q[1]*c[0]<0.0f)?-1.0f:1.0f);
-}
-
-struct light_indices_upload_info{
-   world_instance *world;
-   v3i count;
-
-   void *data;
-};
-
-VG_STATIC void async_upload_light_indices( void *payload, u32 size )
-{
-   struct light_indices_upload_info *info = payload;
-
-   glGenTextures( 1, &info->world->tex_light_cubes );
-   glBindTexture( GL_TEXTURE_3D, info->world->tex_light_cubes );
-   glTexImage3D( GL_TEXTURE_3D, 0, GL_RG32UI,
-                 info->count[0], info->count[1], info->count[2],
-                 0, GL_RG_INTEGER, GL_UNSIGNED_INT, info->data );
-   glTexParameteri( GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
-   glTexParameteri( GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
-}
-
-VG_STATIC void world_compute_light_indices( world_instance *world )
-{
-   /* light cubes */
-   v3f cubes_min, cubes_max;
-   v3_muls( world->scene_geo.bbx[0], 1.0f/k_light_cube_size, cubes_min );
-   v3_muls( world->scene_geo.bbx[1], 1.0f/k_light_cube_size, cubes_max );
-
-   v3_sub( cubes_min, (v3f){ 0.5f, 0.5f, 0.5f }, cubes_min );
-   v3_add( cubes_max, (v3f){ 0.5f, 0.5f, 0.5f }, cubes_max );
-
-   v3_floor( cubes_min, cubes_min );
-   v3_floor( cubes_max, cubes_max );
-
-   v3i icubes_min, icubes_max;
-
-   for( int i=0; i<3; i++ ){
-      icubes_min[i] = cubes_min[i];
-      icubes_max[i] = cubes_max[i];
-   }
-
-   v3f cube_size;
-
-   v3i icubes_count;
-   v3i_sub( icubes_max, icubes_min, icubes_count );
-   
-   for( int i=0; i<3; i++ ){
-      int clamped_count = VG_MIN( 128, icubes_count[i]+1 );
-      float clamped_max = icubes_min[i] + clamped_count,
-                    max = icubes_min[i] + icubes_count[i]+1;
-
-      icubes_count[i] = clamped_count;
-      cube_size[i] = (max / clamped_max) * k_light_cube_size;
-      cubes_max[i] = clamped_max;
-   }
-
-   v3_mul( cubes_min, cube_size, cubes_min );
-   v3_mul( cubes_max, cube_size, cubes_max );
-
-   for( int i=0; i<3; i++ ){
-      float range = cubes_max[i]-cubes_min[i];
-      world->ub_lighting.g_cube_inv_range[i] = 1.0f / range;
-      world->ub_lighting.g_cube_inv_range[i] *= (float)icubes_count[i];
-
-      vg_info( "cubes[%d]: %d\n", i, icubes_count[i] );
-   }
-
-   int total_cubes = icubes_count[0]*icubes_count[1]*icubes_count[2];
-
-   u32 data_size = vg_align8(total_cubes*sizeof(u32)*2),
-       hdr_size  = vg_align8(sizeof(struct light_indices_upload_info));
-
-   vg_async_item *call = vg_async_alloc( data_size + hdr_size );
-   struct light_indices_upload_info *info = call->payload;
-   info->data = ((u8*)call->payload) + hdr_size;
-   info->world = world;
-   u32 *cubes_index = info->data;
-
-   for( int i=0; i<3; i++ )
-      info->count[i] = icubes_count[i];
-                                       
-   vg_info( "Computing light cubes (%d) [%f %f %f] -> [%f %f %f]\n", 
-             total_cubes, cubes_min[0], -cubes_min[2], cubes_min[1],
-                          cubes_max[0], -cubes_max[2], cubes_max[1] );
-   v3_copy( cubes_min, world->ub_lighting.g_cube_min );
-
-   float bound_radius = v3_length( cube_size );
-
-   for( int iz = 0; iz<icubes_count[2]; iz ++ ){
-      for( int iy = 0; iy<icubes_count[1]; iy++ ){
-         for( int ix = 0; ix<icubes_count[0]; ix++ ){
-            boxf bbx;
-            v3_div( (v3f){ ix, iy, iz }, world->ub_lighting.g_cube_inv_range, 
-                  bbx[0] );
-            v3_div( (v3f){ ix+1, iy+1, iz+1 }, 
-                  world->ub_lighting.g_cube_inv_range, 
-                  bbx[1] );
-
-            v3_add( bbx[0], world->ub_lighting.g_cube_min, bbx[0] );
-            v3_add( bbx[1], world->ub_lighting.g_cube_min, bbx[1] );
-
-            v3f center;
-            v3_add( bbx[0], bbx[1], center );
-            v3_muls( center, 0.5f, center );
-            
-            u32 indices[6] = { 0, 0, 0, 0, 0, 0 };
-            u32 count = 0;
-
-            float influences[6] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
-            const int N = vg_list_size( influences );
-
-            for( u32 j=0; j<mdl_arrcount(&world->ent_light); j ++ ){
-               ent_light *light = mdl_arritm( &world->ent_light, j );
-               v3f closest;
-               closest_point_aabb( light->transform.co, bbx, closest );
-
-               float dist = v3_dist( closest, light->transform.co ),
-                     influence = 1.0f/(dist+1.0f);
-
-               if( dist > light->range )
-                  continue;
-
-               if( light->type == k_light_type_spot){
-                  v3f local;
-                  m4x3_mulv( light->inverse_world, center, local );
-
-                  float r = fsd_cone_infinite( local, light->angle_sin_cos );
-
-                  if( r > bound_radius )
-                     continue;
-               }
-
-               int best_pos = N;
-               for( int k=best_pos-1; k>=0; k -- )
-                  if( influence > influences[k] )
-                     best_pos = k;
-
-               if( best_pos < N ){
-                  for( int k=N-1; k>best_pos; k -- ){
-                     influences[k] = influences[k-1];
-                     indices[k] = indices[k-1];
-                  }
-
-                  influences[best_pos] = influence;
-                  indices[best_pos] = j;
-               }
-            }
-
-            for( int j=0; j<N; j++ )
-               if( influences[j] > 0.0f )
-                  count ++;
-
-            int base_index = iz * (icubes_count[0]*icubes_count[1]) +
-                             iy * (icubes_count[0]) +
-                             ix;
-
-            int lower_count = VG_MIN( 3, count );
-            u32 packed_index_lower = lower_count;
-            packed_index_lower |= indices[0]<<2;
-            packed_index_lower |= indices[1]<<12;
-            packed_index_lower |= indices[2]<<22;
-
-            int upper_count = VG_MAX( 0, count - lower_count );
-            u32 packed_index_upper = upper_count;
-            packed_index_upper |= indices[3]<<2;
-            packed_index_upper |= indices[4]<<12;
-            packed_index_upper |= indices[5]<<22;
-
-            cubes_index[ base_index*2 + 0 ] = packed_index_lower;
-            cubes_index[ base_index*2 + 1 ] = packed_index_upper;
-         }
-      }
-   }
-
-   vg_async_dispatch( call, async_upload_light_indices );
-}
-
-VG_STATIC void async_world_postprocess_render( void *payload, u32 _size )
-{
-   /* create scene lighting buffer */
-   world_instance *world = payload;
-
-   u32 size = VG_MAX(mdl_arrcount(&world->ent_light),1) * sizeof(float)*12;
-   vg_info( "Upload %ubytes (lighting)\n", size );
-
-   glGenBuffers( 1, &world->tbo_light_entities );
-   glBindBuffer( GL_TEXTURE_BUFFER, world->tbo_light_entities );
-   glBufferData( GL_TEXTURE_BUFFER, size, NULL, GL_DYNAMIC_DRAW );
-   
-   /* buffer layout
-    *  
-    *  colour               position                direction (spots)
-    * | .   .   .   .     | .   .   .   .         | .   .   .   .  |
-    * | Re  Ge  Be  Night | Xco Yco Zco Range     | Dx  Dy  Dz  Da |
-    *
-    */
-
-   v4f *light_dst = glMapBuffer( GL_TEXTURE_BUFFER, GL_WRITE_ONLY );
-   for( u32 i=0; i<mdl_arrcount(&world->ent_light); i++ ){
-      ent_light *light = mdl_arritm( &world->ent_light, i );
-
-      /* colour  + night */
-      v3_muls( light->colour, light->colour[3] * 2.0f, light_dst[i*3+0] );
-      light_dst[i*3+0][3] = 2.0f;
-
-      if( !light->daytime ){
-         u32 hash = (i * 29986577u) & 0xffu;
-         float switch_on = hash;
-               switch_on *= (1.0f/255.0f);
-
-         light_dst[i*3+0][3] = 0.44f + switch_on * 0.015f;
-      }
-      
-      /* position + 1/range^2 */
-      v3_copy( light->transform.co, light_dst[i*3+1] );
-      light_dst[i*3+1][3] = 1.0f/(light->range*light->range);
-
-      /* direction + angle */
-      q_mulv( light->transform.q, (v3f){0.0f,-1.0f,0.0f}, light_dst[i*3+2]);
-      light_dst[i*3+2][3] = cosf( light->angle );
-   }
-
-   glUnmapBuffer( GL_TEXTURE_BUFFER );
-
-   glGenTextures( 1, &world->tex_light_entities );
-   glBindTexture( GL_TEXTURE_BUFFER, world->tex_light_entities );
-   glTexBuffer( GL_TEXTURE_BUFFER, GL_RGBA32F, world->tbo_light_entities );
-
-   /* Upload lighting uniform buffer */
-   if( world->water.enabled )
-      v4_copy( world->water.plane, world->ub_lighting.g_water_plane );
-
-   v4f info_vec;
-   v3f *bounds = world->scene_geo.bbx;
-
-   info_vec[0] = bounds[0][0];
-   info_vec[1] = bounds[0][2];
-   info_vec[2] = 1.0f/ (bounds[1][0]-bounds[0][0]);
-   info_vec[3] = 1.0f/ (bounds[1][2]-bounds[0][2]);
-   v4_copy( info_vec, world->ub_lighting.g_depth_bounds );
-
-   /* 
-    * Rendering the depth map
-    */
-   camera ortho;
-
-   v3f extent;
-   v3_sub( world->scene_geo.bbx[1], world->scene_geo.bbx[0], extent );
-
-   float fl = world->scene_geo.bbx[0][0],
-         fr = world->scene_geo.bbx[1][0],
-         fb = world->scene_geo.bbx[0][2],
-         ft = world->scene_geo.bbx[1][2],
-         rl = 1.0f / (fr-fl),
-         tb = 1.0f / (ft-fb);
-
-   m4x4_zero( ortho.mtx.p );
-   ortho.mtx.p[0][0] = 2.0f * rl;
-   ortho.mtx.p[2][1] = 2.0f * tb;
-   ortho.mtx.p[3][0] = (fr + fl) * -rl;
-   ortho.mtx.p[3][1] = (ft + fb) * -tb;
-   ortho.mtx.p[3][3] = 1.0f;
-   m4x3_identity( ortho.transform );
-   camera_update_view( &ortho );
-   camera_finalize( &ortho );
-
-   glDisable(GL_DEPTH_TEST);
-   glDisable(GL_BLEND);
-   glDisable(GL_CULL_FACE);
-   render_fb_bind( &world->heightmap, 0 );
-   shader_blitcolour_use();
-   shader_blitcolour_uColour( (v4f){-9999.0f,-9999.0f,-9999.0f,-9999.0f} );
-   render_fsquad();
-
-   glEnable(GL_BLEND);
-   glBlendFunc(GL_ONE, GL_ONE);
-   glBlendEquation(GL_MAX);
-
-   render_world_position( world, &ortho );
-   glDisable(GL_BLEND);
-   glEnable(GL_DEPTH_TEST);
-   glBindFramebuffer( GL_FRAMEBUFFER, 0 );
-
-   /* upload full buffer */
-   glBindBuffer( GL_UNIFORM_BUFFER, world->ubo_lighting );
-   glBufferSubData( GL_UNIFORM_BUFFER, 0, 
-                    sizeof(struct ub_world_lighting), &world->ub_lighting );
-}
-
-VG_STATIC int reset_player( int argc, char const *argv[] );
-VG_STATIC void world_post_process( world_instance *world )
-{
-   world_compute_light_indices( world );
-   vg_async_call( async_world_postprocess_render, world, 0 );
-}
-
-VG_STATIC void world_process_resources( world_instance *world )
-{
-   vg_info( "Loading textures\n" );
-
-   world->texture_count = 0;
-
-   world->texture_count = world->meta.textures.count+1;
-   world->textures = vg_linear_alloc( world->heap,
-                              vg_align8(sizeof(GLuint)*world->texture_count) );
-
-   vg_tex2d_replace_with_error( &world->textures[0] );
-
-   for( u32 i=0; i<mdl_arrcount(&world->meta.textures); i++ ){
-      mdl_texture *tex = mdl_arritm( &world->meta.textures, i );
-
-      if( !tex->file.pack_size ){
-         vg_fatal_error( "World models must have packed textures!" );
-      }
-
-      vg_linear_clear( vg_mem.scratch );
-      void *src_data = vg_linear_alloc( vg_mem.scratch, 
-                                        tex->file.pack_size );
-      mdl_fread_pack_file( &world->meta, &tex->file, src_data );
-
-      vg_tex2d_load_qoi_async( src_data, tex->file.pack_size,
-                               VG_TEX2D_NEAREST|VG_TEX2D_REPEAT,
-                               &world->textures[i+1] );
-   }
-
-   vg_info( "Loading materials\n" );
-
-   world->surface_count = world->meta.materials.count+1;
-   world->surfaces = vg_linear_alloc( world->heap,
-               vg_align8(sizeof(struct world_surface)*world->surface_count) );
-
-   /* error material */
-   struct world_surface *errmat = &world->surfaces[0];
-   memset( errmat, 0, sizeof(struct world_surface) );
-                       
-   for( u32 i=0; i<mdl_arrcount(&world->meta.materials); i++ ){
-      world->surfaces[i+1].info = 
-         *(mdl_material *)mdl_arritm( &world->meta.materials, i );
-   }
-}
-
-VG_STATIC void world_free( world_instance *world )
-{
-   vg_info( "Free world @%p\n", world );
-
-   /* free meshes */
-   mesh_free( &world->mesh_route_lines );
-   mesh_free( &world->mesh_geo );
-   mesh_free( &world->mesh_no_collide );
-   mesh_free( &world->mesh_water );
-   
-   /* glDeleteBuffers silently ignores 0's and names that do not correspond to 
-    * existing buffer objects. 
-    * */
-   glDeleteBuffers( 1, &world->tbo_light_entities );
-   glDeleteTextures( 1, &world->tex_light_entities );
-   glDeleteTextures( 1, &world->tex_light_cubes );
-
-   /* delete textures and meshes */
-   glDeleteTextures( world->texture_count, world->textures );
-
-   u32 world_index = world - world_global.worlds;
-   if( world_index ){
-      vg_linear_del( world_global.worlds[world_index-1].heap, 
-                     vg_linear_header(world->heap) );
-   }
-
-   world->status = k_world_status_unloaded;
-}
-
-/* 
- * checks:
- *  1. to see if all audios owned by the world have been stopped
- *  2. that this is the least significant world
- */
-VG_STATIC int world_freeable( world_instance *world )
-{
-   if( world->status != k_world_status_unloading ) return 0;
-   u8 world_id = (world - world_global.worlds) + 1;
-
-   for( u32 i=world_id; i<vg_list_size(world_global.worlds); i++ ){
-      if( world_global.worlds[i].status != k_world_status_unloaded ){
-         return 0;
-      }
-   }
-
-   int freeable = 1;
-   audio_lock();
-   for( u32 i=0; i<AUDIO_CHANNELS; i++ ){
-      audio_channel *ch = &vg_audio.channels[i];
-      
-      if( ch->allocated && (ch->world_id == world_id)){
-         if( !audio_channel_finished( ch ) ){
-            freeable = 0;
-            break;
-         }
-      }
-   }
-   audio_unlock();
-   return freeable;
-}
-
-VG_STATIC void world_init_blank( world_instance *world )
-{
-   memset( &world->meta, 0, sizeof(mdl_context) );
-
-   world->textures = NULL;
-   world->texture_count = 0;
-   world->surfaces = NULL;
-   world->surface_count = 0;
-
-   world->geo_bh = NULL;
-   world->volume_bh = NULL;
-   world->audio_bh = NULL;
-   world->rendering_gate = NULL;
-
-   world->water.enabled = 0;
-   world->time = 0.0;
-
-   /* default lighting conditions 
-    * -------------------------------------------------------------*/
-   struct ub_world_lighting *state = &world->ub_lighting;
-
-   state->g_light_preview = 0;
-   state->g_shadow_samples = 8;
-   state->g_water_fog = 0.04f;
-
-   v4_zero( state->g_water_plane );
-   v4_zero( state->g_depth_bounds );
-
-   state->g_shadow_length = 9.50f;
-   state->g_shadow_spread = 0.65f;
-
-   v3_copy( (v3f){0.37f, 0.54f, 0.97f}, state->g_daysky_colour );
-   v3_copy( (v3f){0.03f, 0.05f, 0.20f}, state->g_nightsky_colour );
-   v3_copy( (v3f){1.00f, 0.32f, 0.01f}, state->g_sunset_colour );
-   v3_copy( (v3f){0.13f, 0.17f, 0.35f}, state->g_ambient_colour );
-   v3_copy( (v3f){0.25f, 0.17f, 0.51f}, state->g_sunset_ambient );
-   v3_copy( (v3f){1.10f, 0.89f, 0.35f}, state->g_sun_colour );
-}
-
-/* detatches any nonlocal gates */
-VG_STATIC void world_unlink_nonlocal( world_instance *world )
-{
-   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_nonlocel ){
-         gate->type = k_gate_type_nonlocal_unlinked;
-      }
-   }
-}
-
-/* attatches nonlocal gates, to be called from main thread ONLY! */
-VG_STATIC void world_link_nonlocal_async( void *payload, u32 size )
-{
-   world_instance *world = payload;
-   u32 world_id = world - world_global.worlds;
-
-   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_nonlocal_unlinked ){
-         const char *key = mdl_pstr( &world->meta, gate->key );
-         vg_info( "key: %s\n", key );
-
-         for( u32 i=0; i<vg_list_size(world_global.worlds); i++ ){
-            world_instance *other = &world_global.worlds[i];
-            if( other == world ) continue;
-            if( other->status != k_world_status_loaded ) continue;
-            vg_info( "Checking world %u for key matches\n", i );
-
-            for( u32 j=0; j<mdl_arrcount( &other->ent_gate ); j++ ){
-               ent_gate *gate2 = mdl_arritm( &other->ent_gate, j );
-               if( gate2->type != k_gate_type_nonlocal_unlinked ) continue;
-
-               const char *key2 = mdl_pstr( &other->meta, gate2->key );
-               vg_info( " key2: %s\n", key2 );
-
-               if( strcmp( key, key2 ) ) continue;
-
-               vg_success( "Non-local matching pair '%s' found. (%u:%u)\n",
-                            key, world_id, i );
-                  
-               gate->type = k_gate_type_nonlocel;
-               gate2->type = k_gate_type_nonlocel;
-               gate->target = i;
-               gate2->target = world_id;
-
-               v3_copy( gate->co[0], gate2->co[1] );
-               v3_copy( gate2->co[0], gate->co[1] );
-               v4_copy( gate->q[0], gate2->q[1] );
-               v4_copy( gate2->q[0], gate->q[1] );
-
-               v4f qflip;
-               q_axis_angle( qflip, (v3f){0.0f,1.0f,0.0f}, VG_PIf );
-               q_mul( gate->q[0], qflip, gate->q[0] );
-               q_mul( gate->q[1], qflip, gate->q[1] );
-
-               gate_transform_update( gate );
-               gate_transform_update( gate2 );
-
-               goto matched;
-            }
-         }
-matched:;
-      }
-   }
-}
-
-VG_STATIC void world_entities_init( u32 world_id )
-{
-   world_instance *world = &world_global.worlds[world_id];
-
-   /* 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 void world_load( u32 index, const char *path )
-{
-   vg_rand_seed( 9001 );
-
-   world_instance *world = &world_global.worlds[index];
-   world_init_blank( world );
-   world->status = k_world_status_loading;
-
-   vg_info( "Loading world: %s\n", path );
-
-   void *allocator = NULL;
-   if( index == 0 ) allocator = world_global.heap;
-   else allocator = world_global.worlds[index-1].heap;
-
-   u32 heap_availible = vg_linear_remaining( allocator );
-   u32 min_overhead = sizeof(vg_linear_allocator);
-
-   if( heap_availible < (min_overhead+1024) ){
-      vg_fatal_error( "out of memory" );
-   }
-
-   u32 size = heap_availible - min_overhead;
-   void *heap = vg_create_linear_allocator( allocator, size, VG_MEMORY_SYSTEM );
-
-   world->heap = heap;
-   mdl_context *meta = &world->meta;
-
-   mdl_open( meta, path, world->heap );
-   mdl_load_metadata_block( meta, world->heap );
-   mdl_load_animation_block( meta, world->heap );
-   mdl_load_mesh_block( meta, world->heap );
-
-   mdl_load_array( meta, &world->ent_gate,      "ent_gate",       heap );
-   mdl_load_array( meta, &world->ent_camera,    "ent_camera",     heap );
-   mdl_load_array( meta, &world->ent_spawn,     "ent_spawn",      heap );
-   mdl_load_array( meta, &world->ent_light,     "ent_light",      heap );
-   mdl_load_array( meta, &world->ent_route_node,"ent_route_node", heap );
-   mdl_load_array( meta, &world->ent_path_index,"ent_path_index", heap );
-   mdl_load_array( meta, &world->ent_checkpoint,"ent_checkpoint", heap );
-   mdl_load_array( meta, &world->ent_route,     "ent_route",      heap );
-   mdl_load_array( meta, &world->ent_water,     "ent_water",      heap );
-   mdl_load_array( meta, &world->ent_audio_clip,"ent_audio_clip", heap );
-   mdl_load_array( meta, &world->ent_audio,     "ent_audio",      heap );
-   mdl_load_array( meta, &world->ent_volume,    "ent_volume",     heap );
-   mdl_load_array( meta, &world->ent_traffic,   "ent_traffic",    heap );
-   mdl_load_array( meta, &world->ent_marker,    "ent_marker",     heap );
-   mdl_load_array( meta, &world->ent_skateshop, "ent_skateshop",  heap );
-   mdl_load_array( meta, &world->ent_swspreview,"ent_swspreview", heap );
-
-   mdl_array_ptr infos;
-   mdl_load_array( meta, &infos, "ent_worldinfo", vg_mem.scratch );
-
-   if( mdl_arrcount(&infos) ){
-      world->info = *((ent_worldinfo *)mdl_arritm(&infos,0));
-   }
-   else{
-      world->info.pstr_author = 0;
-      world->info.pstr_desc = 0;
-      world->info.pstr_name = 0;
-      world->info.timezone = 0.0f;
-   }
-
-   time_t t;
-   struct tm *tm;
-   time( &t );
-   tm = gmtime( &t );
-   world->time = (float)tm->tm_min/20.0f + (world->info.timezone/24.0f);
-
-   /* process resources from pack */
-   world_process_resources( world );
-
-   world_routes_ent_init( world );
-   world_entities_init( index );
-   world->volume_bh = bh_create( heap, &bh_system_volumes, world,
-                                 mdl_arrcount( &world->ent_volume ), 1 );
-   
-   /* main bulk */
-   world_generate( world );
-   world_routes_generate( world );
-   world_post_process( world );
-
-   mdl_close( meta );
-   world->status = k_world_status_loaded;
-}
+VG_STATIC void world_init_blank( world_instance *world );
+VG_STATIC void world_gen_load_surfaces(void);
+VG_STATIC void world_gen_generate_meshes(void);
+VG_STATIC void world_gen_compute_light_indices(void);
+VG_STATIC void async_world_postprocess_render( void *payload, u32 _size );
 
 #endif /* WORLD_GEN_H */
index 012199eeea5510d93a9d743f9f47144f923ccc17..6b9d6203f52de17653d5c75fb6ded38b5e95b529 100644 (file)
@@ -5,7 +5,7 @@
 #ifndef WORLD_INFO_H
 #define WORLD_INFO_H
 
-#include "vg/vg_stdint.h"
+#include "world.h"
 
 /* Purely an information header, shares common strings across client and 
  * server programs. */
diff --git a/world_load.c b/world_load.c
new file mode 100644 (file)
index 0000000..493b89d
--- /dev/null
@@ -0,0 +1,369 @@
+#ifndef WORLD_LOAD_C
+#define WORLD_LOAD_C
+
+#include "world_load.h"
+#include "world_routes.h"
+#include "world_gate.h"
+
+/* 
+ * load the .mdl file located in path (relative to exe), it will load into the 
+ * slot specified by world_loader.world_index
+ */
+VG_STATIC void world_load_mdl( const char *path )
+{
+   vg_rand_seed( 9001 );
+
+   world_instance *world = world_loading_instance();
+   world_init_blank( world );
+   world->status = k_world_status_loading;
+
+   vg_info( "Loading world: %s\n", path );
+
+   void *allocator = NULL;
+   if( world_loader.world_index == 0 ) allocator = world_static.heap;
+   else allocator = world_static.worlds[world_loader.world_index-1].heap;
+
+   u32 heap_availible = vg_linear_remaining( allocator );
+   u32 min_overhead = sizeof(vg_linear_allocator);
+
+   if( heap_availible < (min_overhead+1024) ){
+      vg_fatal_error( "out of memory" );
+   }
+
+   u32 size = heap_availible - min_overhead;
+   void *heap = vg_create_linear_allocator( allocator, size, VG_MEMORY_SYSTEM );
+
+   world->heap = heap;
+   mdl_context *meta = &world->meta;
+
+   mdl_open( meta, path, world->heap );
+   mdl_load_metadata_block( meta, world->heap );
+   mdl_load_animation_block( meta, world->heap );
+   mdl_load_mesh_block( meta, world->heap );
+
+   mdl_load_array( meta, &world->ent_gate,      "ent_gate",       heap );
+   mdl_load_array( meta, &world->ent_camera,    "ent_camera",     heap );
+   mdl_load_array( meta, &world->ent_spawn,     "ent_spawn",      heap );
+   mdl_load_array( meta, &world->ent_light,     "ent_light",      heap );
+   mdl_load_array( meta, &world->ent_route_node,"ent_route_node", heap );
+   mdl_load_array( meta, &world->ent_path_index,"ent_path_index", heap );
+   mdl_load_array( meta, &world->ent_checkpoint,"ent_checkpoint", heap );
+   mdl_load_array( meta, &world->ent_route,     "ent_route",      heap );
+   mdl_load_array( meta, &world->ent_water,     "ent_water",      heap );
+   mdl_load_array( meta, &world->ent_audio_clip,"ent_audio_clip", heap );
+   mdl_load_array( meta, &world->ent_audio,     "ent_audio",      heap );
+   mdl_load_array( meta, &world->ent_volume,    "ent_volume",     heap );
+   mdl_load_array( meta, &world->ent_traffic,   "ent_traffic",    heap );
+   mdl_load_array( meta, &world->ent_marker,    "ent_marker",     heap );
+   mdl_load_array( meta, &world->ent_skateshop, "ent_skateshop",  heap );
+   mdl_load_array( meta, &world->ent_swspreview,"ent_swspreview", heap );
+
+   mdl_array_ptr infos;
+   mdl_load_array( meta, &infos, "ent_worldinfo", vg_mem.scratch );
+
+   if( mdl_arrcount(&infos) ){
+      world->info = *((ent_worldinfo *)mdl_arritm(&infos,0));
+   }
+   else{
+      world->info.pstr_author = 0;
+      world->info.pstr_desc = 0;
+      world->info.pstr_name = 0;
+      world->info.timezone = 0.0f;
+   }
+
+   time_t t;
+   struct tm *tm;
+   time( &t );
+   tm = gmtime( &t );
+   world->time = (float)tm->tm_min/20.0f + (world->info.timezone/24.0f);
+
+   /* process resources from pack */
+   world_gen_load_surfaces();
+   world_gen_routes_ent_init();
+   world_gen_entities_init();
+   world->volume_bh = bh_create( heap, &bh_system_volumes, world,
+                                 mdl_arrcount( &world->ent_volume ), 1 );
+   
+   /* main bulk */
+   world_gen_generate_meshes();
+   world_gen_routes_generate();
+   world_gen_compute_light_indices();
+   vg_async_call( async_world_postprocess_render, NULL, 0 );
+
+   mdl_close( meta );
+   world->status = k_world_status_loaded;
+}
+
+/*
+ * op: k_async_op_world_loading
+ *     k_async_op_world_preloading
+ * -----------------------------------------------------------------------------
+ */
+
+static void async_skaterift_world_loaded( void *payload, u32 size )
+{
+   skaterift_end_op();
+}
+
+/*
+ * Does a complete world switch using the remaining free slots
+ */
+static void skaterift_world_changer_thread( void *data )
+{
+   if( world_loader.location == k_world_load_type_workshop ){
+      vg_fatal_error( "Unimplemented\n" );
+   }
+
+   char path_buf[4096];
+   vg_str path;
+   vg_strnull( &path, path_buf, 4096 );
+   vg_strcat( &path, "maps/" );
+
+   vg_str folder = path;
+   vg_strcat( &folder, world_loader.name );
+   if( !vg_strgood( &folder ) ) {
+      vg_error( "Load target too long\n" );
+      vg_async_call( workshop_async_any_complete, NULL, 0 );
+      return;
+   }
+
+   char worlds[3][4096];
+   u32 i=0;
+
+   DIR *dir = opendir( folder.buffer );
+   if( !dir ){
+      vg_error( "opendir('%s') failed\n", folder.buffer );
+      vg_async_call( async_skaterift_world_loaded, NULL, 0 );
+      return;
+   }
+
+   struct dirent *entry;
+   while( (entry = readdir(dir)) ){
+      if( entry->d_type == DT_REG ){
+         if( entry->d_name[0] == '.' ) continue;
+
+         vg_str file = folder;
+         vg_strcat( &file, "/" );
+         vg_strcat( &file, entry->d_name );
+         if( !vg_strgood( &file ) ) continue;
+
+         char *ext = vg_strch( &file, '.' );
+         if( !ext ) continue;
+         if( strcmp(ext,".mdl") ) continue;
+
+         if( i == 3 ){
+            vg_warn( "There are too many .mdl files in the map folder!(3)\n" );
+            break;
+         }
+
+         strcpy( worlds[i++], file.buffer );
+      }
+   }
+   closedir(dir);
+
+   if( i == 0 ){
+      vg_warn( "There are no .mdl files in the map folder.\n" );
+   }
+
+   u32 first_index = 0;
+   for( u32 j=0; j<i; j++ ){
+      vg_str name = { .buffer = worlds[j], .i=strlen(worlds[j]), 
+                      sizeof(worlds[j]) };
+      char *fname = vg_strch( &name, '/' );
+      if( fname ){
+         if( !strcmp( fname+1, "main.mdl" ) ){
+            first_index = j;
+         }
+      }
+   }
+
+   world_loader.generate_point_cloud = 1;
+   world_loader.world_index = 1;
+   world_load_mdl( worlds[first_index] );
+
+   world_loader.generate_point_cloud = 0;
+   for( u32 j=0; j<i; j++ ){
+      if( j != first_index ){
+         world_loader.world_index = j+1;
+         world_load_mdl( worlds[j] );
+      }
+   }
+
+   vg_async_call( async_skaterift_world_loaded, NULL, 0 );
+}
+
+/* holding pattern before we can start loading the new world, since we might be
+ * waiting for audio to stop */
+static void skaterift_change_world_preupdate(void)
+{
+   for( u32 i=1; i<vg_list_size(world_static.worlds); i++ ){
+      world_instance *inst = &world_static.worlds[i];
+      
+      if( inst->status == k_world_status_unloading ){
+         if( world_freeable( inst ) ){
+            world_free( inst );
+         }
+         return;
+      }
+   }
+
+   vg_info( "worlds cleared, begining load\n" );
+   skaterift_shift_op( k_async_op_world_loading );
+
+   /* finally can start the loader */
+   vg_loader_start( skaterift_world_changer_thread, NULL );
+}
+
+/* places all loaded worlds into unloading state */
+static void skaterift_change_world( const char *world_name )
+{
+   vg_info( "switching to %s\n", world_name );
+
+   if( world_static.active_world != 0 ){
+      vg_error( "Cannot change worlds while in non-root world\n" );
+   }
+   else{
+      skaterift_begin_op( k_async_op_world_preloading );
+
+      vg_linear_clear( vg_mem.scratch );
+      vg_strncpy( world_name, world_loader.name,
+                  vg_list_size(world_loader.name), k_strncpy_overflow_fatal );
+      
+      vg_info( "unloading old worlds\n" );
+      world_unlink_nonlocal( &world_static.worlds[0] );
+      
+      for( u32 i=1; i<vg_list_size(world_static.worlds); i++ ){
+         world_instance *inst = &world_static.worlds[i];
+
+         if( inst->status == k_world_status_loaded ){
+            inst->status = k_world_status_unloading;
+            world_fadeout_audio( inst );
+         }
+      }
+   }
+}
+
+/* console command for the above function */
+static int skaterift_change_world_command( int argc, const char *argv[] )
+{
+   if( argc == 1 )
+      skaterift_change_world( argv[0] );
+
+   return 0;
+}
+
+
+static world_instance *world_loading_instance(void){
+   return &world_static.worlds[ world_loader.world_index ];
+}
+
+/* 
+ * checks:
+ *  1. to see if all audios owned by the world have been stopped
+ *  2. that this is the least significant world
+ */
+static int world_freeable( world_instance *world )
+{
+   if( world->status != k_world_status_unloading ) return 0;
+   u8 world_id = (world - world_static.worlds) + 1;
+
+   for( u32 i=world_id; i<vg_list_size(world_static.worlds); i++ ){
+      if( world_static.worlds[i].status != k_world_status_unloaded ){
+         return 0;
+      }
+   }
+
+   int freeable = 1;
+   audio_lock();
+   for( u32 i=0; i<AUDIO_CHANNELS; i++ ){
+      audio_channel *ch = &vg_audio.channels[i];
+      
+      if( ch->allocated && (ch->world_id == world_id)){
+         if( !audio_channel_finished( ch ) ){
+            freeable = 0;
+            break;
+         }
+      }
+   }
+   audio_unlock();
+   return freeable;
+}
+
+/*
+ * Free all resources for world instance
+ */
+static void world_free( world_instance *world )
+{
+   vg_info( "Free world @%p\n", world );
+
+   /* free meshes */
+   mesh_free( &world->mesh_route_lines );
+   mesh_free( &world->mesh_geo );
+   mesh_free( &world->mesh_no_collide );
+   mesh_free( &world->mesh_water );
+   
+   /* glDeleteBuffers silently ignores 0's and names that do not correspond to 
+    * existing buffer objects. 
+    * */
+   glDeleteBuffers( 1, &world->tbo_light_entities );
+   glDeleteTextures( 1, &world->tex_light_entities );
+   glDeleteTextures( 1, &world->tex_light_cubes );
+
+   /* delete textures and meshes */
+   glDeleteTextures( world->texture_count, world->textures );
+
+   u32 world_index = world - world_static.worlds;
+   if( world_index ){
+      vg_linear_del( world_static.worlds[world_index-1].heap, 
+                     vg_linear_header(world->heap) );
+   }
+
+   world->status = k_world_status_unloaded;
+}
+
+/* 
+ * reset the world structure without deallocating persistent buffers 
+ * TODO: Make this a memset(0), and have persistent items live in a static loc
+ */
+VG_STATIC void world_init_blank( world_instance *world )
+{
+   memset( &world->meta, 0, sizeof(mdl_context) );
+
+   world->textures = NULL;
+   world->texture_count = 0;
+   world->surfaces = NULL;
+   world->surface_count = 0;
+
+   world->geo_bh = NULL;
+   world->volume_bh = NULL;
+   world->audio_bh = NULL;
+   world->rendering_gate = NULL;
+
+   world->water.enabled = 0;
+   world->time = 0.0;
+
+   /* default lighting conditions 
+    * -------------------------------------------------------------*/
+   struct ub_world_lighting *state = &world->ub_lighting;
+
+   state->g_light_preview = 0;
+   state->g_shadow_samples = 8;
+   state->g_water_fog = 0.04f;
+
+   v4_zero( state->g_water_plane );
+   v4_zero( state->g_depth_bounds );
+
+   state->g_shadow_length = 9.50f;
+   state->g_shadow_spread = 0.65f;
+
+   v3_copy( (v3f){0.37f, 0.54f, 0.97f}, state->g_daysky_colour );
+   v3_copy( (v3f){0.03f, 0.05f, 0.20f}, state->g_nightsky_colour );
+   v3_copy( (v3f){1.00f, 0.32f, 0.01f}, state->g_sunset_colour );
+   v3_copy( (v3f){0.13f, 0.17f, 0.35f}, state->g_ambient_colour );
+   v3_copy( (v3f){0.25f, 0.17f, 0.51f}, state->g_sunset_ambient );
+   v3_copy( (v3f){1.10f, 0.89f, 0.35f}, state->g_sun_colour );
+}
+
+
+
+#endif /* WORLD_LOAD_C */
diff --git a/world_load.h b/world_load.h
new file mode 100644 (file)
index 0000000..74b5911
--- /dev/null
@@ -0,0 +1,32 @@
+#ifndef WORLD_LOAD_H
+#define WORLD_LOAD_H
+
+#include <time.h>
+
+#include "world.h"
+#include "world_gen.h"
+#include "world_routes.h"
+#include "world_entity.h"
+#include "world_volumes.h"
+
+#include "ent_skateshop.h"
+#include "workshop.h"
+
+struct{
+   char name[64];
+
+   enum world_load_type{
+      k_world_load_type_local,
+      k_world_load_type_workshop /* unimplemented */
+   }
+   location;
+   int generate_point_cloud;
+   u32 world_index;
+}
+static world_loader;
+
+static world_instance *world_loading_instance(void);
+static void world_free( world_instance *world );
+static int world_freeable( world_instance *world );
+
+#endif /* WORLD_LOAD_H */
diff --git a/world_physics.c b/world_physics.c
new file mode 100644 (file)
index 0000000..96ffa2f
--- /dev/null
@@ -0,0 +1,110 @@
+#ifndef WORLD_PHYSICS_C
+#define WORLD_PHYSICS_C
+
+#include "world.h"
+#include "world_physics.h"
+
+VG_STATIC void ray_world_get_tri( world_instance *world,
+                                  ray_hit *hit, v3f tri[3] )
+{
+   for( int i=0; i<3; i++ )
+      v3_copy( world->scene_geo.arrvertices[ hit->tri[i] ].co, tri[i] );
+}
+
+VG_STATIC int ray_world( world_instance *world,
+                         v3f pos, v3f dir, ray_hit *hit )
+{
+   return scene_raycast( &world->scene_geo, world->geo_bh, pos, dir, hit );
+}
+
+/*
+ * Cast a sphere from a to b and see what time it hits
+ */
+VG_STATIC int spherecast_world( world_instance *world,
+                                v3f pa, v3f pb, float r, float *t, v3f n )
+{
+   boxf region;
+   box_init_inf( region );
+   box_addpt( region, pa );
+   box_addpt( region, pb );
+   
+   v3_add( (v3f){ r, r, r}, region[1], region[1] );
+   v3_add( (v3f){-r,-r,-r}, region[0], region[0] );
+
+   v3f dir;
+   v3_sub( pb, pa, dir );
+
+   v3f dir_inv;
+   dir_inv[0] = 1.0f/dir[0];
+   dir_inv[1] = 1.0f/dir[1];
+   dir_inv[2] = 1.0f/dir[2];
+
+   int hit = -1;
+   float min_t = 1.0f;
+
+   bh_iter it;
+   bh_iter_init_box( 0, &it, region );
+   i32 idx;
+   while( bh_next( world->geo_bh, &it, &idx ) ){
+      u32 *ptri = &world->scene_geo.arrindices[ idx*3 ];
+      v3f tri[3];
+
+      boxf box;
+      box_init_inf( box );
+
+      for( int j=0; j<3; j++ ){
+         v3_copy( world->scene_geo.arrvertices[ptri[j]].co, tri[j] );
+         box_addpt( box, tri[j] );
+      }
+
+      v3_add( (v3f){ r, r, r}, box[1], box[1] );
+      v3_add( (v3f){-r,-r,-r}, box[0], box[0] );
+
+      if( !ray_aabb1( box, pa, dir_inv, 1.0f ) )
+         continue;
+      
+      float t;
+      v3f n1;
+      if( spherecast_triangle( tri, pa, dir, r, &t, n1 ) ){
+         if( t < min_t ){
+            min_t = t;
+            hit = idx;
+            v3_copy( n1, n );
+         }
+      }
+   }
+
+   *t = min_t;
+   return hit;
+}
+
+VG_STATIC 
+struct world_surface *world_tri_index_surface( world_instance *world, 
+                                                 u32 index )
+{
+   for( int i=1; i<world->surface_count; i++ ){
+      struct world_surface *surf = &world->surfaces[i];
+
+      if( (index >= surf->sm_geo.vertex_start) &&
+          (index  < surf->sm_geo.vertex_start+surf->sm_geo.vertex_count ) )
+      {
+         return surf;
+      }
+   }
+
+   return &world->surfaces[0];
+}
+
+VG_STATIC struct world_surface *world_contact_surface( world_instance *world,
+                                                         rb_ct *ct )
+{
+   return world_tri_index_surface( world, ct->element_id );
+}
+
+VG_STATIC struct world_surface *ray_hit_surface( world_instance *world,
+                                                 ray_hit *hit )
+{
+   return world_tri_index_surface( world, hit->tri[0] );
+}
+
+#endif /* WORLD_PHYSICS_C */
diff --git a/world_physics.h b/world_physics.h
new file mode 100644 (file)
index 0000000..3e7e595
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef WORLD_PHYSICS_H
+#define WORLD_PHYSICS_H
+
+#include "world.h"
+
+VG_STATIC void ray_world_get_tri( world_instance *world,
+                                  ray_hit *hit, v3f tri[3] );
+
+VG_STATIC int ray_world( world_instance *world,
+                         v3f pos, v3f dir, ray_hit *hit );
+
+VG_STATIC int spherecast_world( world_instance *world,
+                                v3f pa, v3f pb, float r, float *t, v3f n );
+
+VG_STATIC struct world_surface *world_tri_index_surface( world_instance *world, 
+                                                         u32 index );
+
+VG_STATIC struct world_surface *world_contact_surface( world_instance *world,
+                                                         rb_ct *ct );
+
+VG_STATIC struct world_surface *ray_hit_surface( world_instance *world,
+                                                 ray_hit *hit );
+
+#endif /* WORLD_PHYSICS_H */
diff --git a/world_render.c b/world_render.c
new file mode 100644 (file)
index 0000000..8255d53
--- /dev/null
@@ -0,0 +1,530 @@
+/*
+ * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#ifndef WORLD_RENDER_C
+#define WORLD_RENDER_C
+
+#include "world.h"
+#include "world_render.h"
+
+VG_STATIC void async_world_render_init( void *payload, u32 size )
+{
+   vg_info( "Allocate uniform buffers\n" );
+   for( int i=0; i<4; i++ ){
+      world_instance *world = &world_static.worlds[i];
+      world->ubo_bind_point = i;
+
+      glGenBuffers( 1, &world->ubo_lighting );
+      glBindBuffer( GL_UNIFORM_BUFFER, world->ubo_lighting );
+      glBufferData( GL_UNIFORM_BUFFER, sizeof(struct ub_world_lighting), 
+                    NULL, GL_DYNAMIC_DRAW );
+
+      glBindBufferBase( GL_UNIFORM_BUFFER, i, world->ubo_lighting );
+      VG_CHECK_GL_ERR();
+   }
+
+   vg_info( "Allocate frame buffers\n" );
+   for( int i=0; i<4; i++ ){
+      world_instance *world = &world_static.worlds[i];
+      struct framebuffer *fb = &world->heightmap;
+
+      fb->display_name = NULL;
+      fb->link = NULL;
+      fb->fixed_w = 1024;
+      fb->fixed_h = 1024;
+      fb->resolution_div = 0;
+
+      fb->attachments[0].display_name     = NULL;
+      fb->attachments[0].purpose = k_framebuffer_attachment_type_texture;
+      fb->attachments[0].internalformat   = GL_RG16F;
+      fb->attachments[0].format           = GL_RG;
+      fb->attachments[0].type             = GL_FLOAT;
+      fb->attachments[0].attachment       = GL_COLOR_ATTACHMENT0;
+
+      fb->attachments[1].purpose = k_framebuffer_attachment_type_none;
+      fb->attachments[2].purpose = k_framebuffer_attachment_type_none;
+      fb->attachments[3].purpose = k_framebuffer_attachment_type_none;
+      fb->attachments[4].purpose = k_framebuffer_attachment_type_none;
+
+      render_fb_allocate( fb );
+   }
+}
+
+VG_STATIC void world_render_init(void)
+{
+   shader_scene_standard_register();
+   shader_scene_standard_alphatest_register();
+   shader_scene_vertex_blend_register();
+   shader_scene_terrain_register();
+   shader_scene_depth_register();
+   shader_scene_position_register();
+   shader_model_sky_register();
+
+   vg_info( "Loading default world textures\n" );
+   vg_tex2d_load_qoi_async_file( "textures/garbage.qoi", 
+                                 VG_TEX2D_NEAREST|VG_TEX2D_REPEAT, 
+                                 &world_render.tex_terrain_noise );
+
+   vg_async_call( async_world_render_init, NULL, 0 );
+}
+
+VG_STATIC void world_link_lighting_ub( world_instance *world, GLuint shader )
+{
+   GLuint idx = glGetUniformBlockIndex( shader, "ub_world_lighting" );   
+   glUniformBlockBinding( shader, idx, world->ubo_bind_point );
+}
+
+VG_STATIC void world_bind_position_texture( world_instance *world, 
+                                            GLuint shader, GLuint location,
+                                            int slot )
+{
+   render_fb_bind_texture( &world->heightmap, 0, slot );
+   glUniform1i( location, slot );
+}
+
+VG_STATIC void world_bind_light_array( world_instance *world,
+                                       GLuint shader, GLuint location, 
+                                       int slot )
+{
+   glActiveTexture( GL_TEXTURE0 + slot );
+   glBindTexture( GL_TEXTURE_BUFFER, world->tex_light_entities );
+   glUniform1i( location, slot );
+}
+
+VG_STATIC void world_bind_light_index( world_instance *world,
+                                       GLuint shader, GLuint location,
+                                       int slot )
+{
+   glActiveTexture( GL_TEXTURE0 + slot );
+   glBindTexture( GL_TEXTURE_3D, world->tex_light_cubes );
+   glUniform1i( location, slot );
+}
+
+VG_STATIC void render_world_depth( world_instance *world, camera *cam );
+
+/*
+ * Rendering
+ */
+
+VG_STATIC void bind_terrain_noise(void)
+{
+   glActiveTexture( GL_TEXTURE0 );
+   glBindTexture( GL_TEXTURE_2D, world_render.tex_terrain_noise );
+}
+
+struct world_pass{
+   camera *cam;
+   enum mdl_shader shader;
+   enum world_geo_type geo_type;
+
+   void (*fn_bind_textures)( world_instance *world, 
+                             struct world_surface *mat );
+   void (*fn_set_mdl)( m4x3f mdl );
+   void (*fn_set_uPvmPrev)( m4x4f pvm );
+};
+
+VG_STATIC void world_render_if( world_instance *world, struct world_pass *pass )
+{
+   for( int i=0; i<world->surface_count; i++ ){
+      struct world_surface *mat = &world->surfaces[i];
+
+      if( mat->info.shader == pass->shader ){
+         mdl_submesh *sm;
+
+         if( pass->geo_type == k_world_geo_type_solid )
+            sm = &mat->sm_geo;
+         else
+            sm = &mat->sm_no_collide;
+
+         if( !sm->indice_count )
+            continue;
+
+         m4x3f mmdl;
+         m4x3_identity( mmdl );
+         pass->fn_set_mdl( mmdl );
+         pass->fn_set_uPvmPrev( pass->cam->mtx_prev.pv );
+
+         pass->fn_bind_textures( world, mat );
+         mdl_draw_submesh( sm );
+
+         for( u32 j=0; j<mdl_arrcount( &world->ent_traffic ); j++ ){
+            ent_traffic *traffic = mdl_arritm( &world->ent_traffic, j );
+            
+            for( u32 k=0; k<traffic->submesh_count; k++ ){
+               sm = mdl_arritm( &world->meta.submeshs, 
+                                 traffic->submesh_start+k );
+
+               q_m3x3( traffic->transform.q, mmdl );
+               v3_copy( traffic->transform.co, mmdl[3] );
+
+               m4x4f m4mdl;
+               m4x3_expand( mmdl, m4mdl );
+               m4x4_mul( pass->cam->mtx_prev.pv, m4mdl, m4mdl );
+
+               pass->fn_set_mdl( mmdl );
+               pass->fn_set_uPvmPrev( m4mdl );
+
+               mdl_draw_submesh( sm );
+            }
+         }
+      }
+   }
+}
+
+VG_STATIC 
+void world_render_both_stages( world_instance *world, struct world_pass *pass )
+{
+   mesh_bind( &world->mesh_geo );
+   pass->geo_type = k_world_geo_type_solid;
+   world_render_if( world, pass );
+
+   glDisable( GL_CULL_FACE );
+   mesh_bind( &world->mesh_no_collide );
+   pass->geo_type = k_world_geo_type_nonsolid;
+   world_render_if( world, pass );
+   glEnable( GL_CULL_FACE );
+}
+
+VG_STATIC void bindpoint_diffuse_texture1( world_instance *world,
+                                           struct world_surface *mat )
+                                         
+{
+   glActiveTexture( GL_TEXTURE1 );
+   glBindTexture( GL_TEXTURE_2D, world->textures[ mat->info.tex_diffuse ] );
+}
+
+VG_STATIC void render_world_vb( world_instance *world, camera *cam )
+{
+   shader_scene_vertex_blend_use();
+   shader_scene_vertex_blend_uTexGarbage(0);
+   shader_scene_vertex_blend_uTexGradients(1);
+   world_link_lighting_ub( world, _shader_scene_vertex_blend.id );
+   world_bind_position_texture( world, _shader_scene_vertex_blend.id, 
+                                _uniform_scene_vertex_blend_g_world_depth, 2 );
+   world_bind_light_array( world, _shader_scene_vertex_blend.id,
+                                _uniform_scene_vertex_blend_uLightsArray, 3 );
+   world_bind_light_index( world, _shader_scene_vertex_blend.id,
+                                _uniform_scene_vertex_blend_uLightsIndex, 4 );
+
+   glActiveTexture( GL_TEXTURE0 );
+   glBindTexture( GL_TEXTURE_2D, world_render.tex_terrain_noise );
+
+   shader_scene_vertex_blend_uPv( cam->mtx.pv );
+   shader_scene_vertex_blend_uCamera( cam->transform[3] );
+
+   struct world_pass pass = {
+      .shader = k_shader_standard_vertex_blend,
+      .cam = cam,
+      .fn_bind_textures = bindpoint_diffuse_texture1,
+      .fn_set_mdl = shader_scene_vertex_blend_uMdl,
+      .fn_set_uPvmPrev = shader_scene_vertex_blend_uPvmPrev,
+   };
+
+   world_render_both_stages( world, &pass );
+}
+
+VG_STATIC void render_world_standard( world_instance *world, camera *cam )
+{
+   shader_scene_standard_use();
+   shader_scene_standard_uTexGarbage(0);
+   shader_scene_standard_uTexMain(1);
+   shader_scene_standard_uPv( cam->mtx.pv );
+
+   world_link_lighting_ub( world, _shader_scene_standard.id );
+   world_bind_position_texture( world, _shader_scene_standard.id, 
+                                _uniform_scene_standard_g_world_depth, 2 );
+   world_bind_light_array( world, _shader_scene_standard.id,
+                                _uniform_scene_standard_uLightsArray, 3 );
+   world_bind_light_index( world, _shader_scene_standard.id,
+                                _uniform_scene_standard_uLightsIndex, 4 );
+
+   bind_terrain_noise();
+   shader_scene_standard_uCamera( cam->transform[3] );
+   
+   struct world_pass pass = {
+      .shader = k_shader_standard,
+      .cam = cam,
+      .fn_bind_textures = bindpoint_diffuse_texture1,
+      .fn_set_mdl = shader_scene_standard_uMdl,
+      .fn_set_uPvmPrev = shader_scene_standard_uPvmPrev,
+   };
+
+   world_render_both_stages( world, &pass );
+}
+
+VG_STATIC void render_world_alphatest( world_instance *world, camera *cam )
+{
+   shader_scene_standard_alphatest_use();
+   shader_scene_standard_alphatest_uTexGarbage(0);
+   shader_scene_standard_alphatest_uTexMain(1);
+   shader_scene_standard_alphatest_uPv( cam->mtx.pv );
+
+   world_link_lighting_ub( world, _shader_scene_standard_alphatest.id );
+   world_bind_position_texture( world, _shader_scene_standard_alphatest.id, 
+                        _uniform_scene_standard_alphatest_g_world_depth, 2 );
+   world_bind_light_array( world, _shader_scene_standard_alphatest.id,
+                        _uniform_scene_standard_alphatest_uLightsArray, 3 );
+   world_bind_light_index( world, _shader_scene_standard_alphatest.id,
+                           _uniform_scene_standard_alphatest_uLightsIndex, 4 );
+
+
+   bind_terrain_noise();
+
+   
+   shader_scene_standard_alphatest_uCamera( cam->transform[3] );
+
+   glDisable(GL_CULL_FACE);
+
+   struct world_pass pass = {
+      .shader = k_shader_standard_cutout,
+      .cam = cam,
+      .fn_bind_textures = bindpoint_diffuse_texture1,
+      .fn_set_mdl = shader_scene_standard_alphatest_uMdl,
+      .fn_set_uPvmPrev = shader_scene_standard_alphatest_uPvmPrev,
+   };
+
+   world_render_both_stages( world, &pass );
+
+   glEnable(GL_CULL_FACE);
+}
+
+VG_STATIC void bindpoint_terrain( world_instance *world,
+                                  struct world_surface *mat )
+{
+   glActiveTexture( GL_TEXTURE1 );
+   glBindTexture( GL_TEXTURE_2D, world->textures[ mat->info.tex_diffuse ] );
+
+   shader_scene_terrain_uSandColour( mat->info.colour );
+   shader_scene_terrain_uBlendOffset( mat->info.colour1 );
+}
+
+VG_STATIC void render_terrain( world_instance *world, camera *cam )
+{
+   shader_scene_terrain_use();
+   shader_scene_terrain_uTexGarbage(0);
+   shader_scene_terrain_uTexGradients(1);
+
+   world_link_lighting_ub( world, _shader_scene_terrain.id );
+   world_bind_position_texture( world, _shader_scene_terrain.id, 
+                        _uniform_scene_terrain_g_world_depth, 2 );
+   world_bind_light_array( world, _shader_scene_terrain.id,
+                        _uniform_scene_terrain_uLightsArray, 3 );
+   world_bind_light_index( world, _shader_scene_terrain.id,
+                           _uniform_scene_terrain_uLightsIndex, 4 );
+
+   glActiveTexture( GL_TEXTURE0 );
+   glBindTexture( GL_TEXTURE_2D, world_render.tex_terrain_noise );
+
+   shader_scene_terrain_uPv( cam->mtx.pv );
+   shader_scene_terrain_uCamera( cam->transform[3] );
+
+   struct world_pass pass = {
+      .shader = k_shader_terrain_blend,
+      .cam = cam,
+      .fn_bind_textures = bindpoint_terrain,
+      .fn_set_mdl = shader_scene_terrain_uMdl,
+      .fn_set_uPvmPrev = shader_scene_terrain_uPvmPrev,
+   };
+
+   world_render_both_stages( world, &pass );
+}
+
+VG_STATIC void render_sky( world_instance *world, camera *cam )
+{
+   /* 
+    * Modify matrix to remove clipping and view translation
+    */
+   m4x4f v,
+         v_prev,
+         pv,
+         pv_prev;
+
+   m4x4_copy( cam->mtx.v, v );
+   m4x4_copy( cam->mtx_prev.v, v_prev );
+   v3_zero( v[3] );
+   v3_zero( v_prev[3] );
+
+   m4x4_copy( cam->mtx.p,      pv );
+   m4x4_copy( cam->mtx_prev.p, pv_prev );
+   m4x4_reset_clipping( pv,      cam->farz, cam->nearz );
+   m4x4_reset_clipping( pv_prev, cam->farz, cam->nearz );
+
+   m4x4_mul( pv,      v,      pv );
+   m4x4_mul( pv_prev, v_prev, pv_prev );
+
+   m4x3f identity_matrix;
+   m4x3_identity( identity_matrix );
+   
+   /*
+    * Draw
+    */
+   shader_model_sky_use();
+   shader_model_sky_uMdl( identity_matrix );
+   shader_model_sky_uPv( pv );
+   shader_model_sky_uPvmPrev( pv_prev );
+   shader_model_sky_uTexGarbage(0);
+   world_link_lighting_ub( world, _shader_model_sky.id );
+
+   glActiveTexture( GL_TEXTURE0 );
+   glBindTexture( GL_TEXTURE_2D, world_render.tex_terrain_noise );
+
+   glDepthMask( GL_FALSE );
+   glDisable( GL_DEPTH_TEST );
+
+   mesh_bind( &world_render.skydome );
+   mesh_draw( &world_render.skydome );
+   
+   glEnable( GL_DEPTH_TEST );
+   glDepthMask( GL_TRUE );
+}
+
+VG_STATIC void render_world_gates( world_instance *world, camera *cam,
+                                   int layer_depth )
+{
+   float closest = INFINITY;
+   struct ent_gate *gate = NULL;
+
+   for( u32 i=0; i<mdl_arrcount(&world->ent_gate); i++ ){
+      ent_gate *gi = mdl_arritm( &world->ent_gate, i );
+
+      if( gi->type == k_gate_type_unlinked )
+         continue;
+
+      float dist = v3_dist2( gi->co[0], cam->transform[3] );
+
+      vg_line_pt3( gi->co[0], 0.25f, VG__BLUE );
+
+      if( dist < closest ){
+         closest = dist;
+         gate = gi;
+      }
+   }
+   
+   if( gate ){
+      /* should really be set in fixed update since its used in the physics
+       * of most systems. too bad! */
+      world->rendering_gate = gate;
+
+      if( gate->type == k_gate_type_teleport ){
+         render_gate( world, gate, cam, layer_depth );
+      }
+      else if( gate->type == k_gate_type_nonlocel ){
+         if( skaterift.async_op != k_async_op_world_loading &&
+             skaterift.async_op != k_async_op_world_preloading ){
+            world_instance *dest_world = &world_static.worlds[ gate->target ];
+            render_gate( dest_world, gate, cam, layer_depth );
+         }
+      }
+      else
+         world->rendering_gate = NULL;
+   }
+}
+
+VG_STATIC void world_prerender( world_instance *world )
+{
+   world->time += vg.time_delta * (1.0/(k_day_length*60.0));
+
+   struct ub_world_lighting *state = &world->ub_lighting;
+
+   state->g_time = world->time;
+   state->g_realtime = vg.time;
+   state->g_debug_indices = k_debug_light_indices;
+   state->g_light_preview = k_light_preview;
+   state->g_debug_complexity = k_debug_light_complexity;
+   state->g_time_of_day = vg_fractf( world->time );
+   state->g_day_phase   = cosf( state->g_time_of_day * VG_PIf * 2.0f );
+   state->g_sunset_phase= cosf( state->g_time_of_day * VG_PIf * 4.0f + VG_PIf );
+
+   state->g_day_phase    =       state->g_day_phase    * 0.5f + 0.5f;
+   state->g_sunset_phase = powf( state->g_sunset_phase * 0.5f + 0.5f, 6.0f );
+
+   float a = state->g_time_of_day * VG_PIf * 2.0f;
+   state->g_sun_dir[0] = sinf( a );
+   state->g_sun_dir[1] = cosf( a );
+   state->g_sun_dir[2] = 0.2f;
+   v3_normalize( state->g_sun_dir );
+
+   world->probabilities[ k_probability_curve_constant ] = 1.0f;
+   float dp = state->g_day_phase;
+
+   world->probabilities[ k_probability_curve_wildlife_day ] =
+      (dp*dp*0.8f+state->g_sunset_phase)*0.8f;
+   world->probabilities[ k_probability_curve_wildlife_night ] = 
+      1.0f-powf(fabsf((state->g_time_of_day-0.5f)*5.0f),5.0f);
+      
+   glBindBuffer( GL_UNIFORM_BUFFER, world->ubo_lighting );
+   glBufferSubData( GL_UNIFORM_BUFFER, 0, 
+                    sizeof(struct ub_world_lighting), &world->ub_lighting );
+}
+
+VG_STATIC void skateshop_render(void);
+VG_STATIC void render_world( world_instance *world, camera *cam,
+                             int layer_depth )
+{
+   render_sky( world, cam );
+
+   render_world_routes( world, cam, layer_depth );
+   render_world_standard( world, cam );
+   render_world_vb( world, cam );
+   render_world_alphatest( world, cam );
+   render_terrain( world, cam );
+
+   if( layer_depth == 0 ){
+      skateshop_render();
+
+      /* Render SFD's */
+      u32 closest = 0;
+      float min_dist = INFINITY;
+
+      if( !mdl_arrcount( &world->ent_route ) )
+         return;
+
+      for( u32 i=0; i<mdl_arrcount( &world->ent_route ); i++ ){
+         ent_route *route = mdl_arritm( &world->ent_route, i );
+         float dist = v3_dist2( route->board_transform[3], cam->pos );
+
+         if( dist < min_dist ){
+            min_dist = dist;
+            closest = i;
+         }
+      }
+
+      ent_route *route = mdl_arritm( &world->ent_route, closest );
+      sfd_render( world, cam, route->board_transform );
+   }
+}
+
+VG_STATIC void render_world_depth( world_instance *world, camera *cam )
+{
+   m4x3f identity_matrix;
+   m4x3_identity( identity_matrix );
+
+   shader_scene_depth_use();
+   shader_scene_depth_uCamera( cam->transform[3] );
+   shader_scene_depth_uPv( cam->mtx.pv );
+   shader_scene_depth_uPvmPrev( cam->mtx_prev.pv );
+   shader_scene_depth_uMdl( identity_matrix );
+   world_link_lighting_ub( world, _shader_scene_depth.id );
+
+   mesh_bind( &world->mesh_geo );
+   mesh_draw( &world->mesh_geo );
+}
+
+VG_STATIC void render_world_position( world_instance *world, camera *cam )
+{
+   m4x3f identity_matrix;
+   m4x3_identity( identity_matrix );
+
+   shader_scene_position_use();
+   shader_scene_position_uCamera( cam->transform[3] );
+   shader_scene_position_uPv( cam->mtx.pv );
+   shader_scene_position_uPvmPrev( cam->mtx_prev.pv );
+   shader_scene_position_uMdl( identity_matrix );
+   world_link_lighting_ub( world, _shader_scene_position.id );
+
+   mesh_bind( &world->mesh_geo );
+   mesh_draw( &world->mesh_geo );
+}
+
+#endif
index 10e416325b306529daa4ec9d0f6f2faa369098b2..3590cf7480d7215131dc13ef5b073d27e30fce21 100644 (file)
 #include "camera.h"
 #include "world.h"
 
-VG_STATIC GLuint tex_terrain_noise;
+#include "shaders/scene_standard.h"
+#include "shaders/scene_standard_alphatest.h"
+#include "shaders/scene_vertex_blend.h"
+#include "shaders/scene_terrain.h"
+#include "shaders/scene_depth.h"
+#include "shaders/scene_position.h"
+#include "shaders/model_sky.h"
 
-VG_STATIC void async_world_render_init( void *payload, u32 size )
-{
-   vg_info( "Allocate uniform buffers\n" );
-   for( int i=0; i<4; i++ ){
-      world_instance *world = &world_global.worlds[i];
-      world->ubo_bind_point = i;
+static const float k_world_light_cube_size = 8.0f;
 
-      glGenBuffers( 1, &world->ubo_lighting );
-      glBindBuffer( GL_UNIFORM_BUFFER, world->ubo_lighting );
-      glBufferData( GL_UNIFORM_BUFFER, sizeof(struct ub_world_lighting), 
-                    NULL, GL_DYNAMIC_DRAW );
+struct world_render{
+   GLuint tex_terrain_noise;
 
-      glBindBufferBase( GL_UNIFORM_BUFFER, i, world->ubo_lighting );
-      VG_CHECK_GL_ERR();
-   }
-
-   vg_info( "Allocate frame buffers\n" );
-   for( int i=0; i<4; i++ ){
-      world_instance *world = &world_global.worlds[i];
-      struct framebuffer *fb = &world->heightmap;
-
-      fb->display_name = NULL;
-      fb->link = NULL;
-      fb->fixed_w = 1024;
-      fb->fixed_h = 1024;
-      fb->resolution_div = 0;
-
-      fb->attachments[0].display_name     = NULL;
-      fb->attachments[0].purpose = k_framebuffer_attachment_type_texture;
-      fb->attachments[0].internalformat   = GL_RG16F;
-      fb->attachments[0].format           = GL_RG;
-      fb->attachments[0].type             = GL_FLOAT;
-      fb->attachments[0].attachment       = GL_COLOR_ATTACHMENT0;
+   /* rendering */
+   glmesh skydome;
 
-      fb->attachments[1].purpose = k_framebuffer_attachment_type_none;
-      fb->attachments[2].purpose = k_framebuffer_attachment_type_none;
-      fb->attachments[3].purpose = k_framebuffer_attachment_type_none;
-      fb->attachments[4].purpose = k_framebuffer_attachment_type_none;
+   double sky_time, sky_rate, sky_target_rate;
 
-      render_fb_allocate( fb );
+   /* water rendering */
+   struct{
+      struct framebuffer fbreflect, fbdepth;
    }
-}
-
-VG_STATIC void world_render_init(void)
-{
-   vg_info( "Loading default world textures\n" );
+   water;
+
+   v3f render_gate_pos;
+   struct timer_text{
+      char text[8];
+      m4x3f transform;
+      ent_gate *gate;
+      ent_route *route;
+   }
+   timer_texts[4];
+   u32 timer_text_count;
 
-   vg_tex2d_load_qoi_async_file( "textures/garbage.qoi", 
-                                 VG_TEX2D_NEAREST|VG_TEX2D_REPEAT, 
-                                 &tex_terrain_noise );
+   struct text_particle{
+      rb_object obj;
+      m4x3f mlocal;
+      ent_glyph *glyph;
+      v4f colour;
 
-   vg_async_call( async_world_render_init, NULL, 0 );
-}
-
-VG_STATIC void world_link_lighting_ub( world_instance *world, GLuint shader )
-{
-   GLuint idx = glGetUniformBlockIndex( shader, "ub_world_lighting" );   
-   glUniformBlockBinding( shader, idx, world->ubo_bind_point );
+      m4x3f mdl;
+   }
+   text_particles[6*4];
+   u32 text_particle_count;
 }
+static world_render;
+VG_STATIC void world_render_init(void);
 
+VG_STATIC void world_link_lighting_ub( world_instance *world, GLuint shader );
 VG_STATIC void world_bind_position_texture( world_instance *world, 
                                             GLuint shader, GLuint location,
-                                            int slot )
-{
-   render_fb_bind_texture( &world->heightmap, 0, slot );
-   glUniform1i( location, slot );
-}
-
+                                            int slot );
 VG_STATIC void world_bind_light_array( world_instance *world,
                                        GLuint shader, GLuint location, 
-                                       int slot )
-{
-   glActiveTexture( GL_TEXTURE0 + slot );
-   glBindTexture( GL_TEXTURE_BUFFER, world->tex_light_entities );
-   glUniform1i( location, slot );
-}
-
+                                       int slot );
 VG_STATIC void world_bind_light_index( world_instance *world,
                                        GLuint shader, GLuint location,
-                                       int slot )
-{
-   glActiveTexture( GL_TEXTURE0 + slot );
-   glBindTexture( GL_TEXTURE_3D, world->tex_light_cubes );
-   glUniform1i( location, slot );
-}
-
+                                       int slot );
+VG_STATIC void render_world_position( world_instance *world, camera *cam );
 VG_STATIC void render_world_depth( world_instance *world, camera *cam );
-
-/*
- * Rendering
- */
-
-VG_STATIC void bind_terrain_noise(void)
-{
-   glActiveTexture( GL_TEXTURE0 );
-   glBindTexture( GL_TEXTURE_2D, tex_terrain_noise );
-}
-
-struct world_pass{
-   camera *cam;
-   enum mdl_shader shader;
-   enum geo_type geo_type;
-
-   void (*fn_bind_textures)( world_instance *world, 
-                             struct world_surface *mat );
-   void (*fn_set_mdl)( m4x3f mdl );
-   void (*fn_set_uPvmPrev)( m4x4f pvm );
-};
-
-VG_STATIC void world_render_if( world_instance *world, struct world_pass *pass )
-{
-   for( int i=0; i<world->surface_count; i++ ){
-      struct world_surface *mat = &world->surfaces[i];
-
-      if( mat->info.shader == pass->shader ){
-         mdl_submesh *sm;
-
-         if( pass->geo_type == k_geo_type_solid )
-            sm = &mat->sm_geo;
-         else
-            sm = &mat->sm_no_collide;
-
-         if( !sm->indice_count )
-            continue;
-
-         m4x3f mmdl;
-         m4x3_identity( mmdl );
-         pass->fn_set_mdl( mmdl );
-         pass->fn_set_uPvmPrev( pass->cam->mtx_prev.pv );
-
-         pass->fn_bind_textures( world, mat );
-         mdl_draw_submesh( sm );
-
-         for( u32 j=0; j<mdl_arrcount( &world->ent_traffic ); j++ ){
-            ent_traffic *traffic = mdl_arritm( &world->ent_traffic, j );
-            
-            for( u32 k=0; k<traffic->submesh_count; k++ ){
-               sm = mdl_arritm( &world->meta.submeshs, 
-                                 traffic->submesh_start+k );
-
-               q_m3x3( traffic->transform.q, mmdl );
-               v3_copy( traffic->transform.co, mmdl[3] );
-
-               m4x4f m4mdl;
-               m4x3_expand( mmdl, m4mdl );
-               m4x4_mul( pass->cam->mtx_prev.pv, m4mdl, m4mdl );
-
-               pass->fn_set_mdl( mmdl );
-               pass->fn_set_uPvmPrev( m4mdl );
-
-               mdl_draw_submesh( sm );
-            }
-         }
-      }
-   }
-}
-
-VG_STATIC 
-void world_render_both_stages( world_instance *world, struct world_pass *pass )
-{
-   mesh_bind( &world->mesh_geo );
-   pass->geo_type = k_geo_type_solid;
-   world_render_if( world, pass );
-
-   glDisable( GL_CULL_FACE );
-   mesh_bind( &world->mesh_no_collide );
-   pass->geo_type = k_geo_type_nonsolid;
-   world_render_if( world, pass );
-   glEnable( GL_CULL_FACE );
-}
-
-VG_STATIC void bindpoint_diffuse_texture1( world_instance *world,
-                                           struct world_surface *mat )
-                                         
-{
-   glActiveTexture( GL_TEXTURE1 );
-   glBindTexture( GL_TEXTURE_2D, world->textures[ mat->info.tex_diffuse ] );
-}
-
-VG_STATIC void render_world_vb( world_instance *world, camera *cam )
-{
-   shader_scene_vertex_blend_use();
-   shader_scene_vertex_blend_uTexGarbage(0);
-   shader_scene_vertex_blend_uTexGradients(1);
-   world_link_lighting_ub( world, _shader_scene_vertex_blend.id );
-   world_bind_position_texture( world, _shader_scene_vertex_blend.id, 
-                                _uniform_scene_vertex_blend_g_world_depth, 2 );
-   world_bind_light_array( world, _shader_scene_vertex_blend.id,
-                                _uniform_scene_vertex_blend_uLightsArray, 3 );
-   world_bind_light_index( world, _shader_scene_vertex_blend.id,
-                                _uniform_scene_vertex_blend_uLightsIndex, 4 );
-
-   glActiveTexture( GL_TEXTURE0 );
-   glBindTexture( GL_TEXTURE_2D, tex_terrain_noise );
-
-   shader_scene_vertex_blend_uPv( cam->mtx.pv );
-   shader_scene_vertex_blend_uCamera( cam->transform[3] );
-
-   struct world_pass pass = {
-      .shader = k_shader_standard_vertex_blend,
-      .cam = cam,
-      .fn_bind_textures = bindpoint_diffuse_texture1,
-      .fn_set_mdl = shader_scene_vertex_blend_uMdl,
-      .fn_set_uPvmPrev = shader_scene_vertex_blend_uPvmPrev,
-   };
-
-   world_render_both_stages( world, &pass );
-}
-
-VG_STATIC void render_world_standard( world_instance *world, camera *cam )
-{
-   shader_scene_standard_use();
-   shader_scene_standard_uTexGarbage(0);
-   shader_scene_standard_uTexMain(1);
-   shader_scene_standard_uPv( cam->mtx.pv );
-
-   world_link_lighting_ub( world, _shader_scene_standard.id );
-   world_bind_position_texture( world, _shader_scene_standard.id, 
-                                _uniform_scene_standard_g_world_depth, 2 );
-   world_bind_light_array( world, _shader_scene_standard.id,
-                                _uniform_scene_standard_uLightsArray, 3 );
-   world_bind_light_index( world, _shader_scene_standard.id,
-                                _uniform_scene_standard_uLightsIndex, 4 );
-
-   bind_terrain_noise();
-   shader_scene_standard_uCamera( cam->transform[3] );
-   
-   struct world_pass pass = {
-      .shader = k_shader_standard,
-      .cam = cam,
-      .fn_bind_textures = bindpoint_diffuse_texture1,
-      .fn_set_mdl = shader_scene_standard_uMdl,
-      .fn_set_uPvmPrev = shader_scene_standard_uPvmPrev,
-   };
-
-   world_render_both_stages( world, &pass );
-}
-
-VG_STATIC void render_world_alphatest( world_instance *world, camera *cam )
-{
-   shader_scene_standard_alphatest_use();
-   shader_scene_standard_alphatest_uTexGarbage(0);
-   shader_scene_standard_alphatest_uTexMain(1);
-   shader_scene_standard_alphatest_uPv( cam->mtx.pv );
-
-   world_link_lighting_ub( world, _shader_scene_standard_alphatest.id );
-   world_bind_position_texture( world, _shader_scene_standard_alphatest.id, 
-                        _uniform_scene_standard_alphatest_g_world_depth, 2 );
-   world_bind_light_array( world, _shader_scene_standard_alphatest.id,
-                        _uniform_scene_standard_alphatest_uLightsArray, 3 );
-   world_bind_light_index( world, _shader_scene_standard_alphatest.id,
-                           _uniform_scene_standard_alphatest_uLightsIndex, 4 );
-
-
-   bind_terrain_noise();
-
-   
-   shader_scene_standard_alphatest_uCamera( cam->transform[3] );
-
-   glDisable(GL_CULL_FACE);
-
-   struct world_pass pass = {
-      .shader = k_shader_standard_cutout,
-      .cam = cam,
-      .fn_bind_textures = bindpoint_diffuse_texture1,
-      .fn_set_mdl = shader_scene_standard_alphatest_uMdl,
-      .fn_set_uPvmPrev = shader_scene_standard_alphatest_uPvmPrev,
-   };
-
-   world_render_both_stages( world, &pass );
-
-   glEnable(GL_CULL_FACE);
-}
-
-VG_STATIC void bindpoint_terrain( world_instance *world,
-                                  struct world_surface *mat )
-{
-   glActiveTexture( GL_TEXTURE1 );
-   glBindTexture( GL_TEXTURE_2D, world->textures[ mat->info.tex_diffuse ] );
-
-   shader_scene_terrain_uSandColour( mat->info.colour );
-   shader_scene_terrain_uBlendOffset( mat->info.colour1 );
-}
-
-VG_STATIC void render_terrain( world_instance *world, camera *cam )
-{
-   shader_scene_terrain_use();
-   shader_scene_terrain_uTexGarbage(0);
-   shader_scene_terrain_uTexGradients(1);
-
-   world_link_lighting_ub( world, _shader_scene_terrain.id );
-   world_bind_position_texture( world, _shader_scene_terrain.id, 
-                        _uniform_scene_terrain_g_world_depth, 2 );
-   world_bind_light_array( world, _shader_scene_terrain.id,
-                        _uniform_scene_terrain_uLightsArray, 3 );
-   world_bind_light_index( world, _shader_scene_terrain.id,
-                           _uniform_scene_terrain_uLightsIndex, 4 );
-
-   glActiveTexture( GL_TEXTURE0 );
-   glBindTexture( GL_TEXTURE_2D, tex_terrain_noise );
-
-   shader_scene_terrain_uPv( cam->mtx.pv );
-   shader_scene_terrain_uCamera( cam->transform[3] );
-
-   struct world_pass pass = {
-      .shader = k_shader_terrain_blend,
-      .cam = cam,
-      .fn_bind_textures = bindpoint_terrain,
-      .fn_set_mdl = shader_scene_terrain_uMdl,
-      .fn_set_uPvmPrev = shader_scene_terrain_uPvmPrev,
-   };
-
-   world_render_both_stages( world, &pass );
-}
-
-VG_STATIC void render_sky( world_instance *world, camera *cam )
-{
-   /* 
-    * Modify matrix to remove clipping and view translation
-    */
-   m4x4f v,
-         v_prev,
-         pv,
-         pv_prev;
-
-   m4x4_copy( cam->mtx.v, v );
-   m4x4_copy( cam->mtx_prev.v, v_prev );
-   v3_zero( v[3] );
-   v3_zero( v_prev[3] );
-
-   m4x4_copy( cam->mtx.p,      pv );
-   m4x4_copy( cam->mtx_prev.p, pv_prev );
-   m4x4_reset_clipping( pv,      cam->farz, cam->nearz );
-   m4x4_reset_clipping( pv_prev, cam->farz, cam->nearz );
-
-   m4x4_mul( pv,      v,      pv );
-   m4x4_mul( pv_prev, v_prev, pv_prev );
-
-   m4x3f identity_matrix;
-   m4x3_identity( identity_matrix );
-   
-   /*
-    * Draw
-    */
-   shader_model_sky_use();
-   shader_model_sky_uMdl( identity_matrix );
-   shader_model_sky_uPv( pv );
-   shader_model_sky_uPvmPrev( pv_prev );
-   shader_model_sky_uTexGarbage(0);
-   world_link_lighting_ub( world, _shader_model_sky.id );
-
-   glActiveTexture( GL_TEXTURE0 );
-   glBindTexture( GL_TEXTURE_2D, tex_terrain_noise );
-
-   glDepthMask( GL_FALSE );
-   glDisable( GL_DEPTH_TEST );
-
-   mesh_bind( &world_global.skydome );
-   mesh_draw( &world_global.skydome );
-   
-   glEnable( GL_DEPTH_TEST );
-   glDepthMask( GL_TRUE );
-}
-
-VG_STATIC void render_world_gates( world_instance *world, camera *cam,
-                                   int layer_depth )
-{
-   float closest = INFINITY;
-   struct ent_gate *gate = NULL;
-
-   for( u32 i=0; i<mdl_arrcount(&world->ent_gate); i++ ){
-      ent_gate *gi = mdl_arritm( &world->ent_gate, i );
-
-      if( gi->type == k_gate_type_unlinked )
-         continue;
-
-      float dist = v3_dist2( gi->co[0], cam->transform[3] );
-
-      vg_line_pt3( gi->co[0], 0.25f, VG__BLUE );
-
-      if( dist < closest ){
-         closest = dist;
-         gate = gi;
-      }
-   }
-   
-   if( gate ){
-      /* should really be set in fixed update since its used in the physics
-       * of most systems. too bad! */
-      world->rendering_gate = gate;
-
-      if( gate->type == k_gate_type_teleport ){
-         render_gate( world, gate, cam, layer_depth );
-      }
-      else if( gate->type == k_gate_type_nonlocel ){
-         if( skaterift.async_op != k_async_op_world_loading &&
-             skaterift.async_op != k_async_op_world_preloading ){
-            world_instance *dest_world = &world_global.worlds[ gate->target ];
-            render_gate( dest_world, gate, cam, layer_depth );
-         }
-      }
-      else
-         world->rendering_gate = NULL;
-   }
-}
-
-VG_STATIC void world_prerender( world_instance *world )
-{
-   world->time += vg.time_delta * (1.0/(k_day_length*60.0));
-
-   struct ub_world_lighting *state = &world->ub_lighting;
-
-   state->g_time = world->time;
-   state->g_realtime = vg.time;
-   state->g_debug_indices = k_debug_light_indices;
-   state->g_light_preview = k_light_preview;
-   state->g_debug_complexity = k_debug_light_complexity;
-   state->g_time_of_day = vg_fractf( world->time );
-   state->g_day_phase   = cosf( state->g_time_of_day * VG_PIf * 2.0f );
-   state->g_sunset_phase= cosf( state->g_time_of_day * VG_PIf * 4.0f + VG_PIf );
-
-   state->g_day_phase    =       state->g_day_phase    * 0.5f + 0.5f;
-   state->g_sunset_phase = powf( state->g_sunset_phase * 0.5f + 0.5f, 6.0f );
-
-   float a = state->g_time_of_day * VG_PIf * 2.0f;
-   state->g_sun_dir[0] = sinf( a );
-   state->g_sun_dir[1] = cosf( a );
-   state->g_sun_dir[2] = 0.2f;
-   v3_normalize( state->g_sun_dir );
-
-   world->probabilities[ k_probability_curve_constant ] = 1.0f;
-   float dp = state->g_day_phase;
-
-   world->probabilities[ k_probability_curve_wildlife_day ] =
-      (dp*dp*0.8f+state->g_sunset_phase)*0.8f;
-   world->probabilities[ k_probability_curve_wildlife_night ] = 
-      1.0f-powf(fabsf((state->g_time_of_day-0.5f)*5.0f),5.0f);
-      
-   glBindBuffer( GL_UNIFORM_BUFFER, world->ubo_lighting );
-   glBufferSubData( GL_UNIFORM_BUFFER, 0, 
-                    sizeof(struct ub_world_lighting), &world->ub_lighting );
-}
-
-VG_STATIC void skateshop_render(void);
 VG_STATIC void render_world( world_instance *world, camera *cam,
-                             int layer_depth )
-{
-   render_sky( world, cam );
-
-   render_world_routes( world, cam, layer_depth );
-   render_world_standard( world, cam );
-   render_world_vb( world, cam );
-   render_world_alphatest( world, cam );
-   render_terrain( world, cam );
-
-   if( layer_depth == 0 ){
-      skateshop_render();
-
-      /* Render SFD's */
-      u32 closest = 0;
-      float min_dist = INFINITY;
-
-      if( !mdl_arrcount( &world->ent_route ) )
-         return;
-
-      for( u32 i=0; i<mdl_arrcount( &world->ent_route ); i++ ){
-         ent_route *route = mdl_arritm( &world->ent_route, i );
-         float dist = v3_dist2( route->board_transform[3], cam->pos );
-
-         if( dist < min_dist ){
-            min_dist = dist;
-            closest = i;
-         }
-      }
-
-      ent_route *route = mdl_arritm( &world->ent_route, closest );
-      sfd_render( world, cam, route->board_transform );
-   }
-}
-
-VG_STATIC void render_world_depth( world_instance *world, camera *cam )
-{
-   m4x3f identity_matrix;
-   m4x3_identity( identity_matrix );
-
-   shader_scene_depth_use();
-   shader_scene_depth_uCamera( cam->transform[3] );
-   shader_scene_depth_uPv( cam->mtx.pv );
-   shader_scene_depth_uPvmPrev( cam->mtx_prev.pv );
-   shader_scene_depth_uMdl( identity_matrix );
-   world_link_lighting_ub( world, _shader_scene_depth.id );
-
-   mesh_bind( &world->mesh_geo );
-   mesh_draw( &world->mesh_geo );
-}
-
-VG_STATIC void render_world_position( world_instance *world, camera *cam )
-{
-   m4x3f identity_matrix;
-   m4x3_identity( identity_matrix );
-
-   shader_scene_position_use();
-   shader_scene_position_uCamera( cam->transform[3] );
-   shader_scene_position_uPv( cam->mtx.pv );
-   shader_scene_position_uPvmPrev( cam->mtx_prev.pv );
-   shader_scene_position_uMdl( identity_matrix );
-   world_link_lighting_ub( world, _shader_scene_position.id );
-
-   mesh_bind( &world->mesh_geo );
-   mesh_draw( &world->mesh_geo );
-}
+                             int layer_depth );
 
 #endif /* WORLD_RENDER_H */
diff --git a/world_routes.c b/world_routes.c
new file mode 100644 (file)
index 0000000..a66f93f
--- /dev/null
@@ -0,0 +1,1261 @@
+/*
+ * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#ifndef ROUTES_C
+#define ROUTES_C
+
+#include <time.h>
+#include "world_routes.h"
+#include "world_gate.h"
+#include "world_load.h"
+#include "highscores.h"
+
+#include "font.h"
+#include "pointcloud.h"
+#include "gui.h"
+
+#include "shaders/scene_route.h"
+#include "shaders/routeui.h"
+
+
+VG_STATIC 
+void world_routes_local_set_record( world_instance *world, ent_route *route,
+                                    double lap_time )
+{
+   vg_success( "  NEW LAP TIME: %f\n", lap_time );
+
+   if( route->official_track_id != 0xffffffff ){
+      double time_centiseconds = lap_time * 100.0;
+      if( time_centiseconds > (float)0xfffe )   /* skill issue */
+         return;
+
+      highscore_record temp;
+      temp.trackid  = route->official_track_id;
+      temp.datetime = time(NULL);
+      temp.playerid = 0;
+      temp.points   = 0;
+      temp.time     = time_centiseconds;
+
+#if 0
+      highscores_push_record( &temp );
+#endif
+
+      struct track_info *ti = &track_infos[ route->official_track_id ];
+      ti->push = 1;
+      
+      if( ti->achievement_id ){
+#if 0
+         steam_set_achievement( ti->achievement_id );
+         steam_store_achievements();
+#endif
+      }
+   }
+   else{
+      vg_warn( "There is no associated track for this record...\n" );
+   }
+}
+
+
+VG_STATIC void world_routes_clear( world_instance *world )
+{
+   for( u32 i=0; i<mdl_arrcount( &world->ent_route ); i++ ){
+      ent_route *route = mdl_arritm( &world->ent_route, i );
+      route->active_checkpoint = 0xffff;
+   }
+
+   for( u32 i=0; i<mdl_arrcount( &world->ent_gate ); i++ ){
+      ent_gate *rg = mdl_arritm( &world->ent_gate, i );
+      rg->timing_version = 0;
+      rg->timing_time = 0.0;
+   }
+
+   world_static.current_run_version += 4;
+   world_static.last_use = 0.0;
+}
+
+VG_STATIC void world_routes_time_lap( world_instance *world, ent_route *route )
+{
+   vg_info( "------- time lap %s -------\n", 
+            mdl_pstr(&world->meta,route->pstr_name) );
+
+   double start_time = 0.0;
+   u32 last_version=0;
+
+   u32 valid_count=0;
+
+   for( u32 i=0; i<route->checkpoints_count; i++ ){
+      u32 cpid  = (i+route->active_checkpoint) % route->checkpoints_count;
+          cpid += route->checkpoints_start;
+
+      ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint, cpid );
+      ent_gate *rg = mdl_arritm( &world->ent_gate, cp->gate_index );
+                rg = mdl_arritm( &world->ent_gate, rg->target );
+
+      if( i == 1 ){
+         route->timing_base = rg->timing_time;
+      }
+
+      if( i == 0 )
+         start_time = rg->timing_time;
+      else{
+         if( last_version+1 == rg->timing_version ) valid_count ++;
+         else valid_count = 0;
+      }
+
+      last_version = rg->timing_version;
+      vg_info( "%u %f\n", rg->timing_version, rg->timing_time );
+   }
+
+   if( world_static.current_run_version == last_version+1 ){
+      valid_count ++;
+
+      if( route->checkpoints_count == 1 ){
+         route->timing_base = world_static.time;
+      }
+   }
+   else valid_count = 0;
+
+   vg_info( "%u %f\n", world_static.current_run_version, world_static.time );
+
+   if( valid_count==route->checkpoints_count ){
+      double lap_time = world_static.time - start_time;
+      world_routes_local_set_record( world, route, lap_time );
+   }
+
+   route->valid_checkpoints = valid_count+1;
+
+   vg_info( "valid: %u\n", valid_count );
+   vg_info( "----------------------------\n" );
+}
+
+/*
+ * When going through a gate this is called for bookkeeping purposes
+ */
+VG_STATIC void world_routes_activate_entry_gate( world_instance *world, 
+                                                 ent_gate *rg )
+{
+   world_static.last_use = world_static.time;
+
+   /* disable all routes and leave the world */
+   if( rg->type == k_gate_type_nonlocel ){
+      for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
+         ent_route *route = mdl_arritm( &world->ent_route, i );
+         route->active_checkpoint = 0xffff;
+      }
+      return;
+   }
+
+   ent_gate *dest = mdl_arritm( &world->ent_gate, rg->target );
+
+   for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
+      ent_route *route = mdl_arritm( &world->ent_route, i );
+
+      u32 active_prev = route->active_checkpoint;
+      route->active_checkpoint = 0xffff;
+
+      for( u32 j=0; j<4; j++ ){
+         if( dest->routes[j] == i ){
+            for( u32 k=0; k<route->checkpoints_count; k++ ){
+               ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint, 
+                                                 route->checkpoints_start+k );
+
+               ent_gate *gk = mdl_arritm( &world->ent_gate, cp->gate_index );
+                         gk = mdl_arritm( &world->ent_gate, gk->target );
+               if( gk == dest ){
+                  route->active_checkpoint = k;
+                  world_routes_time_lap( world, route );
+                  break;
+               }
+            }
+            break;
+         }
+      }
+   }
+
+   dest->timing_version = world_static.current_run_version;
+   dest->timing_time = world_static.time;
+
+   world_static.current_run_version ++;
+}
+
+/* draw lines along the paths */
+VG_STATIC void world_routes_debug( world_instance *world )
+{
+   for( u32 i=0; i<mdl_arrcount(&world->ent_route_node); i++ ){
+      ent_route_node *rn = mdl_arritm(&world->ent_route_node,i);
+      vg_line_pt3( rn->co, 0.25f, VG__WHITE );
+   }
+
+   for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
+      ent_route *route = mdl_arritm(&world->ent_route, i);
+
+      u32 colours[] = { 0xfff58142, 0xff42cbf5, 0xff42f56c, 0xfff542b3,
+                        0xff5442f5 };
+
+      u32 cc = 0xffcccccc;
+      if( route->active_checkpoint != 0xffff ){
+         cc = colours[i%vg_list_size(colours)];
+      }
+
+      for( int i=0; i<route->checkpoints_count; i++ ){
+         int i0 = route->checkpoints_start+i,
+             i1 = route->checkpoints_start+((i+1)%route->checkpoints_count);
+
+         ent_checkpoint *c0 = mdl_arritm(&world->ent_checkpoint, i0),
+                        *c1 = mdl_arritm(&world->ent_checkpoint, i1);
+
+         ent_gate *start_gate = mdl_arritm( &world->ent_gate, c0->gate_index );
+         ent_gate *end_gate = mdl_arritm( &world->ent_gate, c1->gate_index );
+
+         v3f p0, p1;
+         v3_copy( start_gate->co[1], p0 );
+
+         for( int j=0; j<c0->path_count; j ++ ){
+            ent_path_index *index = mdl_arritm( &world->ent_path_index, 
+                                                c0->path_start+j );
+
+            ent_route_node *rn = mdl_arritm( &world->ent_route_node,
+                                             index->index );
+
+            v3_copy( rn->co, p1 );
+            vg_line( p0, p1, cc );
+            v3_copy( p1, p0 );
+         }
+
+         v3_copy( end_gate->co[0], p1 );
+         vg_line( p0, p1, cc );
+      }
+   }
+}
+
+VG_STATIC 
+void world_routes_pointcloud_spot( world_instance *world, 
+                                   pointcloud_buffer *pcbuf, 
+                                   v3f co, f32 radius, u32 samples, v4f colour )
+{
+   v3f inv_ext;
+   v3_sub( pcbuf->boundary[1], pcbuf->boundary[0], inv_ext );
+   v3_div( (v3f){1.0f,1.0f,1.0f}, inv_ext, inv_ext );
+
+   for( u32 j=0; j<samples; j++ ){
+      if( pcbuf->count >= pcbuf->max )
+         return;
+
+      pointcloud_vert *vert = &pcbuf->buf[ pcbuf->count ++ ];
+
+      v3f sample, jitter, point;
+      vg_rand_sphere( jitter );
+      v3_muladds( co, jitter, radius, sample );
+
+      if( bh_closest_point( world->geo_bh, sample, point, radius*1.5f ) == -1 ){
+         v3_copy( sample, point );
+      }
+
+      v3f pos;
+      v3_sub( point, pcbuf->boundary[0], pos );
+      v3_mul( pos, inv_ext, pos );
+
+      for( u32 i=0; i<3; i++ ){
+         vert->pos[i] = (pos[i]-0.5f) * 32767.0f;
+      }
+
+      float dist = 1.0f-(v3_length(jitter));
+
+      for( u32 i=0; i<4; i++ ){
+         vert->colour[i] = colour[i] * 255.0f * dist*dist;
+      }
+   }
+}
+
+VG_STATIC 
+void world_routes_place_curve( world_instance *world, ent_route *route,
+                               v4f h[3], v3f n0, v3f n2, scene_context *scene,
+                               pointcloud_buffer *pcbuf )
+{
+   float t;
+   v3f p, pd;
+   int last_valid=0;
+
+   float total_length = 0.0f,
+         travel_length = 0.0;
+
+   v3f last;
+   v3_copy( h[0], last );
+   for( int it=0; it<128; it ++ ){
+      t = (float)(it+1) * (1.0f/128.0f);
+      eval_bezier3( h[0], h[1], h[2], t, p );
+      total_length += v3_dist( p, last );
+      v3_copy( p, last );
+   }
+
+   float patch_size = 4.0f,
+         patch_count = ceilf( total_length / patch_size );
+
+   t = 0.0f;
+   v3_copy( h[0], last );
+
+   for( int it=0; it<128; it ++ ){
+      float const k_sample_dist = 0.0025f,
+                  k_line_width = 1.5f;
+
+      eval_bezier3( h[0], h[1], h[2], t, p );
+      eval_bezier3( h[0], h[1], h[2], t+k_sample_dist, pd );
+
+      travel_length += v3_dist( p, last );
+
+      float mod = k_sample_dist / v3_dist( p, pd );
+
+      v3f v0,up, right;
+
+      v3_muls( n0, -(1.0f-t), up );
+      v3_muladds( up, n2, -t, up );
+      v3_normalize( up );
+
+      v3_sub( pd,p,v0 );
+      v3_cross( up, v0, right );
+      v3_normalize( right );
+
+      float cur_x = (1.0f-t)*h[0][3] + t*h[2][3];
+      
+      v3f sc, sa, sb, down;
+      v3_muladds( p, right, cur_x * k_line_width, sc );
+      v3_muladds( sc, up, 1.5f, sc );
+      v3_muladds( sc, right, k_line_width*0.95f, sa );
+      v3_muladds( sc, right, 0.0f, sb );
+      v3_muls( up, -1.0f, down );
+      
+      ray_hit ha, hb;
+      ha.dist = 8.0f;
+      hb.dist = 8.0f;
+
+      int resa = ray_world( world, sa, down, &ha ),
+          resb = ray_world( world, sb, down, &hb );
+      
+      if( pcbuf && resa ){
+         world_routes_pointcloud_spot( world, pcbuf, ha.pos, 
+                                       12.0f, 10, route->colour );
+      }
+
+      if( resa && resb ){
+         struct world_surface *surfa = ray_hit_surface( world, &ha ),
+                              *surfb = ray_hit_surface( world, &hb );
+
+         if( (surfa->info.flags & k_material_flag_skate_target) &&
+             (surfb->info.flags & k_material_flag_skate_target) )
+         {
+            scene_vert va, vb;
+
+            float gap = vg_fractf(cur_x*0.5f)*0.02f;
+            
+            v3_muladds( ha.pos, up, 0.06f+gap, va.co );
+            v3_muladds( hb.pos, up, 0.06f+gap, vb.co );
+
+            scene_vert_pack_norm( &va, up );
+            scene_vert_pack_norm( &vb, up );
+
+            float t1 = (travel_length / total_length) * patch_count;
+            va.uv[0] = t1;
+            va.uv[1] = 0.0f;
+            vb.uv[0] = t1;
+            vb.uv[1] = 1.0f;
+
+            scene_push_vert( scene, &va );
+            scene_push_vert( scene, &vb );
+
+            if( last_valid ){
+               /* Connect them with triangles */
+               scene_push_tri( scene, (u32[3]){ 
+                     last_valid+0-2, last_valid+1-2, last_valid+2-2} );
+               scene_push_tri( scene, (u32[3]){ 
+                     last_valid+1-2, last_valid+3-2, last_valid+2-2} );
+            }
+            
+            last_valid = scene->vertex_count;
+         }
+         else
+            last_valid = 0;
+      }
+      else
+         last_valid = 0;
+
+      if( t == 1.0f )
+         return;
+
+      t += 1.0f*mod;
+      if( t > 1.0f )
+         t = 1.0f;
+
+      v3_copy( p, last );
+   }
+}
+
+VG_STATIC void world_routes_gen_meshes( world_instance *world, u32 route_id, 
+                                        scene_context *sc,
+                                        pointcloud_buffer *pcbuf )
+{
+   ent_route *route = mdl_arritm( &world->ent_route, route_id );
+   u8 colour[4];
+   colour[0] = route->colour[0] * 255.0f;
+   colour[1] = route->colour[1] * 255.0f;
+   colour[2] = route->colour[2] * 255.0f;
+   colour[3] = route->colour[3] * 255.0f;
+
+   u32 last_valid = 0;
+
+   for( int i=0; i<route->checkpoints_count; i++ ){
+      int i0 = route->checkpoints_start+i,
+          i1 = route->checkpoints_start+((i+1)%route->checkpoints_count);
+
+      ent_checkpoint *c0 = mdl_arritm(&world->ent_checkpoint, i0),
+                     *c1 = mdl_arritm(&world->ent_checkpoint, i1);
+
+      ent_gate *start_gate = mdl_arritm( &world->ent_gate, c0->gate_index );
+      start_gate = mdl_arritm( &world->ent_gate, start_gate->target );
+
+      ent_gate *end_gate = mdl_arritm( &world->ent_gate, c1->gate_index ),
+               *collector = mdl_arritm( &world->ent_gate, end_gate->target );
+
+      v4f p[3];
+
+      v3_add( (v3f){0.0f,0.1f,0.0f}, start_gate->co[0], p[0] );
+      p[0][3]  = start_gate->ref_count;
+      p[0][3] -= (float)start_gate->route_count * 0.5f;
+      start_gate->ref_count ++;
+
+      if( !c0->path_count )
+         continue;
+
+      /* this is so that we get nice flow through the gates */
+      v3f temp_alignments[2];
+      ent_gate *both[] = { start_gate, end_gate };
+
+      for( int j=0; j<2; j++ ){
+         int pi = c0->path_start + ((j==1)? c0->path_count-1: 0);
+
+         ent_path_index *index = mdl_arritm( &world->ent_path_index, pi );
+         ent_route_node *rn = mdl_arritm( &world->ent_route_node,
+                                          index->index );
+         v3f v0;
+         v3_sub( rn->co, both[j]->co[0], v0 );
+         float d = v3_dot( v0, both[j]->to_world[2] );
+
+         v3_muladds( both[j]->co[0], both[j]->to_world[2], d, 
+                     temp_alignments[j] );
+         v3_add( (v3f){0.0f,0.1f,0.0f}, temp_alignments[j], temp_alignments[j]);
+      }
+
+
+      for( int j=0; j<c0->path_count; j ++ ){
+         ent_path_index *index = mdl_arritm( &world->ent_path_index, 
+                                             c0->path_start+j );
+         ent_route_node *rn = mdl_arritm( &world->ent_route_node,
+                                          index->index );
+         if( j==0 || j==c0->path_count-1 )
+            if( j == 0 )
+               v3_copy( temp_alignments[0], p[1] );
+            else
+               v3_copy( temp_alignments[1], p[1] );
+         else
+            v3_copy( rn->co, p[1] );
+
+         p[1][3] = rn->ref_count;
+         p[1][3] -= (float)rn->ref_total * 0.5f;
+         rn->ref_count ++;
+
+         if( j+1 < c0->path_count ){
+            index = mdl_arritm( &world->ent_path_index, 
+                                c0->path_start+j+1 );
+            rn = mdl_arritm( &world->ent_route_node, index->index );
+
+            if( j+1 == c0->path_count-1 )
+               v3_lerp( p[1], temp_alignments[1], 0.5f, p[2] );
+            else
+               v3_lerp( p[1], rn->co, 0.5f, p[2] );
+
+            p[2][3] = rn->ref_count;
+            p[2][3] -= (float)rn->ref_total * 0.5f;
+         }
+         else{
+            v3_copy( end_gate->co[0], p[2] );
+            v3_add( (v3f){0.0f,0.1f,0.0f}, p[2], p[2] );
+            p[2][3] = collector->ref_count;
+
+            if( i == route->checkpoints_count-1)
+               p[2][3] -= 1.0f;
+
+            p[2][3] -= (float)collector->route_count * 0.5f;
+            //collector->ref_count ++;
+         }
+
+         /* p0,p1,p2 bezier patch is complete
+          * --------------------------------------*/
+         v3f surf0, surf2, n0, n2;
+
+         if( bh_closest_point( world->geo_bh, p[0], surf0, 5.0f ) == -1 )
+            v3_add( (v3f){0.0f,-0.1f,0.0f}, p[0], surf0 );
+
+         if( bh_closest_point( world->geo_bh, p[2], surf2, 5.0f ) == -1 )
+            v3_add( (v3f){0.0f,-0.1f,0.0f}, p[2], surf2 );
+
+         v3_sub( surf0, p[0], n0 );
+         v3_sub( surf2, p[2], n2 );
+         v3_normalize( n0 );
+         v3_normalize( n2 );
+
+         world_routes_place_curve( world, route, p, n0, n2, sc, pcbuf );
+
+         /* --- */
+         v4_copy( p[2], p[0] );
+      }
+   }
+
+   scene_copy_slice( sc, &route->sm );
+}
+
+VG_STATIC 
+struct world_surface *world_tri_index_surface( world_instance *world, 
+                                                 u32 index );
+
+VG_STATIC f64 world_routes_scatter_surface_points( world_instance *world,
+                                                   pointcloud_buffer *pcbuf,
+                                                   f32 rate )
+{
+   static f32 densities[] = {
+      [k_surface_prop_concrete] = 2.0f,
+      [k_surface_prop_grass]    = 0.8f,
+      [k_surface_prop_metal]    = 1.0f,
+      [k_surface_prop_wood]     = 2.5f,
+      [k_surface_prop_tiles]    = 4.0f
+   };
+
+   /* calculate total area */
+   f64 total_area = 0.0f;
+   for( u32 i=0; i<world->scene_geo.indice_count/3; i++ ){
+      u32 *tri = &world->scene_geo.arrindices[i*3];
+      struct world_surface *surf = world_tri_index_surface( world, tri[0] );
+
+      if( surf->info.shader == k_shader_boundary ||
+          surf->info.shader == k_shader_invisible ) continue;
+
+      if( !(surf->info.flags & k_material_flag_preview_visibile) ) continue;
+
+      scene_vert *va = &world->scene_geo.arrvertices[tri[0]],
+                 *vb = &world->scene_geo.arrvertices[tri[1]],
+                 *vc = &world->scene_geo.arrvertices[tri[2]];
+
+      v3f v0, v1, vn;
+      v3_sub( vb->co, va->co, v0 );
+      v3_sub( vc->co, va->co, v1 );
+      v3_cross( v0, v1, vn );
+      if( vn[1] < 0.0f ) continue;
+
+      f32 density = 1.0f;
+      if( surf->info.surface_prop < vg_list_size(densities) )
+         density = densities[surf->info.surface_prop];
+      total_area += v3_length(vn)*0.5f*density;
+   }
+   
+   f32 accum = 0.0f;
+
+   u8 colour[] = { 80,80,80,255 };
+   v3f light_dir = {0.3f,0.8f,0.1f};
+   v3_normalize( light_dir );
+
+   v3f inv_ext;
+   v3_sub( pcbuf->boundary[1], pcbuf->boundary[0], inv_ext );
+   v3_div( (v3f){1.0f,1.0f,1.0f}, inv_ext, inv_ext );
+
+   for( u32 i=0; i<world->scene_geo.indice_count/3; i++ ){
+      u32 *tri = &world->scene_geo.arrindices[i*3];
+      struct world_surface *surf = world_tri_index_surface( world, tri[0] );
+
+      if( surf->info.shader == k_shader_boundary ||
+          surf->info.shader == k_shader_invisible ) continue;
+
+      if( !(surf->info.flags & k_material_flag_preview_visibile) ) continue;
+
+      scene_vert *va = &world->scene_geo.arrvertices[tri[0]],
+                 *vb = &world->scene_geo.arrvertices[tri[1]],
+                 *vc = &world->scene_geo.arrvertices[tri[2]];
+
+      v3f v0, v1, vn;
+      v3_sub( vb->co, va->co, v0 );
+      v3_sub( vc->co, va->co, v1 );
+      v3_cross( v0, v1, vn );
+      if( vn[1] < 0.0f ) continue;
+
+      f32 density = 1.0f;
+      if( surf->info.surface_prop < vg_list_size(densities) )
+         density = densities[surf->info.surface_prop];
+
+      f32 area = v3_length(vn)*0.5f*density;
+      accum += area;
+
+      v3_normalize( vn );
+
+      while( accum > rate ){
+         accum -= rate;
+
+         if( pcbuf->count >= pcbuf->max ) return total_area;
+
+         v2f co = { vg_randf64(), vg_randf64() };
+         if( v2_length2(co) > 0.5f ){
+            co[0] = 1.0f-co[0];
+            co[1] = 1.0f-co[1];
+         }
+
+         v3f pt;
+         v3_muls( v0, co[0], pt );
+         v3_muladds( pt, v1, co[1], pt );
+         v3_add( va->co, pt, pt );
+
+         if( pt[1] < world->water.height ) continue;
+         pointcloud_vert *vert = &pcbuf->buf[ pcbuf->count ++ ];
+
+         v3f pos;
+         v3_sub( pt, pcbuf->boundary[0], pos );
+         v3_mul( pos, inv_ext, pos );
+
+         for( u32 i=0; i<3; i++ ){
+            vert->pos[i] = (pos[i]-0.5f) * 32767.0f;
+         }
+
+         static v4f colours[] = {
+            [k_surface_prop_concrete] = { 0.13, 0.15, 0.17, 1.0 },
+            [k_surface_prop_grass]    = { 0.07, 0.1, 0.14, 1.0 },
+            [k_surface_prop_metal]    = { 0.15, 0.19, 0.22, 1.0 },
+            [k_surface_prop_wood]     = { 0.1, 0.13, 0.17, 1.0 },
+            [k_surface_prop_tiles]    = { 0.05, 0.06, 0.07, 1.0 },
+         };
+
+         v4f col = {0.0f,0.0f,0.0f,0.0f};
+         if( surf->info.surface_prop < vg_list_size(colours) ){
+            v4_copy( colours[surf->info.surface_prop], col );
+         }
+
+         f32 brightness = v3_dot(vn,light_dir)*0.5f+0.5f;
+         v3_muls( col, brightness, col );
+
+         for( u32 j=0; j<4; j++ ){
+            vert->colour[j] = col[j] * 255.0f;
+         }
+      }
+   }
+
+   return total_area;
+}
+
+VG_STATIC void world_routes_surface_grid( world_instance *world,
+                                          pointcloud_buffer *pcbuf )
+{
+   i32 const k_gridlines = 32,
+             k_gridres   = 255;
+
+   v3f inv_ext;
+   v3_sub( pcbuf->boundary[1], pcbuf->boundary[0], inv_ext );
+   v3_div( (v3f){1.0f,1.0f,1.0f}, inv_ext, inv_ext );
+   v4f colour = {0.2f,0.2f,0.2f,1.0f};
+   v3f dir = {0.0f,-1.0f,0.0f};
+
+   for( u32 k=0; k<2; k++ ){
+      u32 a = k*2,
+          b = (k^0x1)*2;
+
+      for( i32 x=0; x<=k_gridlines; x++ ){
+         f32 t = (float)x / (float)k_gridlines,
+             px = vg_lerpf( pcbuf->boundary[0][a], pcbuf->boundary[1][a], t );
+
+         for( i32 z=0; z<=k_gridres; z++ ){
+            f32 tz = (float)z / (float)k_gridres,
+                pz = vg_lerpf(pcbuf->boundary[0][b],pcbuf->boundary[1][b], tz);
+
+            v3f ro, hit;
+            ro[a] = px;
+            ro[1] = 1000.0f;
+            ro[b] = pz;
+
+            bh_iter it;
+            bh_iter_init_ray( 0, &it, ro, dir, INFINITY );
+            i32 idx;
+
+            while( bh_next( world->geo_bh, &it, &idx ) ){
+               u32 *tri = &world->scene_geo.arrindices[ idx*3 ];
+               v3f vs[3];
+
+               for( u32 i=0; i<3; i++ ){
+                  v3_copy( world->scene_geo.arrvertices[tri[i]].co, vs[i] );
+               }
+               
+               f32 t;
+               if( ray_tri( vs, ro, dir, &t ) ){
+                  v3_muladds( ro, dir, t, hit );
+                  struct world_surface *m1 = 
+                     world_tri_index_surface( world, tri[0] );
+
+                  if( !(m1->info.flags & k_material_flag_preview_visibile) )
+                     continue;
+
+                  if( world->water.enabled )
+                     if( hit[1] < world->water.height )
+                        continue;
+
+                  if( pcbuf->count >= pcbuf->max ) return;
+
+                  pointcloud_vert *vert = &pcbuf->buf[ pcbuf->count ++ ];
+
+                  v3f co;
+                  v3_sub( hit, pcbuf->boundary[0], co );
+                  v3_mul( co, inv_ext, co );
+
+                  for( u32 i=0; i<3; i++ ){
+                     vert->pos[i] = (co[i]-0.5f) * 32767.0f;
+                  }
+
+                  for( u32 i=0; i<4; i++ ){
+                     vert->colour[i] = colour[i] * 255.0f;
+                  }
+               }
+            }
+         }
+      }
+   }
+}
+
+/* 
+ * Create the strips of colour that run through the world along course paths
+ */
+VG_STATIC void world_gen_routes_generate(void)
+{
+   world_instance *world = world_loading_instance();
+   vg_info( "Generating route meshes\n" );
+   vg_async_stall();
+
+   vg_rand_seed( 2000 );
+   vg_async_item *call_scene = scene_alloc_async( &world->scene_lines, 
+                                                  &world->mesh_route_lines,
+                                                  200000, 300000 );
+
+   vg_async_item *call_pointcloud = NULL;
+   pointcloud_buffer *pcbuf = NULL;
+
+   if( world_loader.generate_point_cloud ){
+      call_pointcloud = vg_async_alloc( 
+            sizeof(pointcloud_buffer) + 
+            sizeof(pointcloud_vert)*POINTCLOUD_POINTS );
+      pcbuf = call_pointcloud->payload;
+      pcbuf->count = 0;
+      pcbuf->max = POINTCLOUD_POINTS;
+      pcbuf->op = k_pointcloud_op_clear;
+
+      v3f ext, mid, v0;
+      v3_sub( world->scene_geo.bbx[1], world->scene_geo.bbx[0], ext );
+      f32 maxe = v3_maxf( ext );
+      v3_fill( v0, maxe * 0.5f );
+      v3_muladds( world->scene_geo.bbx[0], ext, 0.5f, mid );
+      v3_add( mid, v0, pcbuf->boundary[1] );
+      v3_sub( mid, v0, pcbuf->boundary[0] );
+   }
+
+   for( u32 i=0; i<mdl_arrcount(&world->ent_gate); i++ ){
+      ent_gate *gate = mdl_arritm( &world->ent_gate, i );
+      gate->ref_count = 0;
+      gate->route_count = 0;
+   }
+
+   for( u32 i=0; i<mdl_arrcount(&world->ent_route_node); i++ ){
+      ent_route_node *rn = mdl_arritm( &world->ent_route_node, i );
+      rn->ref_count = 0;
+      rn->ref_total = 0;
+   }
+
+   for( u32 k=0; k<mdl_arrcount(&world->ent_route); k++ ){
+      ent_route *route = mdl_arritm( &world->ent_route, k );
+
+      for( int i=0; i<route->checkpoints_count; i++ ){
+         int i0 = route->checkpoints_start+i,
+             i1 = route->checkpoints_start+((i+1)%route->checkpoints_count);
+
+         ent_checkpoint *c0 = mdl_arritm(&world->ent_checkpoint, i0),
+                        *c1 = mdl_arritm(&world->ent_checkpoint, i1);
+
+         ent_gate *start_gate = mdl_arritm( &world->ent_gate, c0->gate_index );
+         start_gate = mdl_arritm( &world->ent_gate, start_gate->target );
+         start_gate->route_count ++;
+
+         if( !c0->path_count )
+            continue;
+
+         for( int j=0; j<c0->path_count; j ++ ){
+            ent_path_index *index = mdl_arritm( &world->ent_path_index, 
+                                                c0->path_start+j );
+            ent_route_node *rn = mdl_arritm( &world->ent_route_node,
+                                             index->index );
+            rn->ref_total ++;
+         }
+      }
+   }
+
+   for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
+      world_routes_gen_meshes( world, i, &world->scene_lines, pcbuf );
+   }
+
+   if( world_loader.generate_point_cloud ){
+      f64 area = 0.0;
+      area = world_routes_scatter_surface_points( world, pcbuf, 16.0f );
+      world_routes_surface_grid( world, pcbuf );
+      vg_info( "Distrubuted %u points over %fkm^2!\n", 
+                pcbuf->count, area/1e6f );
+      vg_async_dispatch( call_pointcloud, async_pointcloud_sub );
+   }
+
+   vg_async_dispatch( call_scene, async_scene_upload );
+
+   world_routes_clear( world );
+}
+
+/* load all routes from model header */
+VG_STATIC void world_gen_routes_ent_init(void)
+{
+   world_instance *world = world_loading_instance();
+   vg_info( "Initializing routes\n" );
+
+   for( u32 i=0; i<mdl_arrcount(&world->ent_gate); i++ ){
+      ent_gate *gate = mdl_arritm( &world->ent_gate, i );
+      for( u32 j=0; j<4; j++ ){
+         gate->routes[j] = 0xffff;
+      }
+   }
+
+   for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
+      ent_route *route = mdl_arritm(&world->ent_route,i);
+      mdl_transform_m4x3( &route->transform, route->board_transform );
+
+      route->official_track_id = 0xffffffff;
+      for( u32 j=0; j<vg_list_size(track_infos); j ++ ){
+         if( !strcmp(track_infos[j].name, 
+                     mdl_pstr(&world->meta,route->pstr_name))){
+            route->official_track_id = j;
+         }
+      }
+
+      for( u32 j=0; j<route->checkpoints_count; j++ ){
+         u32 id = route->checkpoints_start + j;
+         ent_checkpoint *cp = mdl_arritm(&world->ent_checkpoint,id);
+         
+         ent_gate *gate = mdl_arritm( &world->ent_gate, cp->gate_index );
+
+         for( u32 k=0; k<4; k++ ){
+            if( gate->routes[k] == 0xffff ){
+               gate->routes[k] = i;
+               break;
+            }
+         }
+
+         if( gate->type == k_gate_type_teleport ){
+            gate = mdl_arritm(&world->ent_gate, gate->target );
+
+            for( u32 k=0; k<4; k++ ){
+               if( gate->routes[k] == i ){
+                  vg_error( "already assigned route to gate\n" );
+                  break;
+               }
+               if( gate->routes[k] == 0xffff ){
+                  gate->routes[k] = i;
+                  break;
+               }
+            }
+         }
+      }
+   }
+
+   for( u32 i=0; i<mdl_arrcount(&world->ent_gate); i++ ){
+      ent_gate *gate = mdl_arritm( &world->ent_gate, i );
+   }
+
+   world_routes_clear( world );
+}
+
+/* 
+ * -----------------------------------------------------------------------------
+ *                                    Events
+ * -----------------------------------------------------------------------------
+ */
+
+VG_STATIC void world_routes_init(void)
+{
+   world_static.current_run_version = 200;
+   world_static.time = 300.0;
+   world_static.last_use = 0.0;
+
+   shader_scene_route_register();
+   shader_routeui_register();
+}
+
+VG_STATIC void world_routes_update( world_instance *world )
+{
+   world_static.time += vg.time_delta;
+
+   for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
+      ent_route *route = mdl_arritm( &world->ent_route, i );
+      
+      int target = route->active_checkpoint == 0xffff? 0: 1;
+      route->factive = vg_lerpf( route->factive, target, 0.6f*vg.time_delta );
+   }
+
+   for( u32 i=0; i<world_render.text_particle_count; i++ ){
+      struct text_particle *particle = &world_render.text_particles[i];
+      rb_object_debug( &particle->obj, VG__RED );
+   }
+}
+
+VG_STATIC void world_routes_fixedupdate( world_instance *world )
+{
+   rb_solver_reset();
+
+   for( u32 i=0; i<world_render.text_particle_count; i++ ){
+      struct text_particle *particle = &world_render.text_particles[i];
+
+      if( rb_global_has_space() ){
+         rb_ct *buf = rb_global_buffer();
+
+         int l = rb_sphere__scene( particle->obj.rb.to_world,
+                                   &particle->obj.inf.sphere,
+                                   NULL, &world->rb_geo.inf.scene, buf );
+
+         for( int j=0; j<l; j++ ){
+            buf[j].rba = &particle->obj.rb;
+            buf[j].rbb = &world->rb_geo.rb;
+         }
+
+         rb_contact_count += l;
+      }
+   }
+
+   rb_presolve_contacts( rb_contact_buffer, rb_contact_count );
+
+   for( int i=0; i<rb_contact_count; i++ ){
+      rb_contact_restitution( rb_contact_buffer+i, vg_randf64() );
+   }
+
+   for( int i=0; i<6; i++ ){
+      rb_solve_contacts( rb_contact_buffer, rb_contact_count );
+   }
+
+   for( u32 i=0; i<world_render.text_particle_count; i++ ){
+      struct text_particle *particle = &world_render.text_particles[i];
+      rb_iter( &particle->obj.rb );
+   }
+
+   for( u32 i=0; i<world_render.text_particle_count; i++ ){
+      struct text_particle *particle = &world_render.text_particles[i];
+      rb_update_transform( &particle->obj.rb );
+   }
+}
+
+VG_STATIC void bind_terrain_noise(void);
+VG_STATIC void world_bind_light_array( world_instance *world,
+                                       GLuint shader, GLuint location, 
+                                       int slot );
+VG_STATIC void world_bind_light_index( world_instance *world,
+                                       GLuint shader, GLuint location, 
+                                       int slot );
+
+VG_STATIC void world_routes_update_timer_texts( world_instance *world )
+{
+   world_render.timer_text_count = 0;
+
+   for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
+      ent_route *route = mdl_arritm( &world->ent_route, i );
+
+      if( route->active_checkpoint != 0xffff ){
+         u32 next = route->active_checkpoint+1;
+             next = next % route->checkpoints_count;
+             next += route->checkpoints_start;
+
+         ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint, next );
+         ent_gate *gate = mdl_arritm( &world->ent_gate, cp->gate_index );
+         ent_gate *dest = mdl_arritm( &world->ent_gate, gate->target );
+         
+         u32 j=0;
+         for( ; j<4; j++ ){
+            if( dest->routes[j] == i ){
+               break;
+            }
+         }
+
+         float h0 = 0.8f,
+               h1 = 1.2f,
+               depth = 0.4f,
+               size = 0.4f;
+
+         struct timer_text *text = 
+            &world_render.timer_texts[ world_render.timer_text_count ++ ];
+
+         text->gate = gate;
+         text->route = route;
+
+         if( route->valid_checkpoints >= route->checkpoints_count ){
+            double lap_time = world_static.time - route->timing_base,
+                   time_centiseconds = lap_time * 100.0;
+
+            if( time_centiseconds > (float)0xfffe ) time_centiseconds = 0.0;
+
+            u16 centiseconds = time_centiseconds,
+                seconds      = centiseconds / 100,
+                minutes      = seconds / 60;
+
+            centiseconds %= 100;
+            seconds     %= 60;
+            minutes     %= 60;
+
+            if( minutes > 9 ) minutes = 9;
+            
+            int j=0;
+            if( minutes ){
+               highscore_intr( text->text, minutes, 1, ' ' ); j++;
+               text->text[j++] = ':';
+            }
+            
+            if( seconds >= 10 || minutes ){
+               highscore_intr( text->text+j, seconds, 2, '0' ); j+=2;
+            }
+            else{
+               highscore_intr( text->text+j, seconds, 1, '0' ); j++;
+            }
+
+            text->text[j++] = '.';
+            highscore_intr( text->text+j, centiseconds, 1, '0' ); j++;
+            text->text[j] = '\0';
+         }
+         else{
+            highscore_intr( text->text, route->valid_checkpoints, 1, ' ' );
+            text->text[1] = '/';
+            highscore_intr( text->text+2, route->checkpoints_count+1, 1, ' ' );
+            text->text[3] = '\0';
+         }
+
+         float align_r  = font3d_string_width( &gui.font, 0, text->text );
+               align_r *= size;
+
+         v3f positions[] = {
+            { -0.92f, h0, depth },
+            {  0.92f - align_r, h0, depth },
+            { -0.92f, h1, depth },
+            {  0.92f - align_r, h1, depth },
+         };
+
+         if( dest->route_count == 1 ){
+            positions[0][0] = -align_r*0.5f;
+            positions[0][1] = h1;
+         }
+
+         m3x3_copy( gate->to_world, text->transform );
+         float ratio = v3_length(text->transform[0]) / 
+                        v3_length(text->transform[1]);
+
+         m3x3_scale( text->transform, (v3f){ size, size*ratio, 0.1f } );
+         m4x3_mulv( gate->to_world, positions[j], text->transform[3] );
+      }
+   }
+}
+
+VG_STATIC void world_routes_fracture( world_instance *world, ent_gate *gate,
+                                      v3f imp_co, v3f imp_v )
+{
+   world_render.text_particle_count = 0;
+   
+   for( u32 i=0; i<world_render.timer_text_count; i++ ){
+      struct timer_text *text = &world_render.timer_texts[i];
+
+      if( text->gate != gate ) continue;
+
+      m4x3f transform;
+      m4x3_mul( gate->transport, text->transform, transform );
+
+      v3f co, s;
+      v4f q;
+      m4x3_decompose( transform, co, q, s );
+
+      v3f offset;
+      v3_zero( offset );
+
+      v4f colour;
+      float brightness = 0.3f + world->ub_lighting.g_day_phase;
+      v3_muls( text->route->colour, brightness, colour );
+      colour[3] = 1.0f-text->route->factive;
+
+      for( u32 j=0;; j++ ){
+         char c = text->text[j];
+         if( !c ) break;
+
+         ent_glyph *glyph = font3d_glyph( &gui.font, 0, c );
+         if( !glyph ) continue;
+
+         if( c >= (u32)'0' && c <= (u32)'9' && glyph->indice_count ){
+            struct text_particle *particle = 
+               &world_render.text_particles[world_render.text_particle_count++];
+
+            particle->glyph = glyph;
+            v4_copy( colour, particle->colour );
+
+            v3f origin;
+            v2_muls( glyph->size, 0.5f, origin );
+            origin[2] = -0.5f;
+
+            v3f world_co;
+
+            v3_add( offset, origin, world_co );
+            m4x3_mulv( transform, world_co, world_co );
+
+            float r = vg_maxf( s[0]*glyph->size[0], s[1]*glyph->size[1] )*0.5f;
+
+            m3x3_identity( particle->mlocal );
+            m3x3_scale( particle->mlocal, s );
+            origin[2] *= s[2];
+            v3_muls( origin, -1.0f, particle->mlocal[3] );
+
+            v3_copy( world_co, particle->obj.rb.co );
+            v3_muls( imp_v, 1.0f+vg_randf64(), particle->obj.rb.v );
+            particle->obj.rb.v[1] += 2.0f;
+
+            v4_copy( q, particle->obj.rb.q );
+            particle->obj.rb.w[0] = vg_randf64()*2.0f-1.0f;
+            particle->obj.rb.w[1] = vg_randf64()*2.0f-1.0f;
+            particle->obj.rb.w[2] = vg_randf64()*2.0f-1.0f;
+
+            particle->obj.type = k_rb_shape_sphere;
+            particle->obj.inf.sphere.radius = r*0.6f;
+
+            rb_init_object( &particle->obj );
+         }
+         offset[0] += glyph->size[0];
+      }
+   }
+}
+
+VG_STATIC void render_world_routes( world_instance *world, camera *cam, 
+                                    int layer_depth )
+{
+   m4x3f identity_matrix;
+   m4x3_identity( identity_matrix );
+
+   shader_scene_route_use();
+   shader_scene_route_uTexGarbage(0);
+   world_link_lighting_ub( world, _shader_scene_route.id );
+   world_bind_position_texture( world, _shader_scene_route.id, 
+                        _uniform_scene_route_g_world_depth, 2 );
+   world_bind_light_array( world, _shader_scene_route.id,
+                        _uniform_scene_route_uLightsArray, 3 );
+   world_bind_light_index( world, _shader_scene_route.id,
+                                 _uniform_scene_route_uLightsIndex, 4 );
+   bind_terrain_noise();
+
+   shader_scene_route_uPv( cam->mtx.pv );
+   shader_scene_route_uPvmPrev( cam->mtx_prev.pv );
+   shader_scene_route_uMdl( identity_matrix );
+   shader_scene_route_uCamera( cam->transform[3] );
+
+   mesh_bind( &world->mesh_route_lines );
+
+   for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
+      ent_route *route = mdl_arritm( &world->ent_route, i );
+
+      v4f colour;
+      v3_lerp( (v3f){0.7f,0.7f,0.7f}, route->colour, route->factive, colour );
+      colour[3] = route->factive*0.2f;
+
+      shader_scene_route_uColour( colour );
+      mdl_draw_submesh( &route->sm );
+   }
+
+   /* timers
+    * ---------------------------------------------------- */
+   if( layer_depth == 0 ){
+      font3d_bind( &gui.font, cam );
+
+      for( u32 i=0; i<world_render.timer_text_count; i++ ){
+         struct timer_text *text = &world_render.timer_texts[i];
+
+         v4f colour;
+         float brightness = 0.3f + world->ub_lighting.g_day_phase;
+         v3_muls( text->route->colour, brightness, colour );
+         colour[3] = 1.0f-text->route->factive;
+
+         shader_model_font_uColour( colour );
+         font3d_simple_draw( &gui.font, 0, text->text, cam, text->transform );
+      }
+
+      shader_model_font_uOffset( (v4f){0.0f,0.0f,0.0f,1.0f} );
+
+      for( u32 i=0; i<world_render.text_particle_count; i++ ){
+         struct text_particle *particle = &world_render.text_particles[i];
+
+         m4x4f prev_mtx;
+
+         m4x3_expand( particle->mdl, prev_mtx );
+         m4x4_mul( cam->mtx_prev.pv, prev_mtx, prev_mtx );
+
+         shader_model_font_uPvmPrev( prev_mtx );
+
+         v4f q;
+         m4x3f model;
+         rb_extrapolate( &particle->obj.rb, model[3], q );
+         q_m3x3( q, model );
+
+         m4x3_mul( model, particle->mlocal, particle->mdl );
+         shader_model_font_uMdl( particle->mdl );
+         shader_model_font_uColour( particle->colour );
+
+         mesh_drawn( particle->glyph->indice_start, 
+                     particle->glyph->indice_count );
+      }
+   }
+
+   /* gate markers 
+    * ---------------------------------------------------- */
+
+   shader_model_gate_use();
+   shader_model_gate_uPv( cam->mtx.pv );
+   shader_model_gate_uCam( cam->pos );
+   shader_model_gate_uTime( vg.time*0.25f );
+   shader_model_gate_uInvRes( (v2f){
+         1.0f / (float)vg.window_x,
+         1.0f / (float)vg.window_y });
+
+   mesh_bind( &world_gates.mesh );
+
+   /* skip writing into the motion vectors for this */
+   glDrawBuffers( 1, (GLenum[]){ GL_COLOR_ATTACHMENT0 } );
+
+   for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
+      ent_route *route = mdl_arritm( &world->ent_route, i );
+
+      if( route->active_checkpoint != 0xffff ){
+         v4f colour;
+         float brightness = 0.3f + world->ub_lighting.g_day_phase;
+         v3_muls( route->colour, brightness, colour );
+         colour[3] = 1.0f-route->factive;
+
+         shader_model_gate_uColour( colour );
+
+         u32 next = route->active_checkpoint+1+layer_depth;
+             next = next % route->checkpoints_count;
+             next += route->checkpoints_start;
+
+         ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint, next );
+         ent_gate *gate = mdl_arritm( &world->ent_gate, cp->gate_index );
+         shader_model_gate_uMdl( gate->to_world );
+
+         for( u32 j=0; j<4; j++ ){
+            if( gate->routes[j] == i ){
+               mdl_draw_submesh( &world_gates.sm_marker[j] );
+               break;
+            }
+         }
+      }
+   }
+   glDrawBuffers( 2, (GLenum[]){ GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 } );
+}
+
+#endif /* ROUTES_C */
index b0151b4eecda1f288f0853b8219e437fea71de13..c63dd549d95ff1f9bd9e31138547d06b8d613037 100644 (file)
 #ifndef ROUTES_H
 #define ROUTES_H
 
-#include <time.h>
 #include "world.h"
-#include "world_gate.h"
-#include "font.h"
-#include "pointcloud.h"
-
-#if 0
-#include "shaders/vblend.h"
-#endif
-
-#include "shaders/scene_route.h"
-#include "shaders/routeui.h"
-
-
-VG_STATIC 
-void world_routes_local_set_record( world_instance *world, ent_route *route,
-                                    double lap_time )
-{
-   vg_success( "  NEW LAP TIME: %f\n", lap_time );
-
-   if( route->official_track_id != 0xffffffff ){
-      double time_centiseconds = lap_time * 100.0;
-      if( time_centiseconds > (float)0xfffe )   /* skill issue */
-         return;
-
-      highscore_record temp;
-      temp.trackid  = route->official_track_id;
-      temp.datetime = time(NULL);
-      temp.playerid = 0;
-      temp.points   = 0;
-      temp.time     = time_centiseconds;
-
-#if 0
-      highscores_push_record( &temp );
-#endif
-
-      struct track_info *ti = &track_infos[ route->official_track_id ];
-      ti->push = 1;
-      
-      if( ti->achievement_id ){
-#if 0
-         steam_set_achievement( ti->achievement_id );
-         steam_store_achievements();
-#endif
-      }
-   }
-   else{
-      vg_warn( "There is no associated track for this record...\n" );
-   }
-}
-
-
-VG_STATIC void world_routes_clear( world_instance *world )
-{
-   for( u32 i=0; i<mdl_arrcount( &world->ent_route ); i++ ){
-      ent_route *route = mdl_arritm( &world->ent_route, i );
-      route->active_checkpoint = 0xffff;
-   }
-
-   for( u32 i=0; i<mdl_arrcount( &world->ent_gate ); i++ ){
-      ent_gate *rg = mdl_arritm( &world->ent_gate, i );
-      rg->timing_version = 0;
-      rg->timing_time = 0.0;
-   }
-
-   world_global.current_run_version += 4;
-   world_global.last_use = 0.0;
-}
-
-VG_STATIC void world_routes_time_lap( world_instance *world, ent_route *route )
-{
-   vg_info( "------- time lap %s -------\n", 
-            mdl_pstr(&world->meta,route->pstr_name) );
-
-   double start_time = 0.0;
-   u32 last_version=0;
-
-   u32 valid_count=0;
-
-   for( u32 i=0; i<route->checkpoints_count; i++ ){
-      u32 cpid  = (i+route->active_checkpoint) % route->checkpoints_count;
-          cpid += route->checkpoints_start;
-
-      ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint, cpid );
-      ent_gate *rg = mdl_arritm( &world->ent_gate, cp->gate_index );
-                rg = mdl_arritm( &world->ent_gate, rg->target );
-
-      if( i == 1 ){
-         route->timing_base = rg->timing_time;
-      }
-
-      if( i == 0 )
-         start_time = rg->timing_time;
-      else{
-         if( last_version+1 == rg->timing_version ) valid_count ++;
-         else valid_count = 0;
-      }
-
-      last_version = rg->timing_version;
-      vg_info( "%u %f\n", rg->timing_version, rg->timing_time );
-   }
-
-   if( world_global.current_run_version == last_version+1 ){
-      valid_count ++;
-
-      if( route->checkpoints_count == 1 ){
-         route->timing_base = world_global.time;
-      }
-   }
-   else valid_count = 0;
-
-   vg_info( "%u %f\n", world_global.current_run_version, world_global.time );
-
-   if( valid_count==route->checkpoints_count ){
-      double lap_time = world_global.time - start_time;
-      world_routes_local_set_record( world, route, lap_time );
-   }
-
-   route->valid_checkpoints = valid_count+1;
-
-   vg_info( "valid: %u\n", valid_count );
-   vg_info( "----------------------------\n" );
-}
-
-/*
- * When going through a gate this is called for bookkeeping purposes
- */
-VG_STATIC void world_routes_activate_entry_gate( world_instance *world, 
-                                                 ent_gate *rg )
-{
-   world_global.last_use = world_global.time;
-
-   /* disable all routes and leave the world */
-   if( rg->type == k_gate_type_nonlocel ){
-      for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
-         ent_route *route = mdl_arritm( &world->ent_route, i );
-         route->active_checkpoint = 0xffff;
-      }
-      return;
-   }
-
-   ent_gate *dest = mdl_arritm( &world->ent_gate, rg->target );
-
-   for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
-      ent_route *route = mdl_arritm( &world->ent_route, i );
-
-      u32 active_prev = route->active_checkpoint;
-      route->active_checkpoint = 0xffff;
-
-      for( u32 j=0; j<4; j++ ){
-         if( dest->routes[j] == i ){
-            for( u32 k=0; k<route->checkpoints_count; k++ ){
-               ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint, 
-                                                 route->checkpoints_start+k );
-
-               ent_gate *gk = mdl_arritm( &world->ent_gate, cp->gate_index );
-                         gk = mdl_arritm( &world->ent_gate, gk->target );
-               if( gk == dest ){
-                  route->active_checkpoint = k;
-                  world_routes_time_lap( world, route );
-                  break;
-               }
-            }
-            break;
-         }
-      }
-   }
-
-   dest->timing_version = world_global.current_run_version;
-   dest->timing_time = world_global.time;
-
-   world_global.current_run_version ++;
-}
-
-/* draw lines along the paths */
-VG_STATIC void world_routes_debug( world_instance *world )
-{
-   for( u32 i=0; i<mdl_arrcount(&world->ent_route_node); i++ ){
-      ent_route_node *rn = mdl_arritm(&world->ent_route_node,i);
-      vg_line_pt3( rn->co, 0.25f, VG__WHITE );
-   }
-
-   for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
-      ent_route *route = mdl_arritm(&world->ent_route, i);
-
-      u32 colours[] = { 0xfff58142, 0xff42cbf5, 0xff42f56c, 0xfff542b3,
-                        0xff5442f5 };
-
-      u32 cc = 0xffcccccc;
-      if( route->active_checkpoint != 0xffff ){
-         cc = colours[i%vg_list_size(colours)];
-      }
-
-      for( int i=0; i<route->checkpoints_count; i++ ){
-         int i0 = route->checkpoints_start+i,
-             i1 = route->checkpoints_start+((i+1)%route->checkpoints_count);
-
-         ent_checkpoint *c0 = mdl_arritm(&world->ent_checkpoint, i0),
-                        *c1 = mdl_arritm(&world->ent_checkpoint, i1);
-
-         ent_gate *start_gate = mdl_arritm( &world->ent_gate, c0->gate_index );
-         ent_gate *end_gate = mdl_arritm( &world->ent_gate, c1->gate_index );
-
-         v3f p0, p1;
-         v3_copy( start_gate->co[1], p0 );
-
-         for( int j=0; j<c0->path_count; j ++ ){
-            ent_path_index *index = mdl_arritm( &world->ent_path_index, 
-                                                c0->path_start+j );
-
-            ent_route_node *rn = mdl_arritm( &world->ent_route_node,
-                                             index->index );
-
-            v3_copy( rn->co, p1 );
-            vg_line( p0, p1, cc );
-            v3_copy( p1, p0 );
-         }
-
-         v3_copy( end_gate->co[0], p1 );
-         vg_line( p0, p1, cc );
-      }
-   }
-}
-
-VG_STATIC 
-void world_routes_pointcloud_spot( world_instance *world, 
-                                   pointcloud_buffer *pcbuf, 
-                                   v3f co, f32 radius, u32 samples, v4f colour )
-{
-   v3f inv_ext;
-   v3_sub( pcbuf->boundary[1], pcbuf->boundary[0], inv_ext );
-   v3_div( (v3f){1.0f,1.0f,1.0f}, inv_ext, inv_ext );
-
-   for( u32 j=0; j<samples; j++ ){
-      if( pcbuf->count >= pcbuf->max )
-         return;
-
-      pointcloud_vert *vert = &pcbuf->buf[ pcbuf->count ++ ];
-
-      v3f sample, jitter, point;
-      vg_rand_sphere( jitter );
-      v3_muladds( co, jitter, radius, sample );
-
-      if( bh_closest_point( world->geo_bh, sample, point, radius*1.5f ) == -1 ){
-         v3_copy( sample, point );
-      }
-
-      v3f pos;
-      v3_sub( point, pcbuf->boundary[0], pos );
-      v3_mul( pos, inv_ext, pos );
-
-      for( u32 i=0; i<3; i++ ){
-         vert->pos[i] = (pos[i]-0.5f) * 32767.0f;
-      }
-
-      float dist = 1.0f-(v3_length(jitter));
-
-      for( u32 i=0; i<4; i++ ){
-         vert->colour[i] = colour[i] * 255.0f * dist*dist;
-      }
-   }
-}
-
-VG_STATIC 
-void world_routes_place_curve( world_instance *world, ent_route *route,
-                               v4f h[3], v3f n0, v3f n2, scene_context *scene,
-                               pointcloud_buffer *pcbuf )
-{
-   float t;
-   v3f p, pd;
-   int last_valid=0;
-
-   float total_length = 0.0f,
-         travel_length = 0.0;
-
-   v3f last;
-   v3_copy( h[0], last );
-   for( int it=0; it<128; it ++ ){
-      t = (float)(it+1) * (1.0f/128.0f);
-      eval_bezier3( h[0], h[1], h[2], t, p );
-      total_length += v3_dist( p, last );
-      v3_copy( p, last );
-   }
-
-   float patch_size = 4.0f,
-         patch_count = ceilf( total_length / patch_size );
-
-   t = 0.0f;
-   v3_copy( h[0], last );
-
-   for( int it=0; it<128; it ++ ){
-      float const k_sample_dist = 0.0025f,
-                  k_line_width = 1.5f;
-
-      eval_bezier3( h[0], h[1], h[2], t, p );
-      eval_bezier3( h[0], h[1], h[2], t+k_sample_dist, pd );
-
-      travel_length += v3_dist( p, last );
-
-      float mod = k_sample_dist / v3_dist( p, pd );
-
-      v3f v0,up, right;
-
-      v3_muls( n0, -(1.0f-t), up );
-      v3_muladds( up, n2, -t, up );
-      v3_normalize( up );
-
-      v3_sub( pd,p,v0 );
-      v3_cross( up, v0, right );
-      v3_normalize( right );
-
-      float cur_x = (1.0f-t)*h[0][3] + t*h[2][3];
-      
-      v3f sc, sa, sb, down;
-      v3_muladds( p, right, cur_x * k_line_width, sc );
-      v3_muladds( sc, up, 1.5f, sc );
-      v3_muladds( sc, right, k_line_width*0.95f, sa );
-      v3_muladds( sc, right, 0.0f, sb );
-      v3_muls( up, -1.0f, down );
-      
-      ray_hit ha, hb;
-      ha.dist = 8.0f;
-      hb.dist = 8.0f;
-
-      int resa = ray_world( world, sa, down, &ha ),
-          resb = ray_world( world, sb, down, &hb );
-      
-      if( resa ){
-         world_routes_pointcloud_spot( world, pcbuf, ha.pos, 
-                                       12.0f, 10, route->colour );
-      }
-
-      if( resa && resb ){
-         struct world_surface *surfa = ray_hit_surface( world, &ha ),
-                              *surfb = ray_hit_surface( world, &hb );
-
-         if( (surfa->info.flags & k_material_flag_skate_target) &&
-             (surfb->info.flags & k_material_flag_skate_target) )
-         {
-            scene_vert va, vb;
-
-            float gap = vg_fractf(cur_x*0.5f)*0.02f;
-            
-            v3_muladds( ha.pos, up, 0.06f+gap, va.co );
-            v3_muladds( hb.pos, up, 0.06f+gap, vb.co );
-
-            scene_vert_pack_norm( &va, up );
-            scene_vert_pack_norm( &vb, up );
-
-            float t1 = (travel_length / total_length) * patch_count;
-            va.uv[0] = t1;
-            va.uv[1] = 0.0f;
-            vb.uv[0] = t1;
-            vb.uv[1] = 1.0f;
-
-            scene_push_vert( scene, &va );
-            scene_push_vert( scene, &vb );
-
-            if( last_valid ){
-               /* Connect them with triangles */
-               scene_push_tri( scene, (u32[3]){ 
-                     last_valid+0-2, last_valid+1-2, last_valid+2-2} );
-               scene_push_tri( scene, (u32[3]){ 
-                     last_valid+1-2, last_valid+3-2, last_valid+2-2} );
-            }
-            
-            last_valid = scene->vertex_count;
-         }
-         else
-            last_valid = 0;
-      }
-      else
-         last_valid = 0;
-
-      if( t == 1.0f )
-         return;
-
-      t += 1.0f*mod;
-      if( t > 1.0f )
-         t = 1.0f;
-
-      v3_copy( p, last );
-   }
-}
-
-VG_STATIC void world_routes_gen_meshes( world_instance *world, u32 route_id, 
-                                        scene_context *sc,
-                                        pointcloud_buffer *pcbuf )
-{
-   ent_route *route = mdl_arritm( &world->ent_route, route_id );
-   u8 colour[4];
-   colour[0] = route->colour[0] * 255.0f;
-   colour[1] = route->colour[1] * 255.0f;
-   colour[2] = route->colour[2] * 255.0f;
-   colour[3] = route->colour[3] * 255.0f;
-
-   u32 last_valid = 0;
-
-   for( int i=0; i<route->checkpoints_count; i++ ){
-      int i0 = route->checkpoints_start+i,
-          i1 = route->checkpoints_start+((i+1)%route->checkpoints_count);
-
-      ent_checkpoint *c0 = mdl_arritm(&world->ent_checkpoint, i0),
-                     *c1 = mdl_arritm(&world->ent_checkpoint, i1);
-
-      ent_gate *start_gate = mdl_arritm( &world->ent_gate, c0->gate_index );
-      start_gate = mdl_arritm( &world->ent_gate, start_gate->target );
-
-      ent_gate *end_gate = mdl_arritm( &world->ent_gate, c1->gate_index ),
-               *collector = mdl_arritm( &world->ent_gate, end_gate->target );
-
-      v4f p[3];
-
-      v3_add( (v3f){0.0f,0.1f,0.0f}, start_gate->co[0], p[0] );
-      p[0][3]  = start_gate->ref_count;
-      p[0][3] -= (float)start_gate->route_count * 0.5f;
-      start_gate->ref_count ++;
-
-      if( !c0->path_count )
-         continue;
-
-      /* this is so that we get nice flow through the gates */
-      v3f temp_alignments[2];
-      ent_gate *both[] = { start_gate, end_gate };
-
-      for( int j=0; j<2; j++ ){
-         int pi = c0->path_start + ((j==1)? c0->path_count-1: 0);
-
-         ent_path_index *index = mdl_arritm( &world->ent_path_index, pi );
-         ent_route_node *rn = mdl_arritm( &world->ent_route_node,
-                                          index->index );
-         v3f v0;
-         v3_sub( rn->co, both[j]->co[0], v0 );
-         float d = v3_dot( v0, both[j]->to_world[2] );
-
-         v3_muladds( both[j]->co[0], both[j]->to_world[2], d, 
-                     temp_alignments[j] );
-         v3_add( (v3f){0.0f,0.1f,0.0f}, temp_alignments[j], temp_alignments[j]);
-      }
-
-
-      for( int j=0; j<c0->path_count; j ++ ){
-         ent_path_index *index = mdl_arritm( &world->ent_path_index, 
-                                             c0->path_start+j );
-         ent_route_node *rn = mdl_arritm( &world->ent_route_node,
-                                          index->index );
-         if( j==0 || j==c0->path_count-1 )
-            if( j == 0 )
-               v3_copy( temp_alignments[0], p[1] );
-            else
-               v3_copy( temp_alignments[1], p[1] );
-         else
-            v3_copy( rn->co, p[1] );
-
-         p[1][3] = rn->ref_count;
-         p[1][3] -= (float)rn->ref_total * 0.5f;
-         rn->ref_count ++;
-
-         if( j+1 < c0->path_count ){
-            index = mdl_arritm( &world->ent_path_index, 
-                                c0->path_start+j+1 );
-            rn = mdl_arritm( &world->ent_route_node, index->index );
-
-            if( j+1 == c0->path_count-1 )
-               v3_lerp( p[1], temp_alignments[1], 0.5f, p[2] );
-            else
-               v3_lerp( p[1], rn->co, 0.5f, p[2] );
-
-            p[2][3] = rn->ref_count;
-            p[2][3] -= (float)rn->ref_total * 0.5f;
-         }
-         else{
-            v3_copy( end_gate->co[0], p[2] );
-            v3_add( (v3f){0.0f,0.1f,0.0f}, p[2], p[2] );
-            p[2][3] = collector->ref_count;
-
-            if( i == route->checkpoints_count-1)
-               p[2][3] -= 1.0f;
-
-            p[2][3] -= (float)collector->route_count * 0.5f;
-            //collector->ref_count ++;
-         }
-
-         /* p0,p1,p2 bezier patch is complete
-          * --------------------------------------*/
-         v3f surf0, surf2, n0, n2;
-
-         if( bh_closest_point( world->geo_bh, p[0], surf0, 5.0f ) == -1 )
-            v3_add( (v3f){0.0f,-0.1f,0.0f}, p[0], surf0 );
-
-         if( bh_closest_point( world->geo_bh, p[2], surf2, 5.0f ) == -1 )
-            v3_add( (v3f){0.0f,-0.1f,0.0f}, p[2], surf2 );
-
-         v3_sub( surf0, p[0], n0 );
-         v3_sub( surf2, p[2], n2 );
-         v3_normalize( n0 );
-         v3_normalize( n2 );
-
-         world_routes_place_curve( world, route, p, n0, n2, sc, pcbuf );
-
-         /* --- */
-         v4_copy( p[2], p[0] );
-      }
-   }
-
-   scene_copy_slice( sc, &route->sm );
-}
-
-VG_STATIC 
-struct world_surface *world_tri_index_surface( world_instance *world, 
-                                                 u32 index );
-
-VG_STATIC f64 world_routes_scatter_surface_points( world_instance *world,
-                                                   pointcloud_buffer *pcbuf,
-                                                   f32 rate )
-{
-   static f32 densities[] = {
-      [k_surface_prop_concrete] = 2.0f,
-      [k_surface_prop_grass]    = 0.8f,
-      [k_surface_prop_metal]    = 1.0f,
-      [k_surface_prop_wood]     = 2.5f,
-      [k_surface_prop_tiles]    = 4.0f
-   };
-
-   /* calculate total area */
-   f64 total_area = 0.0f;
-   for( u32 i=0; i<world->scene_geo.indice_count/3; i++ ){
-      u32 *tri = &world->scene_geo.arrindices[i*3];
-      struct world_surface *surf = world_tri_index_surface( world, tri[0] );
-
-      if( surf->info.shader == k_shader_boundary ||
-          surf->info.shader == k_shader_invisible ) continue;
-
-      if( !(surf->info.flags & k_material_flag_preview_visibile) ) continue;
-
-      scene_vert *va = &world->scene_geo.arrvertices[tri[0]],
-                 *vb = &world->scene_geo.arrvertices[tri[1]],
-                 *vc = &world->scene_geo.arrvertices[tri[2]];
-
-      v3f v0, v1, vn;
-      v3_sub( vb->co, va->co, v0 );
-      v3_sub( vc->co, va->co, v1 );
-      v3_cross( v0, v1, vn );
-      if( vn[1] < 0.0f ) continue;
-
-      f32 density = 1.0f;
-      if( surf->info.surface_prop < vg_list_size(densities) )
-         density = densities[surf->info.surface_prop];
-      total_area += v3_length(vn)*0.5f*density;
-   }
-   
-   f32 accum = 0.0f;
-
-   u8 colour[] = { 80,80,80,255 };
-   v3f light_dir = {0.3f,0.8f,0.1f};
-   v3_normalize( light_dir );
-
-   v3f inv_ext;
-   v3_sub( pcbuf->boundary[1], pcbuf->boundary[0], inv_ext );
-   v3_div( (v3f){1.0f,1.0f,1.0f}, inv_ext, inv_ext );
-
-   for( u32 i=0; i<world->scene_geo.indice_count/3; i++ ){
-      u32 *tri = &world->scene_geo.arrindices[i*3];
-      struct world_surface *surf = world_tri_index_surface( world, tri[0] );
-
-      if( surf->info.shader == k_shader_boundary ||
-          surf->info.shader == k_shader_invisible ) continue;
-
-      if( !(surf->info.flags & k_material_flag_preview_visibile) ) continue;
-
-      scene_vert *va = &world->scene_geo.arrvertices[tri[0]],
-                 *vb = &world->scene_geo.arrvertices[tri[1]],
-                 *vc = &world->scene_geo.arrvertices[tri[2]];
-
-      v3f v0, v1, vn;
-      v3_sub( vb->co, va->co, v0 );
-      v3_sub( vc->co, va->co, v1 );
-      v3_cross( v0, v1, vn );
-      if( vn[1] < 0.0f ) continue;
-
-      f32 density = 1.0f;
-      if( surf->info.surface_prop < vg_list_size(densities) )
-         density = densities[surf->info.surface_prop];
-
-      f32 area = v3_length(vn)*0.5f*density;
-      accum += area;
-
-      v3_normalize( vn );
-
-      while( accum > rate ){
-         accum -= rate;
-
-         if( pcbuf->count >= pcbuf->max ) return total_area;
-
-         v2f co = { vg_randf64(), vg_randf64() };
-         if( v2_length2(co) > 0.5f ){
-            co[0] = 1.0f-co[0];
-            co[1] = 1.0f-co[1];
-         }
-
-         v3f pt;
-         v3_muls( v0, co[0], pt );
-         v3_muladds( pt, v1, co[1], pt );
-         v3_add( va->co, pt, pt );
-
-         if( pt[1] < world->water.height ) continue;
-         pointcloud_vert *vert = &pcbuf->buf[ pcbuf->count ++ ];
-
-         v3f pos;
-         v3_sub( pt, pcbuf->boundary[0], pos );
-         v3_mul( pos, inv_ext, pos );
-
-         for( u32 i=0; i<3; i++ ){
-            vert->pos[i] = (pos[i]-0.5f) * 32767.0f;
-         }
-
-         static v4f colours[] = {
-            [k_surface_prop_concrete] = { 0.13, 0.15, 0.17, 1.0 },
-            [k_surface_prop_grass]    = { 0.07, 0.1, 0.14, 1.0 },
-            [k_surface_prop_metal]    = { 0.15, 0.19, 0.22, 1.0 },
-            [k_surface_prop_wood]     = { 0.1, 0.13, 0.17, 1.0 },
-            [k_surface_prop_tiles]    = { 0.05, 0.06, 0.07, 1.0 },
-         };
-
-         v4f col = {0.0f,0.0f,0.0f,0.0f};
-         if( surf->info.surface_prop < vg_list_size(colours) ){
-            v4_copy( colours[surf->info.surface_prop], col );
-         }
-
-         f32 brightness = v3_dot(vn,light_dir)*0.5f+0.5f;
-         v3_muls( col, brightness, col );
-
-         for( u32 j=0; j<4; j++ ){
-            vert->colour[j] = col[j] * 255.0f;
-         }
-      }
-   }
-
-   return total_area;
-}
-
-VG_STATIC void world_routes_surface_grid( world_instance *world,
-                                          pointcloud_buffer *pcbuf )
-{
-   i32 const k_gridlines = 32,
-             k_gridres   = 255;
-
-   v3f inv_ext;
-   v3_sub( pcbuf->boundary[1], pcbuf->boundary[0], inv_ext );
-   v3_div( (v3f){1.0f,1.0f,1.0f}, inv_ext, inv_ext );
-   v4f colour = {0.2f,0.2f,0.2f,1.0f};
-   v3f dir = {0.0f,-1.0f,0.0f};
-
-   for( u32 k=0; k<2; k++ ){
-      u32 a = k*2,
-          b = (k^0x1)*2;
-
-      for( i32 x=0; x<=k_gridlines; x++ ){
-         f32 t = (float)x / (float)k_gridlines,
-             px = vg_lerpf( pcbuf->boundary[0][a], pcbuf->boundary[1][a], t );
-
-         for( i32 z=0; z<=k_gridres; z++ ){
-            f32 tz = (float)z / (float)k_gridres,
-                pz = vg_lerpf(pcbuf->boundary[0][b],pcbuf->boundary[1][b], tz);
-
-            v3f ro, hit;
-            ro[a] = px;
-            ro[1] = 1000.0f;
-            ro[b] = pz;
-
-            bh_iter it;
-            bh_iter_init_ray( 0, &it, ro, dir, INFINITY );
-            i32 idx;
-
-            while( bh_next( world->geo_bh, &it, &idx ) ){
-               u32 *tri = &world->scene_geo.arrindices[ idx*3 ];
-               v3f vs[3];
-
-               for( u32 i=0; i<3; i++ ){
-                  v3_copy( world->scene_geo.arrvertices[tri[i]].co, vs[i] );
-               }
-               
-               f32 t;
-               if( ray_tri( vs, ro, dir, &t ) ){
-                  v3_muladds( ro, dir, t, hit );
-                  struct world_surface *m1 = 
-                     world_tri_index_surface( world, tri[0] );
-
-                  if( !(m1->info.flags & k_material_flag_preview_visibile) )
-                     continue;
-
-                  if( world->water.enabled )
-                     if( hit[1] < world->water.height )
-                        continue;
-
-                  if( pcbuf->count >= pcbuf->max ) return;
-
-                  pointcloud_vert *vert = &pcbuf->buf[ pcbuf->count ++ ];
-
-                  v3f co;
-                  v3_sub( hit, pcbuf->boundary[0], co );
-                  v3_mul( co, inv_ext, co );
-
-                  for( u32 i=0; i<3; i++ ){
-                     vert->pos[i] = (co[i]-0.5f) * 32767.0f;
-                  }
-
-                  for( u32 i=0; i<4; i++ ){
-                     vert->colour[i] = colour[i] * 255.0f;
-                  }
-               }
-            }
-         }
-      }
-   }
-}
-
-/* 
- * Create the strips of colour that run through the world along course paths
- */
-VG_STATIC void world_routes_generate( world_instance *world )
-{
-   vg_info( "Generating route meshes\n" );
-   vg_async_stall();
-
-   vg_rand_seed( 2000 );
-   vg_async_item *call_scene = scene_alloc_async( &world->scene_lines, 
-                                                  &world->mesh_route_lines,
-                                                  200000, 300000 );
-   vg_async_item *call_pointcloud = vg_async_alloc( 
-         sizeof(pointcloud_buffer) + 
-         sizeof(pointcloud_vert)*POINTCLOUD_POINTS );
-   pointcloud_buffer *pcbuf = call_pointcloud->payload;
-   pcbuf->count = 0;
-   pcbuf->max = POINTCLOUD_POINTS;
-   pcbuf->op = k_pointcloud_op_clear;
-
-   v3f ext, mid, v0;
-   v3_sub( world->scene_geo.bbx[1], world->scene_geo.bbx[0], ext );
-   f32 maxe = v3_maxf( ext );
-   v3_fill( v0, maxe * 0.5f );
-   v3_muladds( world->scene_geo.bbx[0], ext, 0.5f, mid );
-   v3_add( mid, v0, pcbuf->boundary[1] );
-   v3_sub( mid, v0, pcbuf->boundary[0] );
-
-   for( u32 i=0; i<mdl_arrcount(&world->ent_gate); i++ ){
-      ent_gate *gate = mdl_arritm( &world->ent_gate, i );
-      gate->ref_count = 0;
-      gate->route_count = 0;
-   }
-
-   for( u32 i=0; i<mdl_arrcount(&world->ent_route_node); i++ ){
-      ent_route_node *rn = mdl_arritm( &world->ent_route_node, i );
-      rn->ref_count = 0;
-      rn->ref_total = 0;
-   }
-
-   for( u32 k=0; k<mdl_arrcount(&world->ent_route); k++ ){
-      ent_route *route = mdl_arritm( &world->ent_route, k );
-
-      for( int i=0; i<route->checkpoints_count; i++ ){
-         int i0 = route->checkpoints_start+i,
-             i1 = route->checkpoints_start+((i+1)%route->checkpoints_count);
-
-         ent_checkpoint *c0 = mdl_arritm(&world->ent_checkpoint, i0),
-                        *c1 = mdl_arritm(&world->ent_checkpoint, i1);
-
-         ent_gate *start_gate = mdl_arritm( &world->ent_gate, c0->gate_index );
-         start_gate = mdl_arritm( &world->ent_gate, start_gate->target );
-         start_gate->route_count ++;
-
-         if( !c0->path_count )
-            continue;
-
-         for( int j=0; j<c0->path_count; j ++ ){
-            ent_path_index *index = mdl_arritm( &world->ent_path_index, 
-                                                c0->path_start+j );
-            ent_route_node *rn = mdl_arritm( &world->ent_route_node,
-                                             index->index );
-            rn->ref_total ++;
-         }
-      }
-   }
-
-   for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
-      world_routes_gen_meshes( world, i, &world->scene_lines, pcbuf );
-   }
-
-   f64 area = 0.0;
-   area = world_routes_scatter_surface_points( world, pcbuf, 16.0f );
-   world_routes_surface_grid( world, pcbuf );
-   vg_info( "Distrubuted %u points over %fkm^2!\n", pcbuf->count, area/1e6f );
-
-   vg_async_dispatch( call_scene, async_scene_upload );
-   vg_async_dispatch( call_pointcloud, async_pointcloud_sub );
-
-   world_routes_clear( world );
-}
-
-/* load all routes from model header */
-VG_STATIC void world_routes_ent_init( world_instance *world )
-{
-   vg_info( "Initializing routes\n" );
-
-   for( u32 i=0; i<mdl_arrcount(&world->ent_gate); i++ ){
-      ent_gate *gate = mdl_arritm( &world->ent_gate, i );
-      for( u32 j=0; j<4; j++ ){
-         gate->routes[j] = 0xffff;
-      }
-   }
-
-   for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
-      ent_route *route = mdl_arritm(&world->ent_route,i);
-      mdl_transform_m4x3( &route->transform, route->board_transform );
-
-      route->official_track_id = 0xffffffff;
-      for( u32 j=0; j<vg_list_size(track_infos); j ++ ){
-         if( !strcmp(track_infos[j].name, 
-                     mdl_pstr(&world->meta,route->pstr_name))){
-            route->official_track_id = j;
-         }
-      }
-
-      for( u32 j=0; j<route->checkpoints_count; j++ ){
-         u32 id = route->checkpoints_start + j;
-         ent_checkpoint *cp = mdl_arritm(&world->ent_checkpoint,id);
-         
-         ent_gate *gate = mdl_arritm( &world->ent_gate, cp->gate_index );
-
-         for( u32 k=0; k<4; k++ ){
-            if( gate->routes[k] == 0xffff ){
-               gate->routes[k] = i;
-               break;
-            }
-         }
-
-         if( gate->type == k_gate_type_teleport ){
-            gate = mdl_arritm(&world->ent_gate, gate->target );
-
-            for( u32 k=0; k<4; k++ ){
-               if( gate->routes[k] == i ){
-                  vg_error( "already assigned route to gate\n" );
-                  break;
-               }
-               if( gate->routes[k] == 0xffff ){
-                  gate->routes[k] = i;
-                  break;
-               }
-            }
-         }
-      }
-   }
-
-   for( u32 i=0; i<mdl_arrcount(&world->ent_gate); i++ ){
-      ent_gate *gate = mdl_arritm( &world->ent_gate, i );
-   }
-
-   world_routes_clear( world );
-}
-
-/* 
- * -----------------------------------------------------------------------------
- *                                    Events
- * -----------------------------------------------------------------------------
- */
-
-VG_STATIC void world_routes_init(void)
-{
-   world_global.current_run_version = 200;
-   world_global.time = 300.0;
-   world_global.last_use = 0.0;
-
-   shader_scene_route_register();
-   shader_routeui_register();
-}
-
-VG_STATIC void world_routes_update( world_instance *world )
-{
-   world_global.time += vg.time_delta;
-
-   for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
-      ent_route *route = mdl_arritm( &world->ent_route, i );
-      
-      int target = route->active_checkpoint == 0xffff? 0: 1;
-      route->factive = vg_lerpf( route->factive, target, 0.6f*vg.time_delta );
-   }
-
-   for( u32 i=0; i<world_global.text_particle_count; i++ ){
-      struct text_particle *particle = &world_global.text_particles[i];
-      rb_object_debug( &particle->obj, VG__RED );
-   }
-}
-
-VG_STATIC void world_routes_fixedupdate( world_instance *world )
-{
-   rb_solver_reset();
-
-   for( u32 i=0; i<world_global.text_particle_count; i++ ){
-      struct text_particle *particle = &world_global.text_particles[i];
-
-      if( rb_global_has_space() ){
-         rb_ct *buf = rb_global_buffer();
-
-         int l = rb_sphere__scene( particle->obj.rb.to_world,
-                                   &particle->obj.inf.sphere,
-                                   NULL, &world->rb_geo.inf.scene, buf );
-
-         for( int j=0; j<l; j++ ){
-            buf[j].rba = &particle->obj.rb;
-            buf[j].rbb = &world->rb_geo.rb;
-         }
-
-         rb_contact_count += l;
-      }
-   }
-
-   rb_presolve_contacts( rb_contact_buffer, rb_contact_count );
-
-   for( int i=0; i<rb_contact_count; i++ ){
-      rb_contact_restitution( rb_contact_buffer+i, vg_randf64() );
-   }
-
-   for( int i=0; i<6; i++ ){
-      rb_solve_contacts( rb_contact_buffer, rb_contact_count );
-   }
-
-   for( u32 i=0; i<world_global.text_particle_count; i++ ){
-      struct text_particle *particle = &world_global.text_particles[i];
-      rb_iter( &particle->obj.rb );
-   }
-
-   for( u32 i=0; i<world_global.text_particle_count; i++ ){
-      struct text_particle *particle = &world_global.text_particles[i];
-      rb_update_transform( &particle->obj.rb );
-   }
-}
-
-VG_STATIC void bind_terrain_noise(void);
-VG_STATIC void world_bind_light_array( world_instance *world,
-                                       GLuint shader, GLuint location, 
-                                       int slot );
-VG_STATIC void world_bind_light_index( world_instance *world,
-                                       GLuint shader, GLuint location, 
-                                       int slot );
-
-VG_STATIC void world_routes_update_timer_texts( world_instance *world )
-{
-   world_global.timer_text_count = 0;
-
-   for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
-      ent_route *route = mdl_arritm( &world->ent_route, i );
-
-      if( route->active_checkpoint != 0xffff ){
-         u32 next = route->active_checkpoint+1;
-             next = next % route->checkpoints_count;
-             next += route->checkpoints_start;
-
-         ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint, next );
-         ent_gate *gate = mdl_arritm( &world->ent_gate, cp->gate_index );
-         ent_gate *dest = mdl_arritm( &world->ent_gate, gate->target );
-         
-         u32 j=0;
-         for( ; j<4; j++ ){
-            if( dest->routes[j] == i ){
-               break;
-            }
-         }
-
-         float h0 = 0.8f,
-               h1 = 1.2f,
-               depth = 0.4f,
-               size = 0.4f;
-
-         struct timer_text *text = 
-            &world_global.timer_texts[ world_global.timer_text_count ++ ];
-
-         text->gate = gate;
-         text->route = route;
-
-         if( route->valid_checkpoints >= route->checkpoints_count ){
-            double lap_time = world_global.time - route->timing_base,
-                   time_centiseconds = lap_time * 100.0;
-
-            if( time_centiseconds > (float)0xfffe ) time_centiseconds = 0.0;
-
-            u16 centiseconds = time_centiseconds,
-                seconds      = centiseconds / 100,
-                minutes      = seconds / 60;
-
-            centiseconds %= 100;
-            seconds     %= 60;
-            minutes     %= 60;
-
-            if( minutes > 9 ) minutes = 9;
-            
-            int j=0;
-            if( minutes ){
-               highscore_intr( text->text, minutes, 1, ' ' ); j++;
-               text->text[j++] = ':';
-            }
-            
-            if( seconds >= 10 || minutes ){
-               highscore_intr( text->text+j, seconds, 2, '0' ); j+=2;
-            }
-            else{
-               highscore_intr( text->text+j, seconds, 1, '0' ); j++;
-            }
-
-            text->text[j++] = '.';
-            highscore_intr( text->text+j, centiseconds, 1, '0' ); j++;
-            text->text[j] = '\0';
-         }
-         else{
-            highscore_intr( text->text, route->valid_checkpoints, 1, ' ' );
-            text->text[1] = '/';
-            highscore_intr( text->text+2, route->checkpoints_count+1, 1, ' ' );
-            text->text[3] = '\0';
-         }
-
-         float align_r  = font3d_string_width( &world_global.font, 0, 
-                                               text->text );
-               align_r *= size;
-
-         v3f positions[] = {
-            { -0.92f, h0, depth },
-            {  0.92f - align_r, h0, depth },
-            { -0.92f, h1, depth },
-            {  0.92f - align_r, h1, depth },
-         };
-
-         if( dest->route_count == 1 ){
-            positions[0][0] = -align_r*0.5f;
-            positions[0][1] = h1;
-         }
-
-         m3x3_copy( gate->to_world, text->transform );
-         float ratio = v3_length(text->transform[0]) / 
-                        v3_length(text->transform[1]);
-
-         m3x3_scale( text->transform, (v3f){ size, size*ratio, 0.1f } );
-         m4x3_mulv( gate->to_world, positions[j], text->transform[3] );
-      }
-   }
-}
 
+VG_STATIC void world_routes_init(void);
 VG_STATIC void world_routes_fracture( world_instance *world, ent_gate *gate,
-                                      v3f imp_co, v3f imp_v )
-{
-   world_global.text_particle_count = 0;
-   
-   for( u32 i=0; i<world_global.timer_text_count; i++ ){
-      struct timer_text *text = &world_global.timer_texts[i];
-
-      if( text->gate != gate ) continue;
-
-      m4x3f transform;
-      m4x3_mul( gate->transport, text->transform, transform );
-
-      v3f co, s;
-      v4f q;
-      m4x3_decompose( transform, co, q, s );
-
-      v3f offset;
-      v3_zero( offset );
-
-      v4f colour;
-      float brightness = 0.3f + world->ub_lighting.g_day_phase;
-      v3_muls( text->route->colour, brightness, colour );
-      colour[3] = 1.0f-text->route->factive;
-
-      for( u32 j=0;; j++ ){
-         char c = text->text[j];
-         if( !c ) break;
-
-         ent_glyph *glyph = font3d_glyph( &world_global.font, 0, c );
-         if( !glyph ) continue;
-
-         if( c >= (u32)'0' && c <= (u32)'9' && glyph->indice_count ){
-            struct text_particle *particle = 
-               &world_global.text_particles[world_global.text_particle_count++];
-
-            particle->glyph = glyph;
-            v4_copy( colour, particle->colour );
-
-            v3f origin;
-            v2_muls( glyph->size, 0.5f, origin );
-            origin[2] = -0.5f;
-
-            v3f world_co;
-
-            v3_add( offset, origin, world_co );
-            m4x3_mulv( transform, world_co, world_co );
-
-            float r = vg_maxf( s[0]*glyph->size[0], s[1]*glyph->size[1] )*0.5f;
-
-            m3x3_identity( particle->mlocal );
-            m3x3_scale( particle->mlocal, s );
-            origin[2] *= s[2];
-            v3_muls( origin, -1.0f, particle->mlocal[3] );
-
-            v3_copy( world_co, particle->obj.rb.co );
-            v3_muls( imp_v, 1.0f+vg_randf64(), particle->obj.rb.v );
-            particle->obj.rb.v[1] += 2.0f;
-
-            v4_copy( q, particle->obj.rb.q );
-            particle->obj.rb.w[0] = vg_randf64()*2.0f-1.0f;
-            particle->obj.rb.w[1] = vg_randf64()*2.0f-1.0f;
-            particle->obj.rb.w[2] = vg_randf64()*2.0f-1.0f;
-
-            particle->obj.type = k_rb_shape_sphere;
-            particle->obj.inf.sphere.radius = r*0.6f;
-
-            rb_init_object( &particle->obj );
-         }
-         offset[0] += glyph->size[0];
-      }
-   }
-}
-
+                                      v3f imp_co, v3f imp_v );
+VG_STATIC void world_routes_activate_entry_gate( world_instance *world, 
+                                                 ent_gate *rg );
 VG_STATIC void render_world_routes( world_instance *world, camera *cam, 
-                                    int layer_depth )
-{
-   m4x3f identity_matrix;
-   m4x3_identity( identity_matrix );
-
-   shader_scene_route_use();
-   shader_scene_route_uTexGarbage(0);
-   world_link_lighting_ub( world, _shader_scene_route.id );
-   world_bind_position_texture( world, _shader_scene_route.id, 
-                        _uniform_scene_route_g_world_depth, 2 );
-   world_bind_light_array( world, _shader_scene_route.id,
-                        _uniform_scene_route_uLightsArray, 3 );
-   world_bind_light_index( world, _shader_scene_route.id,
-                                 _uniform_scene_route_uLightsIndex, 4 );
-   bind_terrain_noise();
-
-   shader_scene_route_uPv( cam->mtx.pv );
-   shader_scene_route_uPvmPrev( cam->mtx_prev.pv );
-   shader_scene_route_uMdl( identity_matrix );
-   shader_scene_route_uCamera( cam->transform[3] );
-
-   mesh_bind( &world->mesh_route_lines );
-
-   for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
-      ent_route *route = mdl_arritm( &world->ent_route, i );
-
-      v4f colour;
-      v3_lerp( (v3f){0.7f,0.7f,0.7f}, route->colour, route->factive, colour );
-      colour[3] = route->factive*0.2f;
-
-      shader_scene_route_uColour( colour );
-      mdl_draw_submesh( &route->sm );
-   }
-
-   /* timers
-    * ---------------------------------------------------- */
-   if( layer_depth == 0 ){
-      font3d_bind( &world_global.font, cam );
-
-      for( u32 i=0; i<world_global.timer_text_count; i++ ){
-         struct timer_text *text = &world_global.timer_texts[i];
-
-         v4f colour;
-         float brightness = 0.3f + world->ub_lighting.g_day_phase;
-         v3_muls( text->route->colour, brightness, colour );
-         colour[3] = 1.0f-text->route->factive;
-
-         shader_model_font_uColour( colour );
-         font3d_simple_draw( &world_global.font, 0, text->text, 
-                             cam, text->transform );
-      }
-
-      shader_model_font_uOffset( (v4f){0.0f,0.0f,0.0f,1.0f} );
-
-      for( u32 i=0; i<world_global.text_particle_count; i++ ){
-         struct text_particle *particle = &world_global.text_particles[i];
-
-         m4x4f prev_mtx;
-
-         m4x3_expand( particle->mdl, prev_mtx );
-         m4x4_mul( cam->mtx_prev.pv, prev_mtx, prev_mtx );
-
-         shader_model_font_uPvmPrev( prev_mtx );
-
-         v4f q;
-         m4x3f model;
-         rb_extrapolate( &particle->obj.rb, model[3], q );
-         q_m3x3( q, model );
-
-         m4x3_mul( model, particle->mlocal, particle->mdl );
-         shader_model_font_uMdl( particle->mdl );
-         shader_model_font_uColour( particle->colour );
-
-         mesh_drawn( particle->glyph->indice_start, 
-                     particle->glyph->indice_count );
-      }
-   }
-
-   /* gate markers 
-    * ---------------------------------------------------- */
-
-   shader_model_gate_use();
-   shader_model_gate_uPv( cam->mtx.pv );
-   shader_model_gate_uCam( cam->pos );
-   shader_model_gate_uTime( vg.time*0.25f );
-   shader_model_gate_uInvRes( (v2f){
-         1.0f / (float)vg.window_x,
-         1.0f / (float)vg.window_y });
-
-   mesh_bind( &world_global.mesh_gate );
-
-   /* skip writing into the motion vectors for this */
-   glDrawBuffers( 1, (GLenum[]){ GL_COLOR_ATTACHMENT0 } );
-
-   for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
-      ent_route *route = mdl_arritm( &world->ent_route, i );
-
-      if( route->active_checkpoint != 0xffff ){
-         v4f colour;
-         float brightness = 0.3f + world->ub_lighting.g_day_phase;
-         v3_muls( route->colour, brightness, colour );
-         colour[3] = 1.0f-route->factive;
-
-         shader_model_gate_uColour( colour );
-
-         u32 next = route->active_checkpoint+1+layer_depth;
-             next = next % route->checkpoints_count;
-             next += route->checkpoints_start;
-
-         ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint, next );
-         ent_gate *gate = mdl_arritm( &world->ent_gate, cp->gate_index );
-         shader_model_gate_uMdl( gate->to_world );
+                                    int layer_depth );
 
-         for( u32 j=0; j<4; j++ ){
-            if( gate->routes[j] == i ){
-               mdl_draw_submesh( &world_global.sm_gate_marker[j] );
-               break;
-            }
-         }
-      }
-   }
-   glDrawBuffers( 2, (GLenum[]){ GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 } );
-}
+VG_STATIC void world_gen_routes_ent_init(void);
+VG_STATIC void world_gen_routes_generate(void);
+VG_STATIC void world_routes_update_timer_texts( world_instance *world );
+VG_STATIC void world_routes_update( world_instance *world );
+VG_STATIC void world_routes_fixedupdate( world_instance *world );
 
 #endif /* ROUTES_H */
diff --git a/world_sfd.c b/world_sfd.c
new file mode 100644 (file)
index 0000000..9fe2d68
--- /dev/null
@@ -0,0 +1,213 @@
+#ifndef SFD_C
+#define SFD_C
+
+#include "world_sfd.h"
+#include "shaders/scene_scoretext.h"
+#include "shaders/scene_vertex_blend.h"
+
+static f32 sfd_encode_glyph( char c )
+{
+   int value = 0;
+   if( c >= 'a' && c <= 'z' )
+      value = c-'a'+11;
+   else if( c >= '0' && c <= '9' )
+      value = c-'0'+1;
+   else if( c >= 'A' && c <= 'Z' )
+      value = c-'A'+11;
+   else if( c >= '\x01' && c <= '\x01'+10 )
+      value = 63-c;
+   else{
+      int base = 11+26;
+
+      switch( c ){
+         case '!': value=base+0; break;
+         case '?': value=base+1; break;
+         case ',': value=base+2; break;
+         case '.': value=base+3; break;
+         case '#': value=base+4; break;
+         case '$': value=base+5; break;
+         case '%': value=base+6; break;
+         case '*': value=base+7; break;
+         case '+': value=base+8; break;
+         case '-': value=base+9; break;
+         case '/': value=base+10; break;
+         case ':': value=base+11; break;
+         default: value=0; break;
+      }
+   }
+
+   return (float)value;
+}
+
+VG_STATIC void sfd_encode( u32 row, const char *str )
+{
+   int end=0;
+   u32 row_h = world_sfd.h -1 -row;
+
+   for( int i=0; i<world_sfd.w; i++ ){
+      u32 idx = (world_sfd.w*row_h + i) * 2;
+
+      if( end ){
+         world_sfd.buffer[idx] = 0.0f;
+      }
+      else{
+         if( !str[i] )
+            end = 1;
+
+         world_sfd.buffer[idx] = sfd_encode_glyph( str[i] );
+      }
+   }
+}
+
+VG_STATIC void sfd_update(void)
+{
+   for( int i=0; i<world_sfd.w*world_sfd.h; i++ ){
+      float *target = &world_sfd.buffer[i*2+0],
+            *cur =    &world_sfd.buffer[i*2+1];
+      
+      float const rate = vg.time_delta * 15.2313131414f;
+      float d1 = *target-*cur;
+      
+      if( fabsf(d1) > rate ){
+         *cur += rate;
+         if( *cur > 60.0f )
+            *cur -= 60.0f;
+      }
+      else
+         *cur = *target;
+   }
+}
+
+VG_STATIC void bind_terrain_noise(void);
+VG_STATIC void sfd_render( world_instance *world, camera *cam, m4x3f transform )
+{
+   mesh_bind( &world_sfd.mesh_display );
+   shader_scene_scoretext_use();
+   shader_scene_scoretext_uTexMain(1);
+
+   world_link_lighting_ub( world, _shader_scene_scoretext.id );
+   world_bind_position_texture( world, _shader_scene_scoretext.id, 
+                        _uniform_scene_scoretext_g_world_depth, 2 );
+   world_bind_light_array( world, _shader_scene_scoretext.id,
+                        _uniform_scene_scoretext_uLightsArray, 3 );
+   world_bind_light_index( world, _shader_scene_scoretext.id,
+                           _uniform_scene_scoretext_uLightsIndex, 4 );
+
+   bind_terrain_noise();
+
+   glActiveTexture( GL_TEXTURE1 );
+   glBindTexture( GL_TEXTURE_2D, world_sfd.tex_scoretex );
+
+   m4x4f pvm_prev;
+   m4x3_expand( transform, pvm_prev );
+   m4x4_mul( cam->mtx_prev.pv, pvm_prev, pvm_prev );
+
+   shader_scene_scoretext_uPv( cam->mtx.pv );
+   shader_scene_scoretext_uPvmPrev( pvm_prev );
+   shader_scene_scoretext_uMdl( transform );
+   shader_scene_scoretext_uCamera( cam->transform[3] );
+
+   for( int y=0;y<world_sfd.h; y++ ){
+      for( int x=0; x<world_sfd.w; x++ ){
+         float value = world_sfd.buffer[(y*world_sfd.w+x)*2+1];
+         shader_scene_scoretext_uInfo( (v3f){ x,y, value } );
+         mesh_draw( &world_sfd.mesh_display );
+      }
+   }
+
+   shader_scene_vertex_blend_use();
+   shader_scene_vertex_blend_uTexGarbage(0);
+   shader_scene_vertex_blend_uTexGradients(1);
+   world_link_lighting_ub( world, _shader_scene_vertex_blend.id );
+   world_bind_position_texture( world, _shader_scene_vertex_blend.id, 
+                                _uniform_scene_vertex_blend_g_world_depth, 2 );
+   world_bind_light_array( world, _shader_scene_vertex_blend.id,
+                                _uniform_scene_vertex_blend_uLightsArray, 3 );
+   world_bind_light_index( world, _shader_scene_vertex_blend.id,
+                                _uniform_scene_vertex_blend_uLightsIndex, 4 );
+   bind_terrain_noise();
+   glActiveTexture( GL_TEXTURE1 );
+   glBindTexture( GL_TEXTURE_2D, world_sfd.tex_scoretex );
+
+   shader_scene_vertex_blend_uPv( cam->mtx.pv );
+   shader_scene_vertex_blend_uPvmPrev( pvm_prev );
+   shader_scene_vertex_blend_uMdl( transform );
+   shader_scene_vertex_blend_uCamera( cam->transform[3] );
+   
+   mesh_bind( &world_sfd.mesh_base );
+   mdl_draw_submesh( &world_sfd.sm_base );
+}
+
+VG_STATIC int world_sfd_test( int argc, const char *argv[] )
+{
+   if( argc == 2 ){
+      int row = vg_min( vg_max(atoi(argv[0]),0), world_sfd.h);
+      sfd_encode( row, argv[1] );
+   }
+
+   return 0;
+}
+
+VG_STATIC void world_sfd_init(void)
+{
+   vg_info( "world_sfd_init\n" );
+   shader_scene_scoretext_register();
+   vg_console_reg_cmd( "sfd", world_sfd_test, NULL );
+
+   vg_linear_clear( vg_mem.scratch );
+
+   mdl_context mscoreboard;
+   mdl_open( &mscoreboard, "models/rs_scoretext.mdl", vg_mem.scratch );
+   mdl_load_metadata_block( &mscoreboard, vg_mem.scratch );
+   mdl_async_load_glmesh( &mscoreboard, &world_sfd.mesh_base );
+
+   mdl_load_mesh_block( &mscoreboard, vg_mem.scratch );
+
+   scene_context *scene = &world_sfd.scene;
+   vg_async_item *call = scene_alloc_async( scene, &world_sfd.mesh_display,
+                                            3000, 8000 );
+
+
+   mdl_mesh *m_backer = mdl_find_mesh( &mscoreboard, "backer" ),
+            *m_card   = mdl_find_mesh( &mscoreboard, "score_card" );
+
+   mdl_submesh 
+      *sm_backer = mdl_arritm( &mscoreboard.submeshs, m_backer->submesh_start ),
+      *sm_card   = mdl_arritm( &mscoreboard.submeshs, m_card->submesh_start );
+   world_sfd.sm_base = *sm_backer;
+
+   m4x3f identity;
+   m4x3_identity( identity );
+
+   for( int i=0;i<4;i++ ){
+      u32 vert_start = scene->vertex_count;
+      scene_add_mdl_submesh( scene, &mscoreboard, sm_card, identity );
+
+      for( int j=0; j<sm_card->vertex_count; j++ ){
+         scene_vert *vert = &scene->arrvertices[ vert_start+j ];
+
+         float const k_glyph_uvw = 1.0f/64.0f;
+         vert->uv[0] -= k_glyph_uvw * (float)(i-1);
+         vert->norm[3] = i*42;
+      }
+   }
+
+   vg_async_dispatch( call, async_scene_upload );
+   vg_tex2d_load_qoi_async_file( "textures/scoretext.qoi", 
+                                 VG_TEX2D_CLAMP|VG_TEX2D_NEAREST,
+                                 &world_sfd.tex_scoretex );
+
+   mdl_close( &mscoreboard );
+
+   int w = 27,
+       h = 13;
+
+   world_sfd.w = w;
+   world_sfd.h = h;
+   world_sfd.buffer = vg_linear_alloc( vg_mem.rtmemory, 2*w*h*sizeof(float) );
+
+   for( int i=0; i<w*h*2; i++ )
+      world_sfd.buffer[i] = 0.0f;
+}
+
+#endif /* WORLD_SFD_C */
index 18789c7e841320a235726cfecad66ae2deaa71d2..e49d8ff4f0c1a63a0e7bc7067da0fb88716871a3 100644 (file)
 #define SFD_H
 
 #include "world.h"
-#include "shaders/scene_scoretext.h"
-#include "shaders/scene_vertex_blend.h"
 
-GLuint tex_scoretex;
-float sfd_encode_glyph( char c )
-{
-   int value = 0;
-   if( c >= 'a' && c <= 'z' )
-      value = c-'a'+11;
-   else if( c >= '0' && c <= '9' )
-      value = c-'0'+1;
-   else if( c >= 'A' && c <= 'Z' )
-      value = c-'A'+11;
-   else if( c >= '\x01' && c <= '\x01'+10 )
-      value = 63-c;
-   else{
-      int base = 11+26;
+struct world_sfd{
+   GLuint tex_scoretex;
 
-      switch( c ){
-         case '!': value=base+0; break;
-         case '?': value=base+1; break;
-         case ',': value=base+2; break;
-         case '.': value=base+3; break;
-         case '#': value=base+4; break;
-         case '$': value=base+5; break;
-         case '%': value=base+6; break;
-         case '*': value=base+7; break;
-         case '+': value=base+8; break;
-         case '-': value=base+9; break;
-         case '/': value=base+10; break;
-         case ':': value=base+11; break;
-         default: value=0; break;
-      }
-   }
+   glmesh mesh_base, mesh_display;
+   mdl_submesh sm_base;
+   u32 active_route_board;
+   scene_context scene;
 
-   return (float)value;
+   u32 w, h;
+   float *buffer;
 }
+static world_sfd;
+VG_STATIC void world_sfd_init(void);
 
-VG_STATIC void sfd_encode( u32 row, const char *str )
-{
-   int end=0;
-   u32 row_h = world_global.sfd.h -1 -row;
-
-   for( int i=0; i<world_global.sfd.w; i++ ){
-      u32 idx = (world_global.sfd.w*row_h + i) * 2;
-
-      if( end ){
-         world_global.sfd.buffer[idx] = 0.0f;
-      }
-      else{
-         if( !str[i] )
-            end = 1;
-
-         world_global.sfd.buffer[idx] = sfd_encode_glyph( str[i] );
-      }
-   }
-}
-
-VG_STATIC void sfd_update(void)
-{
-   for( int i=0; i<world_global.sfd.w*world_global.sfd.h; i++ ){
-      float *target = &world_global.sfd.buffer[i*2+0],
-            *cur =    &world_global.sfd.buffer[i*2+1];
-      
-      float const rate = vg.time_delta * 15.2313131414f;
-      float d1 = *target-*cur;
-      
-      if( fabsf(d1) > rate ){
-         *cur += rate;
-         if( *cur > 60.0f )
-            *cur -= 60.0f;
-      }
-      else
-         *cur = *target;
-   }
-}
-
-VG_STATIC void bind_terrain_noise(void);
-VG_STATIC void sfd_render( world_instance *world, camera *cam, m4x3f transform )
-{
-   mesh_bind( &world_global.sfd.mesh_display );
-   shader_scene_scoretext_use();
-   shader_scene_scoretext_uTexMain(1);
-
-   world_link_lighting_ub( world, _shader_scene_scoretext.id );
-   world_bind_position_texture( world, _shader_scene_scoretext.id, 
-                        _uniform_scene_scoretext_g_world_depth, 2 );
-   world_bind_light_array( world, _shader_scene_scoretext.id,
-                        _uniform_scene_scoretext_uLightsArray, 3 );
-   world_bind_light_index( world, _shader_scene_scoretext.id,
-                           _uniform_scene_scoretext_uLightsIndex, 4 );
-
-   bind_terrain_noise();
-
-   glActiveTexture( GL_TEXTURE1 );
-   glBindTexture( GL_TEXTURE_2D, tex_scoretex );
-
-   m4x4f pvm_prev;
-   m4x3_expand( transform, pvm_prev );
-   m4x4_mul( cam->mtx_prev.pv, pvm_prev, pvm_prev );
-
-   shader_scene_scoretext_uPv( cam->mtx.pv );
-   shader_scene_scoretext_uPvmPrev( pvm_prev );
-   shader_scene_scoretext_uMdl( transform );
-   shader_scene_scoretext_uCamera( cam->transform[3] );
-
-   for( int y=0;y<world_global.sfd.h; y++ ){
-      for( int x=0; x<world_global.sfd.w; x++ ){
-         float value = world_global.sfd.buffer[(y*world_global.sfd.w+x)*2+1];
-         shader_scene_scoretext_uInfo( (v3f){ x,y, value } );
-         mesh_draw( &world_global.sfd.mesh_display );
-      }
-   }
-
-   shader_scene_vertex_blend_use();
-   shader_scene_vertex_blend_uTexGarbage(0);
-   shader_scene_vertex_blend_uTexGradients(1);
-   world_link_lighting_ub( world, _shader_scene_vertex_blend.id );
-   world_bind_position_texture( world, _shader_scene_vertex_blend.id, 
-                                _uniform_scene_vertex_blend_g_world_depth, 2 );
-   world_bind_light_array( world, _shader_scene_vertex_blend.id,
-                                _uniform_scene_vertex_blend_uLightsArray, 3 );
-   world_bind_light_index( world, _shader_scene_vertex_blend.id,
-                                _uniform_scene_vertex_blend_uLightsIndex, 4 );
-   bind_terrain_noise();
-   glActiveTexture( GL_TEXTURE1 );
-   glBindTexture( GL_TEXTURE_2D, tex_scoretex );
-
-   shader_scene_vertex_blend_uPv( cam->mtx.pv );
-   shader_scene_vertex_blend_uPvmPrev( pvm_prev );
-   shader_scene_vertex_blend_uMdl( transform );
-   shader_scene_vertex_blend_uCamera( cam->transform[3] );
-   
-   mesh_bind( &world_global.sfd.mesh_base );
-   mdl_draw_submesh( &world_global.sfd.sm_base );
-}
-
-VG_STATIC int world_sfd_test( int argc, const char *argv[] )
-{
-   if( argc == 2 ){
-      int row = vg_min( vg_max(atoi(argv[0]),0), world_global.sfd.h);
-      sfd_encode( row, argv[1] );
-   }
-
-   return 0;
-}
-
-VG_STATIC void world_sfd_init(void)
-{
-   vg_info( "world_sfd_init\n" );
-   shader_scene_scoretext_register();
-   vg_console_reg_cmd( "sfd", world_sfd_test, NULL );
-
-   vg_linear_clear( vg_mem.scratch );
-
-   mdl_context mscoreboard;
-   mdl_open( &mscoreboard, "models/rs_scoretext.mdl", vg_mem.scratch );
-   mdl_load_metadata_block( &mscoreboard, vg_mem.scratch );
-   mdl_async_load_glmesh( &mscoreboard, &world_global.sfd.mesh_base );
-
-   mdl_load_mesh_block( &mscoreboard, vg_mem.scratch );
-
-   scene_context *scene = &world_global.sfd.scene;
-   vg_async_item *call = scene_alloc_async( scene,
-                                            &world_global.sfd.mesh_display,
-                                            3000, 8000 );
-
-
-   mdl_mesh *m_backer = mdl_find_mesh( &mscoreboard, "backer" ),
-            *m_card   = mdl_find_mesh( &mscoreboard, "score_card" );
-
-   mdl_submesh 
-      *sm_backer = mdl_arritm( &mscoreboard.submeshs, m_backer->submesh_start ),
-      *sm_card   = mdl_arritm( &mscoreboard.submeshs, m_card->submesh_start );
-   world_global.sfd.sm_base = *sm_backer;
-
-   m4x3f identity;
-   m4x3_identity( identity );
-
-   for( int i=0;i<4;i++ ){
-      u32 vert_start = scene->vertex_count;
-      scene_add_mdl_submesh( scene, &mscoreboard, sm_card, identity );
-
-      for( int j=0; j<sm_card->vertex_count; j++ ){
-         scene_vert *vert = &scene->arrvertices[ vert_start+j ];
-
-         float const k_glyph_uvw = 1.0f/64.0f;
-         vert->uv[0] -= k_glyph_uvw * (float)(i-1);
-         vert->norm[3] = i*42;
-      }
-   }
-
-   vg_async_dispatch( call, async_scene_upload );
-   vg_tex2d_load_qoi_async_file( "textures/scoretext.qoi", 
-                                 VG_TEX2D_CLAMP|VG_TEX2D_NEAREST,
-                                 &tex_scoretex );
-
-   mdl_close( &mscoreboard );
-
-   int w = 27,
-       h = 13;
-
-   world_global.sfd.w = w;
-   world_global.sfd.h = h;
-   world_global.sfd.buffer = 
-         vg_linear_alloc( vg_mem.rtmemory, 2*w*h*sizeof(float) );
-
-   for( int i=0; i<w*h*2; i++ )
-      world_global.sfd.buffer[i] = 0.0f;
-}
+VG_STATIC void sfd_render( world_instance *world, camera *cam, 
+                           m4x3f transform );
 
 #endif /* SFD_H */
diff --git a/world_volumes.c b/world_volumes.c
new file mode 100644 (file)
index 0000000..c08fe43
--- /dev/null
@@ -0,0 +1,55 @@
+#ifndef WORLD_VOLUMES_C
+#define WORLD_VOLUMES_C
+
+#include "world_volumes.h"
+
+/*
+ * BVH implementation
+ * ----------------------------------------------------------------------------
+ */
+
+VG_STATIC void volume_vg_expand_bound( void *user, boxf bound, u32 item_index )
+{
+   world_instance *world = user;
+
+   ent_volume *volume = mdl_arritm( &world->ent_volume, item_index );
+
+   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 = 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 *a = mdl_arritm( &world->ent_volume, ia ),
+              *b = mdl_arritm( &world->ent_volume, ib ),
+              temp;
+
+   temp = *a;
+   *a = *b;
+   *b = temp;
+}
+
+VG_STATIC void volume_vg_debug( void *user, u32 item_index )
+{
+   world_instance *world = user;
+   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 );
+}
+
+#endif /* WORLD_VOLUMES_H */
index da39d265644848fd93989ae3ecbac8f9b78cfec5..5bdc2d9ffdc5e52a01fb46536d2e3fde0c1bb012 100644 (file)
@@ -2,57 +2,19 @@
 #define WORLD_VOLUMES_H
 
 #include "world.h"
+#include "bvh.h"
 
-/*
- * BVH implementation
- * ----------------------------------------------------------------------------
- */
-
-VG_STATIC void volume_vg_expand_bound( void *user, boxf bound, u32 item_index )
-{
-   world_instance *world = user;
-
-   ent_volume *volume = mdl_arritm( &world->ent_volume, item_index );
-
-   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 = mdl_arritm( &world->ent_volume, item_index );
-   return volume->to_world[3][axis];
+struct {
+   int inside;
 }
+static world_volumes;
 
-VG_STATIC void volume_vg_swap( void *user, u32 ia, u32 ib )
-{
-   world_instance *world = user;
-   ent_volume *a = mdl_arritm( &world->ent_volume, ia ),
-              *b = mdl_arritm( &world->ent_volume, ib ),
-              temp;
-
-   temp = *a;
-   *a = *b;
-   *b = temp;
-}
-
-VG_STATIC void volume_vg_debug( void *user, u32 item_index )
-{
-   world_instance *world = user;
-   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 void volume_vg_expand_bound( void *user, boxf bound, u32 item_index );
+VG_STATIC float volume_vg_centroid( void *user, u32 item_index, int axis );
+VG_STATIC void volume_vg_swap( void *user, u32 ia, u32 ib );
+VG_STATIC void volume_vg_debug( void *user, u32 item_index );
 
-VG_STATIC bh_system bh_system_volumes = 
+static bh_system bh_system_volumes = 
 {
    .expand_bound = volume_vg_expand_bound,
    .item_centroid = volume_vg_centroid,
diff --git a/world_water.c b/world_water.c
new file mode 100644 (file)
index 0000000..fca3972
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#ifndef WATER_C
+#define WATER_C
+
+#include "world_water.h"
+#include "world_render.h"
+#include "render.h"
+#include "shaders/scene_water.h"
+#include "shaders/scene_water_fast.h"
+#include "scene.h"
+
+VG_STATIC void world_water_init(void)
+{
+   vg_info( "world_water_init\n" );
+   shader_scene_water_register();
+   shader_scene_water_fast_register();
+
+   vg_tex2d_load_qoi_async_file( "textures/water_surf.qoi",
+                                 VG_TEX2D_LINEAR|VG_TEX2D_REPEAT,
+                                 &world_water.tex_water_surf );
+
+   vg_success( "done\n" );
+}
+
+VG_STATIC void water_set_surface( world_instance *world, float height )
+{
+   world->water.height = height;
+   v4_copy( (v4f){ 0.0f, 1.0f, 0.0f, height }, world->water.plane );
+}
+
+VG_STATIC void world_link_lighting_ub( world_instance *world, GLuint shader );
+VG_STATIC void world_bind_position_texture( world_instance *world, 
+                                            GLuint shader, GLuint location,
+                                            int slot );
+VG_STATIC void world_bind_light_array( world_instance *world,
+                                       GLuint shader, GLuint location, 
+                                       int slot );
+VG_STATIC void world_bind_light_index( world_instance *world,
+                                       GLuint shader, GLuint location, 
+                                       int slot );
+
+/*
+ * Does not write motion vectors
+ */
+VG_STATIC void render_water_texture( world_instance *world, camera *cam,
+                                     int layer_depth )
+{
+   if( !world->water.enabled || (vg.quality_profile == k_quality_profile_low) )
+      return;
+
+   /* Draw reflection buffa */
+   render_fb_bind( gpipeline.fb_water_reflection, 1 );
+   glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
+
+   /* 
+    * Create flipped view matrix. Don't care about motion vectors
+    */
+   float cam_height = cam->transform[3][1] - world->water.height;
+
+   camera water_cam;
+   water_cam.farz = cam->farz;
+   water_cam.nearz = cam->nearz;
+   v3_copy( cam->transform[3], water_cam.transform[3] );
+   water_cam.transform[3][1] -= 2.0f * cam_height;
+
+   m3x3f flip;
+   m3x3_identity( flip );
+   flip[1][1] = -1.0f;
+   m3x3_mul( flip, cam->transform, water_cam.transform );
+
+   camera_update_view( &water_cam );
+
+   /* 
+    * Create clipped projection 
+    */
+   v4f clippa = { 0.0f, 1.0f, 0.0f, world->water.height-0.1f };
+   m4x3_mulp( water_cam.transform_inverse, clippa, clippa );
+   clippa[3] *= -1.0f;
+
+   m4x4_copy( cam->mtx.p, water_cam.mtx.p );
+   m4x4_clip_projection( water_cam.mtx.p, clippa );
+
+   camera_finalize( &water_cam );
+
+   /*
+    * Draw world
+    */
+   glEnable( GL_DEPTH_TEST );
+   glDisable( GL_BLEND );
+   glCullFace( GL_FRONT );
+   render_world( world, &water_cam, layer_depth );
+   glCullFace( GL_BACK );
+   
+   /*
+    * Create beneath view matrix
+    */
+   camera beneath_cam;
+   render_fb_bind( gpipeline.fb_water_beneath, 1 );
+   glClearColor( 1.0f, 0.0f, 0.0f, 0.0f );
+   glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
+
+   m4x3_copy( cam->transform, beneath_cam.transform );
+   camera_update_view( &beneath_cam );
+
+   float bias = -(cam->transform[3][1]-world->water.height)*0.1f;
+
+   v4f clippb = { 0.0f, -1.0f, 0.0f, -(world->water.height) + bias };
+   m4x3_mulp( beneath_cam.transform_inverse, clippb, clippb );
+   clippb[3] *= -1.0f;
+
+   m4x4_copy( cam->mtx.p, beneath_cam.mtx.p );
+   m4x4_clip_projection( beneath_cam.mtx.p, clippb );
+   camera_finalize( &beneath_cam );
+
+   glEnable( GL_DEPTH_TEST );
+   glDisable( GL_BLEND );
+   render_world_depth( world, &beneath_cam );
+   //glViewport( 0,0, g_render_x, g_render_y );
+}
+
+VG_STATIC void render_water_surface( world_instance *world, camera *cam )
+{
+   if( !world->water.enabled )
+      return;
+
+   if( vg.quality_profile == k_quality_profile_high ){
+      /* Draw surface */
+      shader_scene_water_use();
+      
+      render_fb_bind_texture( gpipeline.fb_water_reflection, 0, 0 );
+      shader_scene_water_uTexMain( 0 );
+   
+      glActiveTexture( GL_TEXTURE1 );
+      glBindTexture( GL_TEXTURE_2D, world_water.tex_water_surf );
+      shader_scene_water_uTexDudv( 1 );
+      
+      shader_scene_water_uInvRes( (v2f){
+            1.0f / (float)vg.window_x,
+            1.0f / (float)vg.window_y });
+
+      world_link_lighting_ub( world, _shader_scene_water.id );
+      world_bind_position_texture( world, _shader_scene_water.id, 
+                                    _uniform_scene_water_g_world_depth, 2 );
+      world_bind_light_array( world, _shader_scene_water.id,
+                                    _uniform_scene_water_uLightsArray, 4 );
+      world_bind_light_index( world, _shader_scene_water.id,
+                              _uniform_scene_water_uLightsIndex, 5 );
+
+      render_fb_bind_texture( gpipeline.fb_water_beneath, 0, 3 );
+      shader_scene_water_uTexBack( 3 );
+      shader_scene_water_uTime( world_static.time );
+      shader_scene_water_uCamera( cam->transform[3] );
+      shader_scene_water_uSurfaceY( world->water.height );
+
+      shader_scene_water_uPv( cam->mtx.pv );
+      shader_scene_water_uPvmPrev( cam->mtx_prev.pv );
+
+      m4x3f full;
+      m4x3_identity( full );
+      shader_scene_water_uMdl( full );
+
+      glEnable(GL_BLEND);
+      glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
+      glBlendEquation(GL_FUNC_ADD);
+
+      mesh_bind( &world->mesh_no_collide );
+
+      for( int i=0; i<world->surface_count; i++ ){
+         struct world_surface *mat = &world->surfaces[i];
+
+         if( mat->info.shader == k_shader_water ){
+            shader_scene_water_uShoreColour( mat->info.colour );
+            shader_scene_water_uOceanColour( mat->info.colour1 );
+
+            mdl_draw_submesh( &mat->sm_no_collide );
+         }
+      }
+
+      glDisable(GL_BLEND);
+   }
+   else if( vg.quality_profile == k_quality_profile_low ){
+      shader_scene_water_fast_use();
+
+      glActiveTexture( GL_TEXTURE1 );
+      glBindTexture( GL_TEXTURE_2D, world_water.tex_water_surf );
+      shader_scene_water_fast_uTexDudv( 1 );
+
+      shader_scene_water_fast_uTime( world_static.time );
+      shader_scene_water_fast_uCamera( cam->transform[3] );
+      shader_scene_water_fast_uSurfaceY( world->water.height );
+      world_link_lighting_ub( world, _shader_scene_water_fast.id );
+      world_bind_position_texture( world, _shader_scene_water_fast.id,
+                                   _uniform_scene_water_fast_g_world_depth, 2 );
+      world_bind_light_array( world, _shader_scene_water_fast.id,
+                                    _uniform_scene_water_fast_uLightsArray, 4 );
+
+      m4x3f full;
+      m4x3_identity( full );
+      shader_scene_water_fast_uMdl( full );
+      shader_scene_water_fast_uPv( cam->mtx.pv );
+      shader_scene_water_fast_uPvmPrev( cam->mtx_prev.pv );
+
+      glEnable(GL_BLEND);
+      glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
+      glBlendEquation(GL_FUNC_ADD);
+
+      mesh_bind( &world->mesh_no_collide );
+
+      for( int i=0; i<world->surface_count; i++ ){
+         struct world_surface *mat = &world->surfaces[i];
+
+         if( mat->info.shader == k_shader_water ){
+            shader_scene_water_fast_uShoreColour( mat->info.colour );
+            shader_scene_water_fast_uOceanColour( mat->info.colour1 );
+
+            mdl_draw_submesh( &mat->sm_no_collide );
+         }
+      }
+
+      glDisable(GL_BLEND);
+   }
+}
+
+#endif /* WATER_C */
index 14e3cd76409d3c472e7b22374982652f631d8de0..ad446d6669c2cc4605dab45a1cf385df1133d1ac 100644 (file)
 /*
- * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
  */
 
 #ifndef WATER_H
 #define WATER_H
 
 #include "world.h"
-#include "render.h"
-#include "shaders/scene_water.h"
-#include "shaders/scene_water_fast.h"
-#include "scene.h"
 
-VG_STATIC GLuint tex_water_surf;
-
-VG_STATIC void world_water_init(void)
-{
-   vg_info( "world_water_init\n" );
-   shader_scene_water_register();
-   shader_scene_water_fast_register();
-
-   vg_tex2d_load_qoi_async_file( "textures/water_surf.qoi",
-                                 VG_TEX2D_LINEAR|VG_TEX2D_REPEAT,
-                                 &tex_water_surf );
-
-   vg_success( "done\n" );
+struct world_water{
+   VG_STATIC GLuint tex_water_surf;
 }
+static world_water;
+VG_STATIC void world_water_init(void);
 
-VG_STATIC void water_set_surface( world_instance *world, float height )
-{
-   world->water.height = height;
-   v4_copy( (v4f){ 0.0f, 1.0f, 0.0f, height }, world->water.plane );
-}
-
-VG_STATIC void world_link_lighting_ub( world_instance *world, GLuint shader );
-VG_STATIC void world_bind_position_texture( world_instance *world, 
-                                            GLuint shader, GLuint location,
-                                            int slot );
-VG_STATIC void world_bind_light_array( world_instance *world,
-                                       GLuint shader, GLuint location, 
-                                       int slot );
-VG_STATIC void world_bind_light_index( world_instance *world,
-                                       GLuint shader, GLuint location, 
-                                       int slot );
-
-/*
- * Does not write motion vectors
- */
+VG_STATIC void water_set_surface( world_instance *world, float height );
 VG_STATIC void render_water_texture( world_instance *world, camera *cam,
-                                     int layer_depth )
-{
-   if( !world->water.enabled || (vg.quality_profile == k_quality_profile_low) )
-      return;
-
-   /* Draw reflection buffa */
-   render_fb_bind( gpipeline.fb_water_reflection, 1 );
-   glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
-
-   /* 
-    * Create flipped view matrix. Don't care about motion vectors
-    */
-   float cam_height = cam->transform[3][1] - world->water.height;
-
-   camera water_cam;
-   water_cam.farz = cam->farz;
-   water_cam.nearz = cam->nearz;
-   v3_copy( cam->transform[3], water_cam.transform[3] );
-   water_cam.transform[3][1] -= 2.0f * cam_height;
-
-   m3x3f flip;
-   m3x3_identity( flip );
-   flip[1][1] = -1.0f;
-   m3x3_mul( flip, cam->transform, water_cam.transform );
-
-   camera_update_view( &water_cam );
-
-   /* 
-    * Create clipped projection 
-    */
-   v4f clippa = { 0.0f, 1.0f, 0.0f, world->water.height-0.1f };
-   m4x3_mulp( water_cam.transform_inverse, clippa, clippa );
-   clippa[3] *= -1.0f;
-
-   m4x4_copy( cam->mtx.p, water_cam.mtx.p );
-   m4x4_clip_projection( water_cam.mtx.p, clippa );
-
-   camera_finalize( &water_cam );
-
-   /*
-    * Draw world
-    */
-   glEnable( GL_DEPTH_TEST );
-   glDisable( GL_BLEND );
-   glCullFace( GL_FRONT );
-   render_world( world, &water_cam, layer_depth );
-   glCullFace( GL_BACK );
-   
-   /*
-    * Create beneath view matrix
-    */
-   camera beneath_cam;
-   render_fb_bind( gpipeline.fb_water_beneath, 1 );
-   glClearColor( 1.0f, 0.0f, 0.0f, 0.0f );
-   glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
-
-   m4x3_copy( cam->transform, beneath_cam.transform );
-   camera_update_view( &beneath_cam );
-
-   float bias = -(cam->transform[3][1]-world->water.height)*0.1f;
-
-   v4f clippb = { 0.0f, -1.0f, 0.0f, -(world->water.height) + bias };
-   m4x3_mulp( beneath_cam.transform_inverse, clippb, clippb );
-   clippb[3] *= -1.0f;
-
-   m4x4_copy( cam->mtx.p, beneath_cam.mtx.p );
-   m4x4_clip_projection( beneath_cam.mtx.p, clippb );
-   camera_finalize( &beneath_cam );
-
-   glEnable( GL_DEPTH_TEST );
-   glDisable( GL_BLEND );
-   render_world_depth( world, &beneath_cam );
-   //glViewport( 0,0, g_render_x, g_render_y );
-}
-
-VG_STATIC void render_water_surface( world_instance *world, camera *cam )
-{
-   if( !world->water.enabled )
-      return;
-
-   if( vg.quality_profile == k_quality_profile_high ){
-      /* Draw surface */
-      shader_scene_water_use();
-      
-      render_fb_bind_texture( gpipeline.fb_water_reflection, 0, 0 );
-      shader_scene_water_uTexMain( 0 );
-   
-      glActiveTexture( GL_TEXTURE1 );
-      glBindTexture( GL_TEXTURE_2D, tex_water_surf );
-      shader_scene_water_uTexDudv( 1 );
-      
-      shader_scene_water_uInvRes( (v2f){
-            1.0f / (float)vg.window_x,
-            1.0f / (float)vg.window_y });
-
-      world_link_lighting_ub( world, _shader_scene_water.id );
-      world_bind_position_texture( world, _shader_scene_water.id, 
-                                    _uniform_scene_water_g_world_depth, 2 );
-      world_bind_light_array( world, _shader_scene_water.id,
-                                    _uniform_scene_water_uLightsArray, 4 );
-      world_bind_light_index( world, _shader_scene_water.id,
-                              _uniform_scene_water_uLightsIndex, 5 );
-
-      render_fb_bind_texture( gpipeline.fb_water_beneath, 0, 3 );
-      shader_scene_water_uTexBack( 3 );
-      shader_scene_water_uTime( world_global.time );
-      shader_scene_water_uCamera( cam->transform[3] );
-      shader_scene_water_uSurfaceY( world->water.height );
-
-      shader_scene_water_uPv( cam->mtx.pv );
-      shader_scene_water_uPvmPrev( cam->mtx_prev.pv );
-
-      m4x3f full;
-      m4x3_identity( full );
-      shader_scene_water_uMdl( full );
-
-      glEnable(GL_BLEND);
-      glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
-      glBlendEquation(GL_FUNC_ADD);
-
-      mesh_bind( &world->mesh_no_collide );
-
-      for( int i=0; i<world->surface_count; i++ ){
-         struct world_surface *mat = &world->surfaces[i];
-
-         if( mat->info.shader == k_shader_water ){
-            shader_scene_water_uShoreColour( mat->info.colour );
-            shader_scene_water_uOceanColour( mat->info.colour1 );
-
-            mdl_draw_submesh( &mat->sm_no_collide );
-         }
-      }
-
-      glDisable(GL_BLEND);
-   }
-   else if( vg.quality_profile == k_quality_profile_low ){
-      shader_scene_water_fast_use();
-
-      glActiveTexture( GL_TEXTURE1 );
-      glBindTexture( GL_TEXTURE_2D, tex_water_surf );
-      shader_scene_water_fast_uTexDudv( 1 );
-
-      shader_scene_water_fast_uTime( world_global.time );
-      shader_scene_water_fast_uCamera( cam->transform[3] );
-      shader_scene_water_fast_uSurfaceY( world->water.height );
-      world_link_lighting_ub( world, _shader_scene_water_fast.id );
-      world_bind_position_texture( world, _shader_scene_water_fast.id,
-                                   _uniform_scene_water_fast_g_world_depth, 2 );
-      world_bind_light_array( world, _shader_scene_water_fast.id,
-                                    _uniform_scene_water_fast_uLightsArray, 4 );
-
-      m4x3f full;
-      m4x3_identity( full );
-      shader_scene_water_fast_uMdl( full );
-      shader_scene_water_fast_uPv( cam->mtx.pv );
-      shader_scene_water_fast_uPvmPrev( cam->mtx_prev.pv );
-
-      glEnable(GL_BLEND);
-      glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
-      glBlendEquation(GL_FUNC_ADD);
-
-      mesh_bind( &world->mesh_no_collide );
-
-      for( int i=0; i<world->surface_count; i++ ){
-         struct world_surface *mat = &world->surfaces[i];
-
-         if( mat->info.shader == k_shader_water ){
-            shader_scene_water_fast_uShoreColour( mat->info.colour );
-            shader_scene_water_fast_uOceanColour( mat->info.colour1 );
-
-            mdl_draw_submesh( &mat->sm_no_collide );
-         }
-      }
-
-      glDisable(GL_BLEND);
-   }
-}
+                                     int layer_depth );
+VG_STATIC void render_water_surface( world_instance *world, camera *cam );
 
 #endif /* WATER_H */