--- /dev/null
+# 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
--- /dev/null
+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" )
--- /dev/null
+#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){}
--- /dev/null
+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;
+}
--- /dev/null
+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 );
+}
--- /dev/null
+enum sprites_auto_combine_index
+{
+ k_sprite_*,
+};
+
+static struct vg_sprite sprites_auto_combine[] =
+{
+};
\ No newline at end of file
--- /dev/null
+vg_src="main.c"
+vg_target="game"
--- /dev/null
+// 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[] =
+{
+};