+ struct instance_cache
+ {
+ mdl_context *mdl;
+ u32 pstr_file;
+ }
+ instance_cache[32];
+ u32 instance_cache_count;
+}
+world;
+
+
+/*
+ * API
+ */
+
+VG_STATIC int ray_hit_is_ramp( ray_hit *hit );
+VG_STATIC int ray_hit_is_terrain( ray_hit *hit );
+VG_STATIC void ray_world_get_tri( ray_hit *hit, v3f tri[3] );
+VG_STATIC int ray_world( v3f pos, v3f dir, ray_hit *hit );
+
+/*
+ * Submodules
+ */
+
+#include "world_routes.h"
+#include "world_sfd.h"
+#include "world_render.h"
+#include "world_water.h"
+#include "world_gen.h"
+#include "world_gate.h"
+
+/*
+ * -----------------------------------------------------------------------------
+ * Events
+ * -----------------------------------------------------------------------------
+ */
+
+VG_STATIC void world_init(void)
+{
+ vg_convar_push( (struct vg_convar){
+ .name = "water_enable",
+ .data = &world.water.enabled,
+ .data_type = k_convar_dtype_i32,
+ .opt_i32 = { .min=0, .max=1, .clamp=1 },
+ .persistent = 0
+ });
+
+ world.sky_rate = 1.0;
+ world.sky_target_rate = 1.0;
+
+ shader_terrain_register();
+ shader_sky_register();
+ shader_planeinf_register();
+ shader_gpos_register();
+ shader_fscolour_register();
+ shader_alphatest_register();
+
+ vg_info( "Loading world resources\n" );
+
+ vg_linear_clear( vg_mem.scratch );
+ mdl_context *msky = mdl_load_full( vg_mem.scratch, "models/rs_skydome.mdl" );
+
+ mdl_node *nlower = mdl_node_from_name( msky, "dome_lower" ),
+ *nupper = mdl_node_from_name( msky, "dome_upper" );
+
+ world.dome_lower = *mdl_node_submesh( msky, nlower, 0 );
+ world.dome_upper = *mdl_node_submesh( msky, nupper, 0 );
+
+ vg_acquire_thread_sync();
+ {
+ mdl_unpack_glmesh( msky, &world.skydome );
+ }
+ vg_release_thread_sync();
+
+ /* Other systems */
+ vg_info( "Loading other world systems\n" );
+
+ vg_loader_highwater( world_render_init, NULL, NULL );
+ vg_loader_highwater( world_sfd_init, NULL, NULL );
+ vg_loader_highwater( world_water_init, NULL, NULL );
+ vg_loader_highwater( world_gates_init, NULL, NULL );
+ vg_loader_highwater( world_routes_init, NULL, NULL );
+
+ /* Allocate dynamic world memory arena */
+ u32 max_size = 72*1024*1024;
+ world.dynamic_vgl = vg_create_linear_allocator( vg_mem.rtmemory, max_size );
+}
+
+VG_STATIC void world_update( v3f pos )
+{
+ world.sky_time += world.sky_rate * vg.time_delta;
+ world.sky_rate = vg_lerp( world.sky_rate, world.sky_target_rate,
+ vg.time_delta * 5.0 );
+
+ world_routes_update();
+#if 0
+ world_routes_debug();
+#endif
+
+ int closest = 0;
+ float min_dist = INFINITY;
+
+ for( int i=0; i<world.route_count; i++ )
+ {
+ float d = v3_dist2( world.routes[i].scoreboard_transform[3], pos );
+
+ if( d < min_dist )