init
authorhgn <hgodden00@gmail.com>
Sat, 28 May 2022 16:15:23 +0000 (17:15 +0100)
committerhgn <hgodden00@gmail.com>
Sat, 28 May 2022 16:15:23 +0000 (17:15 +0100)
13 files changed:
.gitignore [new file with mode: 0755]
blender_export.py [new file with mode: 0644]
main.c [new file with mode: 0644]
road.h [new file with mode: 0644]
scene.h [new file with mode: 0644]
sprites_autocombine.h [new file with mode: 0644]
textures/gradients.png [new file with mode: 0644]
textures/grid.png [new file with mode: 0644]
textures/norway_foliage.png [new file with mode: 0644]
textures/norwey.png [new file with mode: 0644]
textures/road.png [new file with mode: 0644]
vg.conf [new file with mode: 0644]
vg_config.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100755 (executable)
index 0000000..c400164
--- /dev/null
@@ -0,0 +1,52 @@
+# Gitignore for MMV project.
+#  Whitelist mode
+
+# Ignore all but directories
+*
+!*/
+
+build.linux/
+build.win32/
+restricted/
+.temp_textures/
+
+# ALLOW ============================
+!.gitattributes
+!.gitignore
+!.gitmodules
+
+# Code sources _____________________
+# C source files
+!*.c
+!*.h
+
+# Blender projects
+!*.blend
+
+# GLSL shader source files
+!*.fs
+!*.vs
+!*.gls
+
+# Python source files
+!*.py
+
+# Build scripts
+!*.sh
+!*.bat
+!*.conf
+
+# Compiled resources _______________
+# MMV proprietary files
+!*.vmd
+!*.vma
+!*.cfg
+!*.vmv
+!*.map
+
+# Other game assets (3rd party)
+!*.bdf
+!*.png
+!*.ogg
+!*.txt
+!*.tga
diff --git a/blender_export.py b/blender_export.py
new file mode 100644 (file)
index 0000000..05059c2
--- /dev/null
@@ -0,0 +1,141 @@
+import bpy, math
+from ctypes import *
+
+class model(Structure):
+   _pack_ = 1
+   _fields_ = [("identifier",c_uint32),
+               ("vertex_count",c_uint32),
+               ("indice_count",c_uint32),
+               ("layer_count",c_uint32)]
+
+class sdf_primative(Structure):
+   _pack_ = 1
+   _fields_ = [("origin",c_float*4),
+               ("info",c_float*4)]
+
+class submodel(Structure):
+   _pack_ = 1
+   _fields_ = [("indice_start",c_uint32),
+               ("indice_count",c_uint32),
+               ("vertex_start",c_uint32),
+               ("vertex_count",c_uint32),
+               ("bbx",(c_float*3)*2),
+               ("sdf",sdf_primative),
+               ("sdf_type",c_int32),
+               ("name",c_char*32)]
+
+class model_vert(Structure):
+   _pack_ = 1
+   _fields_ = [("co",c_float*3),
+               ("norm",c_float*3),
+               ("colour",c_float*4),
+               ("uv",c_float*2)]
+
+def fixed_string(dest,string):
+   return
+   for i in range(len(string)):
+      dest[i] = string[i]
+
+def write_model(name):
+   fp = open(F"/home/harry/Documents/carve/models/{name}.mdl", "wb")
+   collection = bpy.data.collections[name]
+   
+   header = model()
+   header.identifier = 0xABCD0000
+   header.vertex_count = 0
+   header.indice_count = 0
+   header.layer_count = 0
+
+   layers = []
+   vertex_buffer = []
+   indice_buffer = []
+
+   for obj in collection.objects:
+      if obj.type == 'MESH':
+         dgraph = bpy.context.evaluated_depsgraph_get()
+         data = obj.evaluated_get(dgraph).data
+         data.calc_loop_triangles()
+         data.calc_normals_split()
+
+         sm = submodel()
+         sm.indice_start = header.indice_count
+         sm.vertex_start = header.vertex_count
+         sm.vertex_count = len(data.vertices)
+         sm.indice_count = len(data.loop_triangles)*3
+         sm.sdf_type = 0
+         for i in range(3):
+            sm.bbx[0][i] =  999999
+            sm.bbx[1][i] = -999999
+
+         if F"{obj.name}.sdf_cone" in bpy.data.objects:
+            cone = bpy.data.objects[F"{obj.name}.sdf_cone"]
+            sm.sdf.origin[0] =  cone.location[0]
+            sm.sdf.origin[1] =  cone.location[2] + cone.scale[1]*2.0
+            sm.sdf.origin[2] = -cone.location[1]
+            sm.sdf.origin[3] = 0.0
+
+            lo = cone.scale[0]
+            la = cone.scale[1]*2.0
+            lh = math.sqrt(lo*lo+la*la)
+
+            sm.sdf.info[0] = lo
+            sm.sdf.info[1] = la
+            sm.sdf.info[2] = lo/lh
+            sm.sdf.info[3] = la/lh
+
+            sm.sdf_type = 1
+         
+         sm.name = obj.name.encode('utf-8')
+
+         for vert in data.vertices:
+            v = model_vert()
+            v.co[0] = vert.co[0]
+            v.co[1] = vert.co[2]
+            v.co[2] = -vert.co[1]
+            v.colour[0] = 0.0
+            v.colour[1] = 0.0
+            v.colour[2] = 0.0
+            v.colour[3] = 1.0
+            vertex_buffer += [v]
+            
+            for i in range(3):
+               sm.bbx[0][i] = min( sm.bbx[0][i], v.co[i] )
+               sm.bbx[1][i] = max( sm.bbx[1][i], v.co[i] )
+
+         for l in data.loops:
+            pvert = vertex_buffer[l.vertex_index + sm.vertex_start]
+            norm = l.normal
+            pvert.norm[0] = norm[0]
+            pvert.norm[1] = norm[2]
+            pvert.norm[2] = -norm[1]
+
+            #if data.vertex_colors:
+            #   colour = data.vertex_colors.active.data[ l.index ].color
+            #   pvert.colour[0] = colour[0]
+         
+            if data.uv_layers:
+               uv = data.uv_layers.active.data[ l.index ].uv
+               pvert.uv[0] = uv[0]
+               pvert.uv[1] = uv[1]
+
+         for tri in data.loop_triangles:
+            indice_buffer += [c_uint32(tri.vertices[_]) for _ in range(3)]
+
+         layers += [sm]
+         header.layer_count += 1
+         header.vertex_count += sm.vertex_count
+         header.indice_count += sm.indice_count
+
+   fp.write( bytearray( header ) )
+   for l in layers:
+      fp.write( bytearray(l) )
+   for v in vertex_buffer:
+      fp.write( bytearray(v) )
+   for i in indice_buffer:
+      fp.write( bytearray(i) )
+
+   fp.close()
+
+write_model( "test" )
+write_model( "free_dev" )
+write_model( "char_dev" )
diff --git a/main.c b/main.c
new file mode 100644 (file)
index 0000000..b397e2e
--- /dev/null
+++ b/main.c
@@ -0,0 +1,684 @@
+#define VG_3D
+#include "vg/vg.h"
+
+/* Resources */
+vg_tex2d tex_norwey = { .path = "textures/norway_foliage.qoi" };
+vg_tex2d tex_grid = { .path = "textures/grid.qoi" };
+vg_tex2d tex_road = { .path = "textures/road.qoi" };
+vg_tex2d tex_gradients = { .path = "textures/gradients.qoi",
+                           .flags = VG_TEXTURE_CLAMP };
+vg_tex2d *texture_list[] =
+{
+   &tex_norwey,
+   &tex_gradients,
+   &tex_grid,
+   &tex_road
+};
+
+/* Convars */
+static int freecam = 0;
+static int debugview = 0;
+static int debugsdf = 0;
+static int debugroad = 0;
+
+/* Components */
+#include "road.h"
+#include "scene.h"
+
+int main( int argc, char *argv[] )
+{ 
+   vg_init( argc, argv, "Voyager Game Engine" ); 
+}
+
+m4x3f world_matrix;
+v3f player_pos;
+v3f player_head;     /* Relative to pos */
+v3f player_vel = { 0.0f, 0.0f, -0.2f };
+float player_yaw;
+v2f look_dir;
+v3f player_accel;
+
+float waves[128][128];
+
+road_patch road_main;
+scene test_scene;
+scene world_scene;
+scene player_scene;
+u32 world_terrain_count,
+    world_road_count;
+
+static int reset_player( int argc, char const *argv[] )
+{
+   v3_zero( player_pos );
+   v3_copy( (v3f){ 0.0f, 0.0f, -0.2f }, player_vel );
+   player_yaw = 0.0f;
+
+   return 0;
+}
+
+void vg_register(void)
+{
+   scene_register();
+}
+
+void vg_start(void)
+{
+   for( int y=0; y<128; y++ )
+   {
+      for( int x=0; x<128; x++ )
+      {
+         v2f pos = {x,y};
+
+         waves[x][y] = sinf( pos[0] * 0.1f ) * 
+                       cosf( pos[1] * 0.3f ) * 5.0f + x*-0.2f;
+      }
+   }
+
+   vg_tex2d_init( texture_list, vg_list_size( texture_list ) );
+   
+   road_patch_init( &road_main );
+   road_generate( &road_main );
+
+   vg_convar_push( (struct vg_convar){
+      .name = "freecam",
+      .data = &freecam,
+      .data_type = k_convar_dtype_i32,
+      .opt_i32 = { .min=0, .max=1, .clamp=1 },
+      .persistent = 1
+   });
+
+   vg_convar_push( (struct vg_convar){
+      .name = "debugview",
+      .data = &debugview,
+      .data_type = k_convar_dtype_i32,
+      .opt_i32 = { .min=0, .max=1, .clamp=0 },
+      .persistent = 1
+   });
+
+   vg_convar_push( (struct vg_convar){
+      .name = "debugsdf",
+      .data = &debugsdf,
+      .data_type = k_convar_dtype_i32,
+      .opt_i32 = { .min=0, .max=1, .clamp=1 },
+      .persistent = 1
+   });
+
+   vg_convar_push( (struct vg_convar){
+      .name = "debugroad",
+      .data = &debugroad,
+      .data_type = k_convar_dtype_i32,
+      .opt_i32 = { .min=0, .max=1, .clamp=1 },
+      .persistent = 1
+   });
+
+       vg_function_push( (struct vg_cmd){
+               .name = "reset",
+               .function = reset_player
+       });
+   
+   v3f lightDir = { 0.1f, 0.8f, 0.2f };
+   v3_normalize( lightDir );
+   
+   scene_init( &world_scene );
+   scene_init( &test_scene );
+   scene_init( &player_scene );
+   model *world = vg_asset_read( "models/free_dev.mdl" );
+   model *test = vg_asset_read( "models/test.mdl" );
+   model *char_dev = vg_asset_read( "models/char_dev.mdl" );
+   scene_add_model( &player_scene, char_dev,
+         submodel_get( char_dev, "joint" ),
+         (v3f){0.0f,0.0f,0.0f}, 0.0f, 1.0f );
+   free(char_dev);
+
+   scene_add_model( &world_scene, world, 
+         submodel_get( world, "terrain" ),
+         (v3f){0.0f,0.0f,0.0f}, 0.0f, 1.0f );
+
+   int id_tree = submodel_get( test, "tree" ),
+       id_groundcover[] = 
+       {
+         submodel_get( test, "bush" ),
+         submodel_get( test, "grass" ),
+         submodel_get( test, "blubber" ),
+         submodel_get( test, "blubber" ),
+         submodel_get( test, "blubber" ),
+         submodel_get( test, "grassthin" )
+       };
+
+   /* Sprinkle some trees in the terrain areas */
+   v3f range;
+   v3_sub( world_scene.bbx[1], world_scene.bbx[0], range );
+
+   for( int i=0; i<8000; i++ )
+   {
+      v3f pos;
+
+      pos[0] = vg_randf();
+      pos[1] = 0.0f;
+      pos[2] = vg_randf();
+      v3_muladd( world_scene.bbx[0], pos, range, pos );
+
+      if( sample_scene_height( &world_scene, pos ) )
+      {
+         scene_add_model( &test_scene, test, 
+               id_tree,
+               pos, vg_randf() * VG_TAUf, vg_randf() * 0.5f + 0.5f );
+      }
+   }
+
+   world_terrain_count = world_scene.indice_count;
+
+   scene_add_model( &world_scene, world, 
+         submodel_get( world, "road" ),
+         (v3f){0.0f,0.0f,0.0f}, 0.0f, 1.0f );
+   world_road_count = world_scene.indice_count-world_terrain_count;
+
+   free( test );
+   free( world );
+
+#if 0
+   scene_compute_occlusion( &test_scene );
+   scene_shadow_gradient( &test_scene, 1, 0.0f, 1.0f );
+   scene_shadow_sphere( &test_scene, (v3f){ 0.0f,4.0f,0.0f},
+         (v4f){1.0f, 2.0f, 0.0f, 0.0f}, lightDir );
+#endif
+
+   scene_upload( &player_scene );
+   scene_upload( &world_scene );
+   scene_upload( &test_scene );
+}
+
+static float sample_terrain( v3f pos )
+{
+   v3f local;
+   v3_muls( pos, 0.1f, local );
+
+   v2i ico = { local[0], local[2] };
+   for( int i=0; i<2; i++ )
+   {
+      if( ico[i] < 0 ) ico[i] = 0;
+      if( ico[i] > 126 ) ico[i] = 126;
+   }
+
+   float hse = waves[ico[0]+1][ico[1]],
+         hsw = waves[ico[0]][ico[1]],
+         hne = waves[ico[0]+1][ico[1]+1],
+         hnw = waves[ico[0]][ico[1]+1];
+
+   v3f subcoord;
+   v3_floor( local, subcoord );
+   v3_sub( local, subcoord, subcoord );
+
+   float hs = vg_lerpf( hse, hsw, 1.0f-subcoord[0] ),
+         hn = vg_lerpf( hne, hnw, 1.0f-subcoord[0] ),
+         sampleh = vg_lerpf( hs, hn, subcoord[2] );
+   
+   return sampleh*10.0f;
+}
+
+static void draw_terrain(void)
+{
+   float sf = 10.0f;
+
+   /* Draw waves
+    */
+   for( int y=0; y<63; y++ )
+   {
+      for( int x=0; x<63; x++ )
+      {
+         v2i pos = {x*2,y*2};
+         vg_line( (v3f){ pos[0]*sf, waves[pos[0]][pos[1]]*sf, pos[1]*sf },
+                  (v3f){ (pos[0]+2)*sf, waves[pos[0]+2][pos[1]]*sf, pos[1]*sf },
+                  0xa0ffffff );
+
+         vg_line( (v3f){ pos[1]*sf, waves[pos[1]][pos[0]]*sf, pos[0]*sf },
+                  (v3f){ pos[1]*sf, waves[pos[1]][pos[0]+2]*sf, (pos[0]+2)*sf },
+                  0xa0ffffff );
+      }
+   }
+
+}
+
+v3f head, butt, knee_r, knee_l, foot_r, foot_l, hand_r, hand_l,
+    shoulder_r, shoulder_l;
+
+
+void vg_update(void)
+{
+   float timestep = 1.0f/60.0f;
+
+   if( freecam )
+   {
+      m4x3f cam_rot;
+      m4x3_identity( cam_rot );
+      m4x3_rotate_y( cam_rot, -look_dir[0] );
+      m4x3_rotate_x( cam_rot, -look_dir[1] );
+
+      v3f lookdir = { 0.0f, 0.0f, -1.0f },
+          sidedir = { 1.0f, 0.0f,  0.0f };
+      
+      m4x3_mulv( cam_rot, lookdir, lookdir );
+      m4x3_mulv( cam_rot, sidedir, sidedir );
+      
+      float movespeed = 5.0f;
+      static v2f mouse_last,
+                 view_vel = { 0.0f, 0.0f };
+      static v3f move_vel = { 0.0f, 0.0f, 0.0f };
+
+      if( vg_get_button_down( "primary" ) )
+      {
+         v2_copy( vg_mouse, mouse_last );
+      }
+      else if( vg_get_button( "primary" ) )
+      {
+         v2f delta;
+         v2_sub( vg_mouse, mouse_last, delta );
+         v2_copy( vg_mouse, mouse_last );
+
+         v2_muladds( view_vel, delta, 0.005f, view_vel );
+      }
+      
+      v2_muls( view_vel, 0.75f, view_vel );
+      v2_add( view_vel, look_dir, look_dir );
+      look_dir[1] = vg_clampf( look_dir[1], -VG_PIf*0.5f, VG_PIf*0.5f );
+
+      if( vg_get_button( "forward" ) )
+         v3_muladds( move_vel, lookdir, timestep * movespeed, move_vel );
+      if( vg_get_button( "back" ) )
+         v3_muladds( move_vel, lookdir, timestep *-movespeed, move_vel );
+      if( vg_get_button( "left" ) )
+         v3_muladds( move_vel, sidedir, timestep *-movespeed, move_vel );
+      if( vg_get_button( "right" ) )
+         v3_muladds( move_vel, sidedir, timestep * movespeed, move_vel );
+
+      v3_muls( move_vel, 0.75f, move_vel );
+      v3_add( move_vel, player_head, player_head );
+
+      return;
+   }
+
+   if( vg_get_button( "forward" ) )
+   {
+      v3f accel = { sinf( player_yaw ), 0.0f, -cosf( player_yaw ) };
+      v3_muladds( player_vel, accel, 5.0f * timestep, player_vel );
+   }
+   
+   m4x3f player_transform,
+         world_to_local, local_to_world;
+
+   m4x3_identity( player_transform );
+   m4x3_translate( player_transform, player_pos );
+   m4x3_rotate_y( player_transform, -player_yaw );
+
+   m4x3_identity( world_to_local );
+   m4x3_rotate_y( world_to_local, player_yaw );
+   
+   m4x3_identity( local_to_world );
+   m4x3_rotate_y( local_to_world, -player_yaw );
+
+   /* Get front and back contact points */
+   v3f contact_front, contact_back, fwd, fwd1, contact_norm;
+
+   m4x3_mulv( local_to_world, (v3f){0.0f,0.0f,-1.0f}, fwd );
+   m4x3_mulv( local_to_world, (v3f){0.03f,0.0f,-1.0f}, fwd1 );
+
+   v3_muladds( player_pos, fwd, 1.0f, contact_front );
+   v3_muladds( player_pos, fwd,-1.0f, contact_back );
+   v3_muladds( player_pos, fwd1, 1.0f, contact_norm );
+   
+#if 0
+   road_patch_setplayer( &road_main, player_pos );
+   sample_road_height( &road_main, contact_front );
+   sample_road_height( &road_main, contact_back );
+#else
+   sample_scene_height( &world_scene, contact_front );
+   sample_scene_height( &world_scene, contact_back );
+   sample_scene_height( &world_scene, contact_norm );
+#endif
+
+   v3f norm;
+   v3f v0, v1;
+   v3_sub( contact_back, contact_norm, v0 );
+   v3_sub( contact_front, contact_norm, v1 );
+   v3_cross( v1, v0, norm );
+   v3_normalize( norm );
+
+   v3f gravity = { 0.0f, -9.6f, 0.0f };
+   v3_muladds( player_vel, gravity, timestep, player_vel );
+
+   v3f ground_pos;
+   v3_copy( player_pos, ground_pos );
+   sample_scene_height( &world_scene, ground_pos );
+
+   static int in_air = 1;
+
+   if( in_air )
+   {
+      if( ground_pos[1] > player_pos[1] )
+         in_air = 0;
+   }
+
+   if( !in_air )
+   {
+      float resistance = v3_dot( norm, player_vel );
+
+      if( resistance >= 0.0f )
+         in_air = 1;
+      else
+      {
+         v3_muladds( player_vel, norm, -resistance, player_vel );
+      }
+   }
+
+   /* vg_info( "%.3f | %.3f\n", player_vel[1], resistance ); */
+   v3_muladds( player_pos, player_vel, timestep, player_pos );
+   
+   float slip = 0.0f;
+
+   if( !in_air )
+   {
+      player_pos[1] = (contact_front[1]+contact_back[1])*0.5f;
+
+   vg_line( player_pos, contact_front, 0xff00ffff );
+   vg_line( player_pos, contact_back, 0xff00ffa0 );
+
+   /* Create the 'travel' vector */
+   v3f travel;
+   v3_sub( contact_front, contact_back, travel );
+   v3_normalize( travel );
+
+   /* Apply gravity */
+#if 0
+   float gravity_conversion = -v3_dot( travel, gravity );
+   vel[2] += gravity_conversion * substep;
+#endif
+
+   /* Get localized (rotated) rigidbody forces 
+    *           -z
+    *            ^
+    *           -|-
+    *            |
+    *           +z
+    */
+   
+   v3f vel;
+   m4x3_mulv( world_to_local, player_vel, vel );
+
+   /* Calculate local forces */
+   slip = -vel[0] / vel[2];
+   float substep = timestep * 0.2f;
+
+   if( fabsf( slip ) > 1.2f )
+   {
+      slip = vg_signf( slip ) * 1.2f;
+   }
+   
+   for( int i=0; i<5; i++ )
+   {
+      if( fabsf(vel[2]) >= 0.02f*substep )
+         vel[2] += vg_signf( vel[2] ) * -0.02f * substep;
+      if( fabsf(vel[0]) >= 6.0f*substep )
+         vel[0] += vg_signf( vel[0] ) * -6.0f * substep;
+   }
+   
+   m4x3_mulv( local_to_world, vel, player_vel );
+
+   if( vg_get_button( "yawl" ) )
+      player_yaw -= 1.6f * timestep;
+   if( vg_get_button( "yawr" ) )
+      player_yaw += 1.6f * timestep;
+
+   player_yaw += vg_get_axis( "horizontal" ) * 1.6f * timestep;
+   }
+   else
+   {
+      player_yaw += vg_get_axis( "horizontal" ) * 3.6f * timestep;
+      look_dir[0] = player_yaw;
+   }
+
+   look_dir[0] = atan2f( player_vel[0], -player_vel[2] );
+   
+   /* Creating a skeleton of the player dynamically */
+   float kheight = 1.8f,
+         kleg = 0.6f;
+
+   v2f ac;
+
+   static v3f last_vel = { 0.0f, 0.0f, 0.0f };
+   static v3f bob, bob1;
+
+   v3_sub( player_vel, last_vel, player_accel );
+   v3_copy( player_vel, last_vel );
+   v3_add( bob, player_accel, bob );
+
+   bob[0] = vg_clampf( bob[0], -0.4f, 1.0f );
+   bob[1] = vg_clampf( bob[1], -0.4f, 1.3f );
+   bob[2] = vg_clampf( bob[2], -0.4f, 1.0f );
+   
+   v3_lerp( bob, (v3f){ 0.0f, 0.0f, 0.0f }, 0.1f, bob );
+   v3_lerp( bob1, bob, 0.1f, bob1 );
+   
+   /* Feet */
+   foot_r[0] =  0.0f;
+   foot_r[1] =  0.0f;
+   foot_r[2] =  0.3f;
+
+   foot_l[0] =  0.0f;
+   foot_l[1] =  0.0f;
+   foot_l[2] = -0.3f;
+   
+   /* Head */
+   head[0] =  (-sinf(slip)*0.9f * kheight + bob1[0]*0.6f) * 0.54f;
+   head[1] =  cosf(slip)*0.9f * kheight +-bob1[1]*1.4f;
+   head[2] =  0.0f;
+
+   /* Hips */
+   butt[0] = -sinf(slip)*0.2f;
+   butt[1] =  cosf(slip);
+   butt[2] =  0.0f;
+   v2_normalize(butt);
+   v2_muls( butt, -0.7f, butt );
+   v2_add( head, butt, butt );
+   
+   /* Knees */
+   v2_sub( butt, (v2f){0.0f,0.0f}, ac );
+   float cl = v2_length( ac ),
+          d = acosf( (2.0f * kleg*kleg - cl*cl) / 2.0f * kleg*kleg ),
+          x = atan2f( ac[0], ac[1] ),
+          ad = (-VG_PIf-d)/2.0f;
+
+   v2_muladds( (v2f){0.0f,0.0f}, 
+               (v2f){ sinf( ad+x ), cosf( ad + x ) },
+               kleg, knee_l );
+   knee_l[2] = -0.3f;
+
+   v2_copy( knee_l, knee_r );
+   knee_r[2] = 0.3f;
+
+   /* shoulders */
+   v3_add( (v3f){0.0f,-0.1f,-0.2f}, head, shoulder_r );
+
+   /* Hands */
+   hand_r[0] =  sinf( slip ) * 0.1f;
+   hand_r[1] = -cosf( slip*5.0f ) * 0.5f - 0.5f;
+   hand_r[2] = -sinf( fabsf(slip) * 2.4f );
+   v3_add( shoulder_r, hand_r, hand_r );
+
+   m4x3_mulv( player_transform, head, head );
+   m4x3_mulv( player_transform, butt, butt );
+   m4x3_mulv( player_transform, knee_l, knee_l );
+   m4x3_mulv( player_transform, knee_r, knee_r );
+   m4x3_mulv( player_transform, foot_l, foot_l );
+   m4x3_mulv( player_transform, foot_r, foot_r );
+   m4x3_mulv( player_transform, hand_r, hand_r );
+   m4x3_mulv( player_transform, shoulder_r, shoulder_r );
+
+   v3_copy( head, player_head );
+   
+}
+
+static void debug_grid( v3f at )
+{
+   v3f base;
+   v3_floor( at, base );
+
+   for( int y=0; y<16; y++ )
+   {
+      vg_line( (v3f){ base[0] - 8, base[1], base[2]+y-8 },
+               (v3f){ base[0] + 8, base[1], base[2]+y-8 },
+               0x40ffffff );
+   }
+   for( int x=0; x<16; x++ )
+   {
+      vg_line( (v3f){ base[0]+x-8, base[1], base[2]-8 },
+               (v3f){ base[0]+x-8, base[1], base[2]+8 },
+               0x40ffffff );
+   }
+}
+
+void vg_render(void) 
+{
+   glViewport( 0,0, vg_window_x, vg_window_y );
+
+   glDisable( GL_DEPTH_TEST );
+   glClearColor( 0.1f, 0.0f, 0.2f, 1.0f );
+   glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
+
+   v3f pos_inv;
+   v3_negate( player_head, pos_inv );
+
+   float speed = v3_length( player_vel );
+   v3f shake = { vg_randf()-0.5f, vg_randf()-0.5f, vg_randf()-0.5f };
+   v3_muls( shake, speed*0.01f, shake );
+
+   m4x3_identity( world_matrix );
+   m4x3_rotate_x( world_matrix, freecam? look_dir[1]: 0.3f+shake[1]*0.04f );
+   m4x3_rotate_y( world_matrix, look_dir[0]+shake[0]*0.02f );
+   m4x3_translate( world_matrix, pos_inv );
+   
+   m4x4f world_4x4;
+   m4x3_expand( world_matrix, world_4x4 );
+
+   m4x4_projection( vg_pv, 
+         freecam? 90.0f: 130.0f,
+         (float)vg_window_x / (float)vg_window_y, 
+         0.01f, 1000.0f );
+   m4x4_mul( vg_pv, world_4x4, vg_pv );
+
+   if( debugroad )
+      draw_road_patch_dev( &road_main );
+
+   vg_line( (v3f){ 0.0f, 0.0f, 0.0f }, (v3f){ 1.0f, 0.0f, 0.0f }, 0xffff0000 );
+   vg_line( (v3f){ 0.0f, 0.0f, 0.0f }, (v3f){ 0.0f, 1.0f, 0.0f }, 0xff00ff00 );
+   vg_line( (v3f){ 0.0f, 0.0f, 0.0f }, (v3f){ 0.0f, 0.0f, 1.0f }, 0xff0000ff );
+
+   v3f board_fwd = { -sinf( player_yaw ), 0.0f, cosf( player_yaw ) };
+   v3f board_side = { -board_fwd[2], 0.0f, board_fwd[0] };
+   v3f bnw, bne, bse, bsw;
+   
+   v3_muladds( player_pos, board_fwd, 0.75f, bnw );
+   v3_muladds( player_pos, board_fwd, -0.75f, bsw );
+   v3_muladds( bnw, board_side,  0.1f, bne );
+   v3_muladds( bnw, board_side, -0.1f, bnw );
+   v3_muladds( bsw, board_side,  0.1f, bse );
+   v3_muladds( bsw, board_side, -0.1f, bsw );
+
+   vg_line( bnw, bne, 0xff00ff00 );
+   vg_line( bne, bse, 0xff00ff00 );
+   vg_line( bse, bsw, 0xff00ff00 );
+   vg_line( bsw, bnw, 0xff00ff00 );
+
+   glEnable( GL_DEPTH_TEST );
+
+   v3f leg_dr, leg_dl, leg_ar, leg_al;
+
+   v3_sub( foot_l, butt, leg_dl );
+   v3_sub( foot_r, butt, leg_dr );
+   leg_dl[1] = 0.0f;
+   leg_dr[1] = 0.0f;
+   v3_normalize( leg_dl );
+   v3_normalize( leg_dr );
+
+   v3f v0;
+   v3_sub( butt, knee_l, v0 );
+   float leg_pl = atan2f( v0[1], v3_dot( v0, leg_dl ) );
+
+   v3_sub( knee_l, foot_l, v0 );
+   float knee_pl = atan2f( v0[1], v3_dot( v0, leg_dl ) );
+
+   float leg_yl = -atan2f( leg_dl[2], leg_dl[0] ),
+         leg_yr = atan2f( leg_dr[2], leg_dr[0] );
+
+   SHADER_USE( shader_debug_vcol );
+   m4x3f temp;
+   m4x4f temp1;
+
+   vg_tex2d_bind( &tex_grid, 0 );
+   scene_tree_sway = 0.0f;
+
+   vg_line( head, butt, 0xff0000ff );
+   vg_line( butt, knee_l, 0xff0000ff );
+   vg_line( butt, knee_r, 0xff0000ff );
+   vg_line( foot_l, knee_l, 0xff00a0ff );
+   vg_line( foot_r, knee_r, 0xff00a0ff );
+   vg_line( head, shoulder_r, 0xa0ff00ff );
+   vg_line( shoulder_r, hand_r, 0xffff00ff );
+   m4x3_identity( temp );
+
+   m4x3_translate( temp, knee_l );
+   m4x3_rotate_x( temp, knee_pl );
+   m4x3_rotate_y( temp, leg_yl );
+
+   m4x3_expand( temp, temp1 );
+       glUniformMatrix4fv( SHADER_UNIFORM( shader_debug_vcol, "uMdl" ), 
+         1, GL_FALSE, (float *)temp1 );
+
+   scene_draw( &player_scene, -1, 0 );
+   
+   m4x3_identity( temp );
+   m4x3_expand( temp, temp1 );
+       glUniformMatrix4fv( SHADER_UNIFORM( shader_debug_vcol, "uMdl" ), 
+         1, GL_FALSE, (float *)temp1 );
+
+   vg_tex2d_bind( &tex_norwey, 0 );
+   scene_tree_sway = 0.1f;
+   scene_draw( &test_scene, -1, 0 );
+
+   vg_tex2d_bind( &tex_grid, 0 );
+   scene_tree_sway = 0.0f;
+   scene_draw( &world_scene, world_terrain_count, 0 );
+
+   vg_tex2d_bind( &tex_road, 0 );
+   scene_draw( &world_scene, world_road_count, world_terrain_count );
+
+   glDisable( GL_DEPTH_TEST );
+}
+
+void vg_ui(void)
+{
+   char buf[20];
+
+   snprintf( buf, 20, "%.2fm/s", v3_length( player_vel ) );
+   gui_text( (ui_px [2]){ 0, 0 }, buf, 1, k_text_align_left );
+   
+   snprintf( buf, 20, "%.2f %.2f %.2f m/s", 
+         player_accel[0], player_accel[1], player_accel[2] );
+
+   gui_text( (ui_px [2]){ 0, 20 }, buf, 1, k_text_align_left );
+
+   if( vg_gamepad_ready )
+   {
+      for( int i=0; i<6; i++ )
+      {
+         snprintf( buf, 20, "%.2f", vg_gamepad.axes[i] );
+         gui_text( (ui_px [2]){ 0, (i+2)*20 }, buf, 1, k_text_align_left );
+      }
+   }
+   else
+   {
+      gui_text( (ui_px [2]){ 0, 40 }, 
+            "Gamepad not ready", 1, k_text_align_left );
+   }
+}
+
+void vg_free(void){}
diff --git a/road.h b/road.h
new file mode 100644 (file)
index 0000000..29a7a4d
--- /dev/null
+++ b/road.h
@@ -0,0 +1,273 @@
+float const k_road_width = 4.0f;
+
+#define ROAD_PATCH_NODES          1024
+#define ROAD_SEGMENT_VERTICES    8
+#define ROAD_SEGMENT_INDICES       ((3*2)*5)
+#define ROAD_INDEX_BUFFER_SIZE    (ROAD_SEGMENT_INDICES * (ROAD_PATCH_NODES-2))
+#define ROAD_RENDER_DIST          12
+#define ROAD_RENDER_DIST_INDICES (ROAD_SEGMENT_INDICES*ROAD_RENDER_DIST)
+
+typedef struct road_node
+{
+   v3f pos,
+       side,
+       fwd;
+   
+} road_node;
+
+typedef struct road_patch
+{
+   road_node nodes[ ROAD_PATCH_NODES ];
+   
+   int active_id;
+   road_node *active;
+} 
+road_patch;
+
+static void road_get_range( road_patch *road, int range, int *start, int *end )
+{
+   *start = VG_MIN( VG_MAX( 0, road->active_id-range ), ROAD_PATCH_NODES-2 );
+   *end   = VG_MIN( VG_MAX( 0, road->active_id+range ), ROAD_PATCH_NODES-2 );
+}
+
+static int test_edge_with_norm( v3f p0, v3f p1, v3f norm, v3f p )
+{
+   v3f edge, edge_norm, v1;
+   
+   v3_sub( p1, p0, edge );
+   v3_cross( edge, norm, edge_norm );
+   v3_sub( p0, p, v1 );
+   
+   if( v3_dot( edge_norm, v1 ) > 0.0f )
+   {
+      vg_line( p0, p1, 0xff0000ff );
+      return 0;
+   }
+   else
+   {   
+      vg_line( p0, p1, 0xff00ff00 );
+      return 1;
+   }
+}
+
+static int patch_seg_test_inside( road_node *node, v3f p )
+{
+   road_node *next = node + 1;
+
+   v3f norm,
+       sa, sb, sc, sd;
+   
+   v3_muls( node->side, -k_road_width, sa );
+   v3_add( node->pos, sa, sa );
+   
+   v3_muls( next->side, -k_road_width, sb );
+   v3_add( next->pos, sb, sb );
+   
+   v3_muls( next->side, k_road_width, sc );
+   v3_add( next->pos, sc, sc );
+   
+   v3_muls( node->side, k_road_width, sd );
+   v3_add( node->pos, sd, sd );
+   
+   v3_cross( node->side, node->fwd, norm );
+   
+   if( !test_edge_with_norm( sa, sb, norm, p ) ) return 0;
+   if( !test_edge_with_norm( sb, sc, norm, p ) ) return 0;
+   if( !test_edge_with_norm( sc, sd, norm, p ) ) return 0;
+   if( !test_edge_with_norm( sd, sa, norm, p ) ) return 0;
+   
+   return 1;
+}
+
+static void road_patch_setplayer( road_patch *road, v3f pos )
+{
+   int idx_start, idx_end;
+   road_get_range( road, ROAD_RENDER_DIST, &idx_start, &idx_end );
+   
+   for( int i = idx_start; i < idx_end; i ++ )
+   {
+      if( patch_seg_test_inside( road->nodes + i, pos ) )
+      {
+         road->active_id = i;
+         road->active = road->nodes + i;
+         return;
+      }
+   }
+}
+
+static void road_patch_init( road_patch *road )
+{
+   road->active = road->nodes + road->active_id;
+}
+
+static void road_generate( road_patch *road )
+{
+   v3f dir_fwd = { 1.0f, 0.0f, 0.0f },
+       dir_up = { 0.0f, 1.0f, 0.0f },
+       dir_side = { 0.0f, 0.0f, 1.0f },
+       current_point = { 0.0f, 0.0f, 0.0f };
+
+   float current_rotation_amt = 0.0f,
+         current_pitch_amt = -0.2f,
+         current_roll_amt = 0.00f;
+   
+   for( int i = 0; i < ROAD_PATCH_NODES; i ++ )
+   {
+      road_node *node = road->nodes + i;
+      
+      if( (float)rand()/(float)(RAND_MAX) < 0.3f )
+      {
+         current_rotation_amt = (float)rand()/(float)(RAND_MAX)*0.6f-0.3f;
+         current_pitch_amt = (float)rand()/(float)(RAND_MAX)*0.03f-0.015f;
+      }
+      
+      v3_rotate( dir_up, current_roll_amt, dir_fwd, dir_up );
+      v3_cross( dir_fwd, dir_up, dir_side );
+      dir_side[1] = 0.0f;
+      
+      v3_rotate( dir_fwd, current_rotation_amt, (v3f){0.f, 1.f, 0.f}, dir_fwd );
+      v3_rotate( dir_fwd, current_pitch_amt, dir_side, dir_fwd );
+      v3_rotate( dir_up, current_pitch_amt, dir_side, dir_up );
+      
+      v3_muladds( current_point, dir_fwd, 7.0f, current_point );
+      
+      v3_copy( current_point, node->pos );
+      v3_copy( dir_side, node->side );
+      v3_copy( dir_fwd, node->fwd );
+      current_pitch_amt = 0.f;
+
+      node->pos[1] += (float)rand()/(float)(RAND_MAX)*0.2f;
+   }
+   
+   road->active_id = 0;
+}
+
+void draw_road_patch_dev( road_patch *road )
+{
+   v3f dir;
+   v3f norm;
+   v3f p0 = { 0.0f, 0.0f, 0.0f }, p1 = { 0.0f, 0.0f, 0.0f };
+   v3f p2; v3f p3;
+   
+   for( int i = 0; i < ROAD_PATCH_NODES-1; i ++ )
+   {
+      road_node *node = &road->nodes[i];
+      road_node *next = &road->nodes[i+1];
+      
+      vg_line( node->pos, next->pos, 0x55ffcc22 );
+      
+      // Get line dir
+      v3_sub( next->pos, node->pos, dir );
+      v3_normalize( dir );
+      
+      // Perpendicular vector
+      norm[0] = -dir[2];
+      norm[1] = 0.f;
+      norm[2] = dir[0];
+      
+      v3_muls( node->side,  k_road_width, p2 );
+      v3_add( p2, node->pos, p2 );
+      v3_muls( node->side, -k_road_width, p3 );
+      v3_add( p3, node->pos, p3 );
+      
+      vg_line( p3, p1, 0xccffcc22 );
+      vg_line( p2, p0, 0xccffcc22 );
+      
+      v3_copy( p3, p1 );
+      v3_copy( p2, p0 );
+   }
+}
+
+static void sample_road( road_patch *patch, v3f pos )
+{
+   v3f v1, norm;
+   v3_sub( patch->active->pos, pos, v1 );
+   v3_cross( patch->active->side, patch->active->fwd, norm );
+
+   float d = v3_dot( norm, v1 );
+   v3_muladds( pos, norm, d, pos );
+}
+
+static int triangle_raycast( v3f pA, v3f pB, v3f pC, v3f ray, float *height )
+{
+       v2f v0, v1, v2, vp, vp2;
+       float d, bca = 0.f, bcb = 0.f, bcc = 0.f;
+   
+   v0[0] = pB[0] - pA[0];
+   v0[1] = pB[2] - pA[2];
+   v1[0] = pC[0] - pA[0];
+   v1[1] = pC[2] - pA[2];
+   v2[0] = pB[0] - pC[0];
+   v2[1] = pB[2] - pC[2];
+       
+       d = 1.f / (v0[0]*v1[1] - v1[0]*v0[1]);
+
+#if 0
+       /* Backface culling */
+       if( v2_cross( v0, v1 ) > 0.f )
+               return;
+#endif
+
+   vp[0] = ray[0] - pA[0];
+   vp[1] = ray[2] - pA[2];
+
+   if( v2_cross( v0, vp ) > 0.f ) return 0;
+   if( v2_cross( vp, v1 ) > 0.f ) return 0;
+       
+   vp2[0] = ray[0] - pB[0];
+   vp2[1] = ray[2] - pB[2];
+
+   if( v2_cross( vp2, v2 ) > 0.f ) return 0;
+
+   bcb = (vp[0]*v1[1] - v1[0]*vp[1]) * d;
+   bcc = (v0[0]*vp[1] - vp[0]*v0[1]) * d;
+   bca = 1.f - bcb - bcc;
+                               
+   *height = pA[1]*bca + pB[1]*bcb + pC[1]*bcc;
+   return 1;
+}
+
+
+static int sample_road_height( road_patch *road, v3f pos )
+{
+   v3f norm, 
+       sa, sb, sc, sd;
+   
+   int idx_start, idx_end;
+   road_get_range( road, ROAD_RENDER_DIST, &idx_start, &idx_end );
+   
+   for( int i = idx_start; i < idx_end; i ++ )
+   {
+      road_node *node = &road->nodes[i],
+                *next = &road->nodes[i+1];
+
+      v3_muls( node->side, -k_road_width, sa );
+      v3_add( node->pos, sa, sa );
+      
+      v3_muls( next->side, -k_road_width, sb );
+      v3_add( next->pos, sb, sb );
+      
+      v3_muls( next->side, k_road_width, sc );
+      v3_add( next->pos, sc, sc );
+      
+      v3_muls( node->side, k_road_width, sd );
+      v3_add( node->pos, sd, sd );
+      
+      /* Triangle 1 */
+      float height;
+
+      if( triangle_raycast( sa, sc, sb, pos, &height ) )
+      {
+         pos[1] = height;
+         return 1;
+      }
+
+      if( triangle_raycast( sa, sd, sc, pos, &height ) )
+      {
+         pos[1] = height;
+         return 1;
+      }
+   }
+
+   return 0;
+}
diff --git a/scene.h b/scene.h
new file mode 100644 (file)
index 0000000..af9e26a
--- /dev/null
+++ b/scene.h
@@ -0,0 +1,884 @@
+typedef struct model model;
+typedef struct submodel submodel;
+typedef struct model_vert model_vert;
+typedef struct scene scene;
+typedef struct sdf_primative sdf_primative;
+typedef enum esdf_type esdf_type;
+
+GLuint tex_dual_noise;
+
+#pragma pack(push,1)
+struct model
+{
+   u32 identifier;
+
+   u32 vertex_count,
+       indice_count,
+       layer_count;
+};
+
+struct sdf_primative
+{
+   v4f origin;   /* xyz, yaw */
+   /* Cone: 
+       x  base scale
+       y  height 
+   */
+   v4f info;
+};
+
+struct submodel
+{
+   u32 indice_start,
+       indice_count,
+       vertex_start,
+       vertex_count;
+   
+   boxf bbx;
+   sdf_primative sdf;
+
+   enum esdf_type
+   {
+      k_sdf_none = 0,
+      k_sdf_cone,
+      k_sdf_sphere,
+      k_sdf_box
+   }
+   sdf_type;
+
+   char name[32];
+};
+
+struct model_vert
+{
+   v3f co,
+       norm;
+   v4f colour;
+   v2f uv;
+};
+#pragma pack(pop)
+
+struct scene
+{
+   GLuint vao, vbo, ebo;
+
+   model_vert *verts;
+   u32 *indices;
+
+   u32 vertex_count,
+       indice_count,
+       vertex_cap,
+       indice_cap;
+
+   boxf bbx;
+
+   struct shadower
+   {
+      sdf_primative sdf;
+      esdf_type sdf_type;
+   }
+   *shadowers;
+
+   u32 shadower_count,
+       shadower_cap;
+};
+
+static void scene_init( scene *pscene )
+{
+   pscene->verts = NULL;
+   pscene->indices = NULL;
+   pscene->vertex_count = 0;
+   pscene->indice_count = 0;
+   pscene->shadowers = NULL;
+   pscene->shadower_count = 0;
+   pscene->shadower_cap = 0;
+
+   v3_fill( pscene->bbx[0],  999999.9f );
+   v3_fill( pscene->bbx[1], -999999.9f );
+
+   static int noise_ready = 0;
+   if( !noise_ready )
+   {
+      noise_ready = 1;
+
+      u8 *buf = malloc( 256*256*2 );
+
+      for( int i=0; i<256*256; i++ )
+      {
+         u8 val = rand()&0xff;
+         buf[i*2] = val;
+      }
+
+      for( int y=0; y<256; y++ )
+      {
+         for( int x=0; x<256; x++ )
+         {
+            u8 *pr = &buf[(y*256+x)*2],
+               *pg = &buf[(((y+17)&0xff)*256+((x+37)&0xff))*2+1];
+            *pg = *pr;
+         }
+      }
+      
+      /* TODO: This texture should be delted somewhere */
+      glGenTextures( 1, &tex_dual_noise );
+      glBindTexture( GL_TEXTURE_2D, tex_dual_noise );
+      glTexImage2D( GL_TEXTURE_2D, 0, GL_RG, 256, 256, 0, GL_RG,
+            GL_UNSIGNED_BYTE, buf );
+
+      vg_tex2d_linear();
+      vg_tex2d_repeat();
+
+      free( buf );
+   }
+}
+
+/* https://www.shadertoy.com/view/4sfGzS */
+#define SHADER_VALUE_NOISE_3D                         \
+"uniform sampler2D uTexNoise;"                        \
+""                                                    \
+"float noise( vec3 x )"                               \
+"{"                                                   \
+   "vec3 i = floor(x);"                               \
+   "vec3 f = fract(x);"                               \
+   "f = f*f*(3.0-2.0*f);"                             \
+   "vec2 uv = (i.xy+vec2(37.0,17.0)*i.z) + f.xy;"     \
+   "vec2 rg = texture( uTexNoise, (uv+0.5)/256.0).yx;"\
+   "return mix( rg.x, rg.y, f.z );"                   \
+"}"                                                   \
+""                                                    \
+"const mat3 m = mat3( 0.00,  0.80,  0.60,"            \
+                    "-0.80,  0.36, -0.48,"            \
+                    "-0.60, -0.48,  0.64 );"          \
+""                                                    \
+"float fractalNoise( vec3 x )"                        \
+"{"                                                   \
+   "vec3 q = 8.0*x;"                                  \
+   "float f;"                                         \
+   "f  = 0.5000*noise( q ); q = m*q*2.01;"            \
+   "f += 0.2500*noise( q ); q = m*q*2.02;"            \
+   "f += 0.1250*noise( q ); q = m*q*2.03;"            \
+   "f += 0.0625*noise( q ); q = m*q*2.01;"            \
+   "return f;"                                        \
+"}"
+
+SHADER_DEFINE( shader_debug_vcol,
+       "layout (location=0) in vec3 a_co;"
+   "layout (location=1) in vec3 a_norm;"
+   "layout (location=2) in vec4 a_colour;"
+   "layout (location=3) in vec2 a_uv;"
+   ""
+       "uniform mat4 uPv;"
+   "uniform mat4 uMdl;"
+   "uniform float uTime;"
+   "uniform float uSwayAmt;"
+   ""
+   "out vec4 aColour;"
+   "out vec2 aUv;"
+   "out vec3 aNorm;"
+   "out vec3 aCo;"
+   ""
+   "vec3 compute_sway( vec3 pos )"
+   "{"
+      "vec4 sines = vec4( sin(uTime + pos.x)*1.0," 
+                         "sin(uTime*1.2 + pos.z*2.0)*1.1,"
+                         "sin(uTime*2.33)*0.5,"
+                         "sin(uTime*0.6 + pos.x*0.3)*1.3 );"
+
+      "vec3 offset = vec3( sines.x+sines.y*sines.w, 0.0, sines.x+sines.z );"
+      "return pos + offset*a_colour.r*uSwayAmt;"
+   "}"
+       ""
+       "void main()"
+       "{"
+      "vec3 swaypos = compute_sway( a_co );"
+               "gl_Position = uPv * uMdl * vec4( swaypos, 1.0 );"
+      "aColour = a_colour;"
+      "aUv = a_uv;"
+      "aNorm = normalize(mat3(uMdl) * a_norm);"
+      "aCo = a_co;"
+       "}",
+   /* Fragment */
+       "out vec4 FragColor;"
+       ""
+   "uniform int uMode;"
+   "uniform sampler2D uTexMain;"
+   "uniform sampler2D uTexGradients;"
+   ""
+   /* Include */ SHADER_VALUE_NOISE_3D
+   ""
+   "in vec4 aColour;"
+   "in vec2 aUv;"
+   "in vec3 aNorm;"
+   "in vec3 aCo;"
+   ""
+       "void main()"
+       "{"
+      "vec4 colour = vec4(1.0,0.0,0.5,1.0);"
+      "vec4 diffuse = texture( uTexMain, aUv );"
+
+      "if( uMode == 1 )"
+      "{"
+         "colour = vec4(aNorm * 0.5 + 0.5, 1.0);"
+      "}"
+      "if( uMode == 2 )"
+      "{"
+         "colour = aColour;"
+      "}"
+      "if( uMode == 3 )"
+      "{"
+         "float light = dot(aNorm, vec3(0.2,0.8,0.1));"
+         "vec3 grid3 = fract(aCo);"
+         
+         "colour = vec4(vec3(light)*(1.0-grid3*0.3),1.0);"
+      "}"
+      "if( uMode == 4 )"
+      "{"
+         "colour = vec4( aUv, 0.0, 1.0 );"
+      "}"
+      "if( uMode == 5 )"
+      "{"
+         "if( diffuse.a < 0.45 ) discard;"
+         "colour = diffuse;"
+      "}"
+      "if( uMode == 6 )"
+      "{"
+         "float r1 = fractalNoise(aCo);"
+         "colour = vec4( vec3(r1), 1.0 );"
+      "}"
+      "if( uMode == 7 )"
+      "{"
+         "if( diffuse.a < 0.45 ) discard;"
+         "float lighting = 1.0 - aColour.g;"
+         "colour = vec4(vec3(pow(lighting,1.6)*(diffuse.r*0.7+0.5)),1.0);"
+      "}"
+      "if( uMode == 8 )"
+      "{"
+         "if( diffuse.a < 0.45 ) discard;"
+         "float light = 1.0 - aColour.g;"
+         "light = pow(light,1.6)*(diffuse.r*0.7+0.5);"
+         "float r1 = fractalNoise(aCo*0.01);"
+         
+         "vec2 gradUV = vec2(light*1.9,r1+aColour.b*0.1);"
+         "vec4 gradient_sample = texture( uTexGradients, gradUV );"
+         "colour = aColour*light;"
+      "}"
+
+               "FragColor = colour;"
+       "}"
+       ,
+       UNIFORMS({ "uPv", "uMode", "uTexMain", "uTexGradients", "uTexNoise", \
+              "uTime", "uSwayAmt", "uMdl" })
+)
+
+/*
+ * Helper functions for file offsets
+ */
+static submodel *model_get_submodel( model *mdl, int id )
+{
+   return ((submodel*)(mdl+1)) + id;
+}
+
+static model_vert *model_vertex_base( model *mdl )
+{
+   return (model_vert *)model_get_submodel( mdl, mdl->layer_count );
+}
+
+static u32 *model_indice_base( model *mdl )
+{
+   return (u32 *)(model_vertex_base( mdl ) + mdl->vertex_count);
+}
+
+static model_vert *submodel_vert_data( model *mdl, submodel *sub )
+{
+   return model_vertex_base(mdl) + sub->vertex_start;
+}
+
+static u32 *submodel_indice_data( model *mdl, submodel *sub )
+{
+   return model_indice_base(mdl) + sub->indice_start;
+}
+
+/* Returns -1 if not found */
+static int submodel_get( model *mdl, const char *name )
+{
+   for( int i=0; i<mdl->layer_count; i++ )
+   {
+      if( !strcmp( model_get_submodel(mdl,i)->name, name ))
+      {
+         return i;
+      }
+   }
+   
+   return -1;
+}
+
+static void *buffer_reserve( void *buffer, u32 count, u32 *cap, u32 amount, 
+      size_t emsize )
+{
+   if( count+amount > *cap )
+   {
+      *cap = VG_MAX( (*cap)*2, (*cap)+amount );
+      
+      return realloc( buffer, (*cap) * emsize );
+   }
+
+   return buffer;
+}
+
+/* 
+ * Append a model into the scene with a given transform
+ */
+static void scene_add_model( scene *pscene, model *mdl, int id,
+      v3f pos, float yaw, float scale )
+{
+   submodel *submodel = model_get_submodel( mdl, id );
+   
+   pscene->verts = buffer_reserve( pscene->verts, pscene->vertex_count, 
+         &pscene->vertex_cap, submodel->vertex_count, sizeof(model_vert) );
+   pscene->indices = buffer_reserve( pscene->indices, pscene->indice_count,
+         &pscene->indice_cap, submodel->indice_count, sizeof(u32) );
+
+   if( submodel->sdf_type )
+   {
+      pscene->shadowers = buffer_reserve( pscene->shadowers, 
+            pscene->shadower_count, &pscene->shadower_cap, 1,
+            sizeof( struct shadower ));
+      
+      struct shadower *shadower = 
+         &pscene->shadowers[ pscene->shadower_count ++ ];
+
+      shadower->sdf = submodel->sdf;
+      shadower->sdf_type = submodel->sdf_type;
+      
+      v2_muls( shadower->sdf.info, scale, shadower->sdf.info );
+      v3_muls( shadower->sdf.origin, scale, shadower->sdf.origin );
+      v3_add( pos, shadower->sdf.origin, shadower->sdf.origin );
+   }
+   
+   /* Transform and place vertices */
+   model_vert *src_verts = submodel_vert_data( mdl, submodel );
+   u32 *src_indices = submodel_indice_data( mdl, submodel );
+   
+   m4x3f mtx;
+   m4x3_identity( mtx );
+   m4x3_translate( mtx, pos );
+   m4x3_rotate_y( mtx, yaw );
+   m4x3_scale( mtx, scale );
+
+   boxf bbxnew;
+   box_copy( submodel->bbx, bbxnew );
+   m4x3_transform_aabb( mtx, bbxnew );
+   box_concat( pscene->bbx, bbxnew );
+   
+   m3x3f rotation;
+   m4x3_to_3x3( mtx, rotation );
+
+   float rand_hue = vg_randf();
+
+   for( u32 i=0; i<submodel->vertex_count; i++ )
+   {
+      model_vert *pvert = &pscene->verts[ pscene->vertex_count+i ],
+                 *src = &src_verts[ i ];
+
+      m4x3_mulv( mtx, src->co, pvert->co );
+      m3x3_mulv( rotation, src->norm, pvert->norm );
+
+      v4_copy( src->colour, pvert->colour );
+      v2_copy( src->uv, pvert->uv );
+      
+      float rel_y = src->co[1] / submodel->bbx[1][1];
+      pvert->colour[0] = rel_y;
+      pvert->colour[2] = rand_hue;
+   }
+
+   for( u32 i=0; i<submodel->indice_count; i++ )
+   {
+      u32 *pidx = &pscene->indices[ pscene->indice_count+i ];
+      *pidx = src_indices[i] + pscene->vertex_count;
+   }
+
+   pscene->vertex_count += submodel->vertex_count;
+   pscene->indice_count += submodel->indice_count;
+}
+
+static void scene_shadow_sphere( scene *pscene, v3f sphere, 
+      v4f params, v3f lightdir )
+{
+   for( int i=0; i<pscene->vertex_count; i++ )
+   {
+      model_vert *vert = &pscene->verts[i];
+
+      v3f delta;
+      v3_sub( sphere, vert->co, delta );
+
+      float d = v3_dot( lightdir, delta );
+      v3f closest;
+
+      v3_muls( lightdir, d, closest );
+      float dist = v3_dist( closest, delta ),
+            shading = vg_maxf( dist - params[0], 0.0f );
+
+      shading = vg_minf( shading * params[1], 1.0f );
+      vert->colour[1] *= shading;
+   }
+}
+
+static void scene_shadow_gradient( scene *pscene, int comp,
+      float start, float length )
+{
+   float scale = 1.0f / length;
+
+   for( int i=0; i<pscene->vertex_count; i++ )
+   {
+      model_vert *vert = &pscene->verts[i];
+      float shading = start + vert->co[comp] * scale;
+
+      vert->colour[1] = shading;
+   }
+}
+
+/* Temporary */
+static int sample_scene_height( scene *pscene, v3f pos )
+{
+   for( int i=0; i<pscene->indice_count/3; i++ )
+   {
+      u32 *tri = &pscene->indices[i*3];
+      
+      float height;
+      if( triangle_raycast( 
+            pscene->verts[ tri[0] ].co,
+            pscene->verts[ tri[1] ].co,
+            pscene->verts[ tri[2] ].co, pos, &height ))
+      {
+         pos[1] = height;
+         return 1;
+      }
+   }
+   return 0;
+}
+
+static void sample_scene_normal( scene *pscene, v3f pos, v3f normal )
+{
+   for( int i=0; i<pscene->indice_count/3; i++ )
+   {
+      u32 *tri = &pscene->indices[i*3];
+      
+      float height;
+      if( triangle_raycast( 
+            pscene->verts[ tri[0] ].co,
+            pscene->verts[ tri[1] ].co,
+            pscene->verts[ tri[2] ].co, pos, &height ))
+      {
+         v3f v0, v1;
+
+         v3_sub( pscene->verts[ tri[1] ].co,
+                 pscene->verts[ tri[0] ].co,
+                 v0 );
+
+         v3_sub( pscene->verts[ tri[2] ].co,
+                 pscene->verts[ tri[0] ].co,
+                 v1 );
+
+         v3_cross( v0, v1, normal );
+         v3_normalize( normal );
+         return;
+      }
+   }
+
+   normal[0] = 0.0f;
+   normal[1] = 1.0f;
+   normal[2] = 0.0f;
+}
+
+/* 
+ * Experimental SDF based shadows
+ *
+ * https://iquilezles.org/articles/distfunctions/
+ */
+static float sd_cone( v3f co, sdf_primative *prim )
+{
+   float bound = prim->info[1]*1.75f;
+   if( v3_dist2( prim->origin, co ) > bound*bound )
+      return 999999.9f;
+
+   v3f p;
+   v3_sub( co, prim->origin, p );
+   
+   float h = prim->info[1];
+   v2f c = { prim->info[2], prim->info[3] };
+
+   v2f q, w, a, b;
+   v2_muls( (v2f){ c[0]/c[1], -1.0f }, h, q );
+   
+   w[0] = v2_length( (v2f){ p[0], p[2] } );
+   w[1] = p[1];
+
+   v2_muladds( w, q, -vg_clampf( v2_dot(w,q)/v2_dot(q,q), 0.0f, 1.0f ), a );
+   v2_muladd( w, q, (v2f){ vg_clampf( w[0]/q[0], 0.0f, 1.0f ), 1.0f }, b );
+
+   float k = vg_signf( q[1] ),
+         d = vg_minf( v2_dot( a,a ), v2_dot( b,b ) ),
+         s = vg_maxf( k*(w[0]*q[1]-w[1]*q[0]), k*(w[1]-q[1]) );
+
+   return sqrtf(d)*vg_signf(s);
+}
+
+#define CACHE_AMBIENT_SHAPES
+
+static float scene_ambient_sample( scene *pscene, v3f pos, v3f dir )
+{
+   float accum = 0.0f;
+
+#ifdef CACHE_AMBIENT_SHAPES
+   static struct shadower *local_shadowers[32];
+   static int local_shadower_count = 0;
+   static v3f local_shadower_last = { -99999.9f, -999999.9f, -9999999.9f };
+
+   if( v3_dist2( pos, local_shadower_last ) > 10.0f*10.0f )
+   {
+      local_shadower_count = 0;
+      v3_copy( pos, local_shadower_last );
+
+      for( int k=0; k<pscene->shadower_count; k++ )
+      {
+         struct shadower *shadower = &pscene->shadowers[k];
+
+         if( sd_cone( pos, &shadower->sdf ) <= 20.0f )
+         {
+            local_shadowers[ local_shadower_count ++ ] = shadower;
+            if( local_shadower_count == vg_list_size( local_shadowers ) )
+               break;
+         }
+      }
+   }
+#endif
+
+   for( int j=0; j<5; j++ )
+   {
+      v3f tracepos;
+      v3_muladds( pos, dir, 1.5f*(float)j, tracepos );
+
+      float mindist = 99999.9f;
+
+#ifndef CACHE_AMBIENT_SHAPES
+
+      for( int k=0; k<pscene->shadower_count; k++ ){
+         struct shadower *shadower = &pscene->shadowers[k];
+#else
+
+      for( int k=0; k<local_shadower_count; k++ ){
+         struct shadower *shadower = local_shadowers[k];
+#endif
+
+         float dist = vg_maxf( 0.0f, sd_cone( tracepos, &shadower->sdf ));
+         mindist = vg_minf( mindist, dist );
+      }
+
+
+      accum += vg_clampf( 1.0f - mindist, 0.0f, 1.0f )*0.2f;
+   }
+   
+   return accum;
+}
+
+#define DYNAMIC_GRID
+#define JUST_DO_EVERY_VERT
+
+static void scene_compute_occlusion( scene *pscene )
+{
+   v3f sundir = { 0.2f, 0.9f, 0.2f };
+   v3_normalize( sundir );
+
+   /* TODO: Make this sample grid be dynamically required.
+    *
+    *   1. Only resample the light grid (1x1x1), when a vertex is outside the 
+    *      current cube
+    *
+    *   2. Reorder all vertices so that each group of vertices that fit in a 
+    *      cube are next to eachother in the buffer. This will save cache
+    *      misses.
+    *
+    *      for the sorting algorithm, i think we can already assume that *most
+    *      vertices will be quite close to eachother. so instead of doing an
+    *      exhaustive search we can reorder 1k chunks at a time.
+    */
+
+   v3f sample_area;
+   v3_sub( pscene->bbx[1], pscene->bbx[0], sample_area );
+   v3_ceil( sample_area, sample_area );
+   int ax = sample_area[0],
+       ay = sample_area[1],
+       az = sample_area[2];
+
+#ifndef DYNAMIC_GRID
+   float *samplegrid = malloc( ax*ay*az* sizeof(float) );
+
+   for( int x=0; x<ax; x++ ){
+   for( int y=0; y<ay; y++ ){
+   for( int z=0; z<az; z++ )
+   {
+      v3f sample_pos = { x,y,z };
+      v3_add( pscene->bbx[0], sample_pos, sample_pos );
+      float accum = scene_ambient_sample( pscene, sample_pos, sundir );
+
+      samplegrid[x + y*ax + z*ax*ay] = accum;
+   }}}
+#else
+   v3i cube_pos = { -999999, -999999, -999999 };
+   int cube_resamples = 0, hits = 0, misses = 0;
+
+   float s0=0.0f,s1=0.0f,s2=0.0f,s3=0.0f,s4=0.0f,s5=0.0f,s6=0.0f,s7=0.0f;
+#endif
+
+   for( int i=0; i<pscene->vertex_count; i++ )
+   {
+      model_vert *vert = &pscene->verts[i];
+      v3f rel, q;
+      
+#ifndef DYNAMIC_GRID
+      v3_sub( vert->co, pscene->bbx[0], q );
+#else
+      v3_copy( vert->co, q );
+#endif
+
+      v3_floor( q, rel );
+      v3_sub( q, rel, q );
+      
+      int x=rel[0],
+          y=rel[1],
+          z=rel[2];
+
+#ifndef JUST_DO_EVERY_VERT
+#ifndef DYNAMIC_GRID
+      x = VG_MIN(x,ax-2);
+      y = VG_MIN(y,ay-2);
+      z = VG_MIN(z,az-2);
+      x = VG_MAX(x,0);
+      y = VG_MAX(y,0);
+      z = VG_MAX(z,0);
+
+      float 
+         s0 = samplegrid[ x    +  y*ax    + z*ax*ay],
+         s1 = samplegrid[(x+1) +  y*ax    + z*ax*ay],
+         s2 = samplegrid[ x    + (y+1)*ax + z*ax*ay],
+         s3 = samplegrid[(x+1) + (y+1)*ax + z*ax*ay],
+         s4 = samplegrid[ x    +  y*ax    + (z+1)*ax*ay],
+         s5 = samplegrid[(x+1) +  y*ax    + (z+1)*ax*ay],
+         s6 = samplegrid[ x    + (y+1)*ax + (z+1)*ax*ay],
+         s7 = samplegrid[(x+1) + (y+1)*ax + (z+1)*ax*ay],
+#else
+      if( x!=cube_pos[0] || y!=cube_pos[1] || z!=cube_pos[2] )
+      {
+         cube_pos[0] = x;
+         cube_pos[1] = y;
+         cube_pos[2] = z;
+
+         s0 = scene_ambient_sample( pscene, (v3f){ x,y,z }, sundir );
+         s1 = scene_ambient_sample( pscene, (v3f){ x+1,y,z }, sundir );
+         s2 = scene_ambient_sample( pscene, (v3f){ x,y+1,z }, sundir );
+         s3 = scene_ambient_sample( pscene, (v3f){ x+1,y+1,z }, sundir );
+         s4 = scene_ambient_sample( pscene, (v3f){ x,y,z+1 }, sundir );
+         s5 = scene_ambient_sample( pscene, (v3f){ x+1,y,z+1 }, sundir );
+         s6 = scene_ambient_sample( pscene, (v3f){ x,y+1,z+1 }, sundir );
+         s7 = scene_ambient_sample( pscene, (v3f){ x+1,y+1,z+1 }, sundir );
+
+         cube_resamples += 8;
+         misses ++;
+      }
+      else
+         hits ++;
+
+      float
+#endif
+
+         s0_s1 = vg_lerpf( s0, s1, q[0] ),
+         s2_s3 = vg_lerpf( s2, s3, q[0] ),
+         s4_s5 = vg_lerpf( s4, s5, q[0] ),
+         s6_s7 = vg_lerpf( s6, s7, q[0] ),
+
+         s0s1_s2s3 = vg_lerpf( s0_s1, s2_s3, q[1] ),
+         s4s5_s6s7 = vg_lerpf( s4_s5, s6_s7, q[1] ),
+         s0s1s2s3_s4s5s6s7 = vg_lerpf( s0s1_s2s3, s4s5_s6s7, q[2] );
+
+      vert->colour[1] = s0s1s2s3_s4s5s6s7;
+#else
+      vert->colour[1] = scene_ambient_sample( pscene, vert->co, sundir );
+#endif
+   }
+
+#ifndef DYNAMIC_GRID
+   int cube_resamples = -1, misses = 0, hits = 0;
+#endif
+
+   int static_samples = ax*ay*az,
+       vertex_samples = pscene->vertex_count;
+
+   if( cube_resamples < static_samples )
+      vg_success( "Walking cube beat static grid (%d<%d. %d)!\n",
+         cube_resamples, static_samples, vertex_samples );
+   else
+      vg_warn( "Walking cube was worse than static grid (%d<%d. %d).\n",
+         cube_resamples, static_samples, vertex_samples );
+
+   vg_info( "Hits; %d, misses: %d\n", hits, misses );
+
+#ifndef DYNAMIC_GRID
+   free( samplegrid );
+#endif
+
+   return;
+
+   for( int i=0; i<pscene->vertex_count; i++ )
+   {
+      model_vert *vert = &pscene->verts[i];
+      float accum = 0.0f;
+
+      for( int j=0; j<5; j++ )
+      {
+         v3f tracepos;
+         v3_copy( vert->co, tracepos );
+         v3_muladds( tracepos, sundir, 1.5f*(float)j, tracepos );
+
+         float mindist = 99999.9f;
+
+         for( int k=0; k<pscene->shadower_count; k++ )
+         {
+            struct shadower *shadower = &pscene->shadowers[k];
+            float dist = vg_maxf( 0.0f, sd_cone( tracepos, &shadower->sdf ));
+            mindist = vg_minf( mindist, dist );
+         }
+
+         accum += vg_clampf( 1.0f - mindist, 0.0f, 1.0f )*0.2f;
+      }
+
+      vert->colour[1] = vg_minf( accum, 1.0f );
+   }
+}
+
+static void scene_upload( scene *pscene )
+{
+   glGenVertexArrays( 1, &pscene->vao );
+   glGenBuffers( 1, &pscene->vbo );
+   glGenBuffers( 1, &pscene->ebo );
+   glBindVertexArray( pscene->vao );
+
+   glBindBuffer( GL_ARRAY_BUFFER, pscene->vbo );
+   glBufferData( GL_ARRAY_BUFFER, pscene->vertex_count*sizeof(model_vert),
+         pscene->verts, GL_STATIC_DRAW );
+
+   glBindVertexArray( pscene->vao );
+   glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, pscene->ebo );
+   glBufferData( GL_ELEMENT_ARRAY_BUFFER, pscene->indice_count*sizeof(u32),
+         pscene->indices, GL_STATIC_DRAW );
+   
+   glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 
+         sizeof(model_vert), (void*)0 );
+   glEnableVertexAttribArray( 0 );
+
+   glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE, 
+         sizeof(model_vert), (void *)offsetof(model_vert, norm) );
+   glEnableVertexAttribArray( 1 );
+
+   glVertexAttribPointer( 2, 4, GL_FLOAT, GL_FALSE, 
+         sizeof(model_vert), (void *)offsetof(model_vert, colour) );
+   glEnableVertexAttribArray( 2 );
+
+   glVertexAttribPointer( 3, 2, GL_FLOAT, GL_FALSE, 
+         sizeof(model_vert), (void *)offsetof(model_vert, uv) );
+   glEnableVertexAttribArray( 3 );
+
+   VG_CHECK_GL();
+
+   vg_info( "Scene upload\n" );
+   vg_info( "   indices:%u\n", pscene->indice_count );
+   vg_info( "   verts:%u\n", pscene->vertex_count );
+}
+
+float scene_tree_sway = 0.1f;
+static void scene_draw( scene *pscene, int count, int start )
+{
+   SHADER_USE( shader_debug_vcol );
+
+       glUniformMatrix4fv( SHADER_UNIFORM( shader_debug_vcol, "uPv" ), 
+         1, GL_FALSE, (float *)vg_pv );
+
+   glUniform1i( SHADER_UNIFORM( shader_debug_vcol, "uMode" ), debugview );
+   glUniform1i( SHADER_UNIFORM( shader_debug_vcol, "uTexMain" ), 0 );
+   
+   glUniform1i( SHADER_UNIFORM( shader_debug_vcol, "uTexGradients" ), 1 );
+   vg_tex2d_bind( &tex_gradients, 1 );
+
+   glUniform1i( SHADER_UNIFORM( shader_debug_vcol, "uTexNoise" ), 2 );
+   glActiveTexture( GL_TEXTURE2 );
+   glBindTexture( GL_TEXTURE_2D, tex_dual_noise );
+
+   glUniform1f( SHADER_UNIFORM( shader_debug_vcol, "uTime" ), vg_time );
+   glUniform1f( SHADER_UNIFORM( shader_debug_vcol, "uSwayAmt" ), 
+         scene_tree_sway );
+
+   glBindVertexArray( pscene->vao );
+
+   if( count == -1 )
+   {
+      glDrawElements( GL_TRIANGLES, pscene->indice_count,
+         GL_UNSIGNED_INT,
+         (void *)0
+      );
+   }
+   else
+   {
+      glDrawElements( GL_TRIANGLES, count,
+         GL_UNSIGNED_INT,
+         (void *)(start*sizeof(u32))
+      );
+   }
+
+   if( debugsdf )
+   {
+      for( int i=0; i<pscene->shadower_count; i++ )
+      {
+         struct shadower *shadower = &pscene->shadowers[i];
+
+         v3f base, side;
+         v3_copy( shadower->sdf.origin, base );
+         base[1] -= shadower->sdf.info[1];
+         v3_copy( base, side );
+         side[0] += shadower->sdf.info[0];
+
+         vg_line2( shadower->sdf.origin, base, 0xff00ff00, 0xff0000ff );
+         vg_line2( side, base, 0xff00ff00, 0xff0000ff );
+         vg_line( side, shadower->sdf.origin, 0xff00ff00 );
+      }
+
+      v3f p0 = { pscene->bbx[0][0], pscene->bbx[0][1], pscene->bbx[0][2] },
+          p1 = { pscene->bbx[0][0], pscene->bbx[1][1], pscene->bbx[0][2] }, 
+          p2 = { pscene->bbx[1][0], pscene->bbx[1][1], pscene->bbx[0][2] },
+          p3 = { pscene->bbx[1][0], pscene->bbx[0][1], pscene->bbx[0][2] },
+
+          p4 = { pscene->bbx[0][0], pscene->bbx[0][1], pscene->bbx[1][2] },
+          p5 = { pscene->bbx[0][0], pscene->bbx[1][1], pscene->bbx[1][2] }, 
+          p6 = { pscene->bbx[1][0], pscene->bbx[1][1], pscene->bbx[1][2] },
+          p7 = { pscene->bbx[1][0], pscene->bbx[0][1], pscene->bbx[1][2] };
+      
+      u32 col = 0xffff00c8;
+      vg_line( p0, p1, col );
+      vg_line( p1, p2, col );
+      vg_line( p2, p3, col );
+      vg_line( p3, p0, col );
+
+      vg_line( p4, p5, col );
+      vg_line( p5, p6, col );
+      vg_line( p6, p7, col );
+      vg_line( p7, p4, col );
+
+      vg_line( p0, p4, col );
+      vg_line( p1, p5, col );
+      vg_line( p2, p6, col );
+      vg_line( p3, p7, col );
+   }
+}
+
+static void scene_register(void)
+{
+   SHADER_INIT( shader_debug_vcol );
+}
diff --git a/sprites_autocombine.h b/sprites_autocombine.h
new file mode 100644 (file)
index 0000000..cb1229f
--- /dev/null
@@ -0,0 +1,8 @@
+enum sprites_auto_combine_index
+{
+       k_sprite_*,
+};
+
+static struct vg_sprite sprites_auto_combine[] = 
+{
+};
\ No newline at end of file
diff --git a/textures/gradients.png b/textures/gradients.png
new file mode 100644 (file)
index 0000000..8b60c3b
Binary files /dev/null and b/textures/gradients.png differ
diff --git a/textures/grid.png b/textures/grid.png
new file mode 100644 (file)
index 0000000..3b9d99d
Binary files /dev/null and b/textures/grid.png differ
diff --git a/textures/norway_foliage.png b/textures/norway_foliage.png
new file mode 100644 (file)
index 0000000..707558a
Binary files /dev/null and b/textures/norway_foliage.png differ
diff --git a/textures/norwey.png b/textures/norwey.png
new file mode 100644 (file)
index 0000000..9ac01ff
Binary files /dev/null and b/textures/norwey.png differ
diff --git a/textures/road.png b/textures/road.png
new file mode 100644 (file)
index 0000000..06c95cc
Binary files /dev/null and b/textures/road.png differ
diff --git a/vg.conf b/vg.conf
new file mode 100644 (file)
index 0000000..896d68a
--- /dev/null
+++ b/vg.conf
@@ -0,0 +1,2 @@
+vg_src="main.c"
+vg_target="game"
diff --git a/vg_config.h b/vg_config.h
new file mode 100644 (file)
index 0000000..b1348a0
--- /dev/null
@@ -0,0 +1,26 @@
+// Copyright (C) 2021 Harry Godden (hgn) - All Rights Reserved
+#define VG_CONFIG
+
+static struct button_binding vg_button_binds[] = 
+{
+       { .name = "primary", .bind = GLFW_MOUSE_BUTTON_LEFT },
+       { .name = "secondary", .bind = GLFW_MOUSE_BUTTON_RIGHT },
+   { .name = "left", .bind = GLFW_KEY_A },
+   { .name = "right", .bind = GLFW_KEY_D },
+   { .name = "forward", .bind = GLFW_KEY_W },
+   { .name = "back", .bind = GLFW_KEY_S },
+   { .name = "up", .bind = GLFW_KEY_R },
+   { .name = "down", .bind = GLFW_KEY_F },
+   { .name = "yawl", .bind = GLFW_KEY_Q },
+   { .name = "yawr", .bind = GLFW_KEY_E },
+};
+
+static struct axis_binding vg_axis_binds[] = 
+{
+       { .name = "horizontal", .axis = GLFW_GAMEPAD_AXIS_LEFT_X },
+       { .name = "vertical",   .axis = GLFW_GAMEPAD_AXIS_LEFT_Y }
+};
+
+static struct vg_achievement vg_achievements[] =
+{
+};