commit 8d9917332526c3f791c64c5b4354f3d6689226d0
parent 9b71f78c33d4ed0f7347eb7a532aea695bd3dec9
Author: Michael Savage <mikejsavage@gmail.com>
Date: Sat, 4 Nov 2017 19:48:46 +0200
Add networking, fireballs, and semi broken walking to the clipmap engine
Diffstat:
clipmap.cc | | | 418 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- |
make.lua | | | 2 | +- |
2 files changed, 413 insertions(+), 7 deletions(-)
diff --git a/clipmap.cc b/clipmap.cc
@@ -8,6 +8,11 @@
#include "decompress_bc.h"
#include "gl.h"
#include "skybox.h"
+#include "stream.h"
+#include "hashtable.h"
+#include "pool.h"
+#include "profiler.h"
+#include "platform_network.h"
#include "libs/lz4/lz4.h"
@@ -20,8 +25,37 @@ struct ClipmapTerrain {
HeightmapQuadTree quadtree;
};
+static const float EYE_HEIGHT = 1.8f;
+
static ClipmapTerrain clipmap;
+struct Fireball {
+ v3 pos;
+ v3 velocity;
+};
+
+struct Explosion {
+ v3 pos;
+ double created_at;
+};
+
+struct Player {
+ u64 sid;
+ v3 pos;
+};
+
+static UDPSocket sock;
+static NetAddress server_addr;
+static u64 sid;
+static double last_connection_attempt = -5.0; // TODO
+static bool connected = false;
+
+static Pool< Fireball, 1024 > fireballs;
+static Pool< Explosion, 1024 > explosions;
+
+static Pool< Player, 1024 > players;
+static HashTable< Player *, 1024 * 2 > sid_to_player;
+
static constexpr u32 PATCH_RESOLUTION = 48;
static constexpr u32 NUM_LODS = 7;
@@ -163,6 +197,13 @@ GAME_INIT( game_init ) {
game->sun_angle = 0.3f;
skybox_init( &game->skybox );
+
+ sock = net_new_udp( NET_NONBLOCKING );
+
+ if( !dns_first( "localhost", &server_addr ) ) {
+ FATAL( "dns_first" );
+ }
+ server_addr.port = 13337;
}
static void draw_qt( MinMaxu32 aabb, const array< HeightmapQuadTreeNode > nodes, size_t node_idx ) {
@@ -180,6 +221,30 @@ static void draw_qt( MinMaxu32 aabb, const array< HeightmapQuadTreeNode > nodes,
}
}
+static bool segment_vs_quadtree( const HeightmapQuadTree * qt, const v3 & start, const v3 & end, float * t, v3 * xnormal ) {
+ float dont_care_t;
+ v3 dont_care_normal;
+ if( t == NULL )
+ t = &dont_care_t;
+ if( xnormal == NULL )
+ xnormal = &dont_care_normal;
+
+ *t = 1.0f;
+
+ if( start == end ) return false;
+ Ray3 ray( start, end - start );
+ float local_t;
+ v3 local_normal;
+ bool hit = ray_vs_quadtree( qt, ray, &local_t, &local_normal );
+ if( !hit || local_t > 1.0f )
+ return false;
+
+ *t = local_t;
+ *xnormal = local_normal;
+
+ return true;
+}
+
GAME_FRAME( game_frame ) {
float fb = float( input->keys[ KEY_W ] - input->keys[ KEY_S ] );
float lr = float( input->keys[ KEY_D ] - input->keys[ KEY_A ] );
@@ -195,6 +260,10 @@ GAME_FRAME( game_frame ) {
game->draw_wireframe = !game->draw_wireframe;
if( input->keys[ KEY_T ] && input->key_edges[ KEY_T ] )
game->draw_quadtree = !game->draw_quadtree;
+ if( input->keys[ KEY_N ] && input->key_edges[ KEY_N ] ) {
+ game->noclip = !game->noclip;
+ game->velocity = v3( 0 );
+ }
dpitch -= float( input->mouse_dy * 0.25 );
dyaw -= float( input->mouse_dx * 0.25 );
@@ -210,13 +279,156 @@ GAME_FRAME( game_frame ) {
v3 right = normalize( cross( forward, world_up ) );
v3 up = normalize( cross( right, forward ) );
- const float speed = 100.0f;
- game->pos += forward * dt * fb * speed;
- game->pos += right * dt * lr * speed;
- game->pos.z += dt * dz * speed;
+ if( game->noclip ) {
+ const float speed = 100.0f;
+ game->pos += forward * dt * fb * speed;
+ game->pos += right * dt * lr * speed;
+ game->pos.z += dt * dz * speed;
+ }
+ else {
+ if( game->on_ground ) {
+ if( input->keys[ KEY_SPACE ] ) {
+ game->velocity.z = 9.8;
+ game->on_ground = false;
+ }
+ }
+
+ const float acceleration = 12;
+ if( game->on_ground ) {
+ // apply friction
+ if( dot( game->velocity, game->velocity ) < 0.01 ) {
+ game->velocity.x = 0;
+ game->velocity.y = 0;
+ }
+ else {
+ float current_speed = length( game->velocity );
+ float drop = max( acceleration, current_speed ) * acceleration * dt / 2.0f;
+ float new_speed = max( 0.0f, current_speed - drop );
+ game->velocity = new_speed * ( game->velocity / current_speed );
+ }
+
+ if( fb != 0 || lr != 0 ) {
+ // apply acceleration
+ float max_speed = 10;
+
+ v3 forward_xy = v3_forward_xy( game->yaw );
+ v3 right_xy = normalize( cross( forward_xy, world_up ) );
+ v3 desired_direction = fb * forward_xy + lr * right_xy;
+
+ desired_direction = normalize( desired_direction );
+
+ float speed_in_desired_direction = dot( game->velocity, desired_direction );
+
+ if( speed_in_desired_direction < max_speed ) {
+ float accel_speed = min( acceleration * dt * max_speed, max_speed - speed_in_desired_direction );
+ game->velocity += accel_speed * desired_direction;
+ }
+ }
+ }
+
+ int spins = 0;
+ float remaining_t = dt;
+ while( remaining_t > 0 && spins < 5 ) {
+ spins++;
+ if( game->on_ground ) {
+ const float step_height = 0.1f;
+
+ v3 low_start = game->pos + v3( 2048, 2048, 0 );
+ v3 low_end = low_start + game->velocity * remaining_t;
+
+ v3 high_start = game->pos + v3( 2048, 2048, 0 ) + v3( 0, 0, step_height );
+ v3 high_end = high_start + game->velocity * remaining_t;
+
+ float low_t;
+ // initialise high_t to -1 so we never pick the high trace
+ // if we couldn't step up (fails the high_t > low_t test)
+ float high_t = -1;
+ v3 low_normal, high_normal;
+
+ bool low_hit = segment_vs_quadtree( &clipmap.quadtree, low_start, low_end, &low_t, &low_normal );
+ bool high_hit = false;
+
+ bool step_up_hit = segment_vs_quadtree( &clipmap.quadtree, low_start, high_start, NULL, NULL );
+ if( !step_up_hit ) {
+ high_hit = segment_vs_quadtree( &clipmap.quadtree, high_start, high_end, &high_t, &high_normal );
+ }
+
+ v3 step_down_start;
+ float step_down_distance;
+ bool should_step_down = false;
+
+ if( high_t > low_t ) {
+ if( high_hit ) {
+ game->pos = high_start + game->velocity * high_t * remaining_t - v3( 2048, 2048, 0 );
+ remaining_t = ( 1.0f - high_t ) * remaining_t;
+ }
+ else {
+ step_down_start = high_end;
+ step_down_distance = step_height * 2;
+ should_step_down = true;
+ remaining_t = 0;
+ }
+ }
+ else {
+ if( low_hit ) {
+ game->pos = low_start + game->velocity * low_t * remaining_t - v3( 2048, 2048, 0 );
+ remaining_t = ( 1.0f - low_t ) * remaining_t;
+ }
+ else {
+ step_down_start = low_end;
+ step_down_distance = step_height;
+ should_step_down = true;
+ remaining_t = 0;
+ }
+ }
+
+ if( should_step_down ) {
+ float step_down_t;
+ v3 step_down_end = step_down_start - v3( 0, 0, step_down_distance );
+
+ bool step_down_hit = segment_vs_quadtree( &clipmap.quadtree, step_down_start, step_down_end, &step_down_t, NULL );
+
+ game->pos = lerp( step_down_start, step_down_t, step_down_end ) - v3( 2048, 2048, 0 );
+ game->on_ground = step_down_hit;
+ }
+ }
+
+ // TODO: this might be quite harmful
+ if( !game->on_ground && remaining_t > 0 ) {
+ // TODO: rk4 integration
+ const float gravity = -30.0f;
+ game->velocity += v3( 0, 0, gravity ) * remaining_t;
+
+ v3 start = game->pos + v3( 2048, 2048, 0 );
+ v3 end = start + game->velocity * remaining_t;
+ float t;
+ v3 normal;
+ bool hit = segment_vs_quadtree( &clipmap.quadtree, start, end, &t, &normal );
+ if( hit ) {
+ // TODO: check normal is flat enough, otherwise stay in the air
+ game->on_ground = true;
+ game->pos += game->velocity * t * remaining_t;
+ // TODO: truncate velocity
+ game->velocity.z = 0;
+
+ remaining_t = ( 1.0f - t ) * remaining_t;
+ }
+ else {
+ game->pos = end - v3( 2048, 2048, 0 );
+ remaining_t = 0;
+ }
+ }
+
+ if( game->on_ground ) {
+ // move up 1mm so float imprecision doesn't make us fall through the world
+ game->pos.z += 0.001f;
+ }
+ }
+ }
+ v3 eye_pos = game->pos + v3( 0, 0, EYE_HEIGHT );
m4 P = m4_perspective( VERTICAL_FOV, get_aspect_ratio(), NEAR_PLANE_DEPTH, FAR_PLANE_DEPTH );
- m4 V = m4_view( forward, right, up, game->pos );
+ m4 V = m4_view( forward, right, up, eye_pos );
m4 Vsky = m4_view( forward, right, up, v3( 0 ) );
v3 sun_dir = v3( -cosf( game->sun_angle ), 0, sinf( game->sun_angle ) );
@@ -267,6 +479,199 @@ GAME_FRAME( game_frame ) {
immediate_arrow( impact, normal, 16, v4( 0, 1, 1, 1 ) );
}
+ RenderState trace_render_state;
+ trace_render_state.shader = get_shader( SHADER_FLAT_VERTEX_COLOURS );
+ trace_render_state.uniforms[ UNIFORMS_VIEW ] = view_uniforms;
+ trace_render_state.wireframe = break3;
+ immediate_render( trace_render_state );
+ }
+
+ {
+ const float aspect = get_aspect_ratio();
+ const float crosshair_thickness = 0.0025f;
+ const float crosshair_length = 0.01f;
+
+ const v4 red( 1, 0, 0, 1 );
+ immediate_triangle(
+ v3( -crosshair_length, crosshair_thickness, 0 ),
+ v3( -crosshair_length, -crosshair_thickness, 0 ),
+ v3( crosshair_length, crosshair_thickness, 0 ),
+ red
+ );
+ immediate_triangle(
+ v3( crosshair_length, -crosshair_thickness, 0 ),
+ v3( crosshair_length, crosshair_thickness, 0 ),
+ v3( -crosshair_length, -crosshair_thickness, 0 ),
+ red
+ );
+ immediate_triangle(
+ v3( crosshair_thickness / aspect, crosshair_length * aspect, 0 ),
+ v3( -crosshair_thickness / aspect, crosshair_length * aspect, 0 ),
+ v3( crosshair_thickness / aspect, -crosshair_length * aspect, 0 ),
+ red
+ );
+ immediate_triangle(
+ v3( -crosshair_thickness / aspect, -crosshair_length * aspect, 0 ),
+ v3( crosshair_thickness / aspect, -crosshair_length * aspect, 0 ),
+ v3( -crosshair_thickness / aspect, crosshair_length * aspect, 0 ),
+ red
+ );
+ RenderState render_state;
+ render_state.shader = get_shader( SHADER_UI );
+ render_state.depth_func = DEPTHFUNC_DISABLED;
+ immediate_render( render_state );
+ }
+
+ if( input->keys[ KEY_F ] ) {
+ Fireball * fireball = fireballs.acquire();
+ fireball->pos = game->pos + v3( 0.0f, 0.0f, EYE_HEIGHT );
+ fireball->velocity = 128.0f * forward;
+ }
+
+ {
+ {
+ PROFILE_BLOCK( "Update fireballs" );
+ for( size_t i = 0; i < fireballs.elems.n; i++ ) {
+ Fireball * fireball = &fireballs.elems[ i ];
+ Ray3 ray( fireball->pos + v3( 2048.0f, 2048.0f, 0 ), fireball->velocity * dt );
+
+ float t;
+ v3 normal;
+ if( ray_vs_quadtree( &clipmap.quadtree, ray, &t, &normal ) && t <= 1 ) {
+ Explosion * explosion = explosions.acquire();
+ explosion->pos = fireball->pos + t * fireball->velocity;
+ explosion->created_at = current_time;
+
+ fireballs.release( fireball );
+ i--;
+ }
+ else {
+ const float gravity = 9.81f;
+ fireball->pos += fireball->velocity * dt;
+ fireball->velocity.z -= dt * gravity;
+ immediate_sphere( fireball->pos, 4, v4( 1, 0, 0, 1 ), 4 );
+ }
+ }
+ }
+
+ RenderState fireball_render_state;
+ fireball_render_state.shader = get_shader( SHADER_FLAT_VERTEX_COLOURS );
+ fireball_render_state.uniforms[ UNIFORMS_VIEW ] = view_uniforms;
+ immediate_render( fireball_render_state );
+
+ for( size_t i = 0; i < explosions.elems.n; i++ ) {
+ Explosion * explosion = &explosions.elems[ i ];
+ float t = float( current_time - explosion->created_at );
+ if( t < 0.5f ) {
+ immediate_sphere( explosion->pos, t * 32.0f, v4( 1, 0.5, 0, 1 ), 4 );
+ }
+ else {
+ explosions.release( explosion );
+ i--;
+ }
+ }
+
+ RenderState explosion_render_state;
+ explosion_render_state.shader = get_shader( SHADER_FLAT_VERTEX_COLOURS );
+ explosion_render_state.uniforms[ UNIFORMS_VIEW ] = view_uniforms;
+ immediate_render( explosion_render_state );
+ }
+
+ {
+ char buf[ 1400 ];
+ NetAddress recv_addr;
+ size_t len;
+ if( !net_tryrecv( sock, buf, sizeof( buf ), &recv_addr, &len ) ) {
+ len = 0;
+ }
+ if( recv_addr != server_addr ) {
+ len = 0;
+ }
+
+ ReadStream rs( buf, len );
+
+ if( connected && len > 0 ) {
+ char write_buf[ 1400 ];
+ WriteStream ws( write_buf );
+ write( &ws, sid );
+ write( &ws, game->pos );
+
+ net_send( sock, ws.start, ws.len(), server_addr );
+
+ u16 player_count = read_u16( &rs );
+ ReadStreamCheckpoint rsc = rs.checkpoint();
+
+ for( u16 i = 0; i < player_count; i++ ) {
+ read_u64( &rs );
+ u8 player_state = read_u8( &rs );
+ if( player_state == 0 ) {
+ v3 pos;
+ read( &rs, &pos );
+ }
+ }
+
+ if( rs.done() ) {
+ rs.reset( &rsc );
+ for( u16 i = 0; i < player_count; i++ ) {
+ u64 player_sid = read_u64( &rs );
+ u8 player_state = read_u8( &rs );
+ v3 pos;
+ if( player_state == 0 ) {
+ read( &rs, &pos );
+ }
+
+ Player * player = NULL;
+ if( !sid_to_player.get( player_sid, &player ) && player_state == 0 ) {
+ player = players.acquire();
+ ASSERT( player != NULL ); // TODO
+ sid_to_player.add( player_sid, player );
+ player->sid = player_sid;
+ }
+
+ if( player_state == 0 ) {
+ player->pos = pos;
+ }
+ else if( player_state == 1 ) {
+ ggprint( "{08x} disconnected\n", player->sid );
+ players.release( player );
+ }
+ }
+
+ ASSERT( rs.done() );
+ }
+ else {
+ if( len > 0 ) {
+ FATAL( "bad message from the server" );
+ }
+ }
+ }
+ else if( len > 0 ) {
+ sid = read_u64( &rs );
+ if( rs.ok ) {
+ connected = true;
+ }
+ }
+
+ if( !connected && current_time - last_connection_attempt > 1.0 ) {
+ char write_buf[ 1400 ];
+ WriteStream ws( write_buf );
+
+ write_u64( &ws, ~u64( 0 ) );
+ write( &ws, game->pos );
+
+ net_send( sock, ws.start, ws.len(), server_addr );
+
+ last_connection_attempt = current_time;
+ }
+
+ for( const Player & player : players.elems ) {
+ if( player.sid != sid ) {
+ v3 mins = player.pos - v3( 0.4f, 0.4f, 0.0f );
+ v3 maxs = player.pos + v3( 0.4f, 0.4f, EYE_HEIGHT + 0.1f );
+ immediate_aabb( mins, maxs, v4( 1, 1, 0, 1 ) );
+ }
+ }
+
RenderState impact_render_state;
impact_render_state.shader = get_shader( SHADER_FLAT_VERTEX_COLOURS );
impact_render_state.uniforms[ UNIFORMS_VIEW ] = view_uniforms;
@@ -286,7 +691,8 @@ GAME_FRAME( game_frame ) {
}
{
- const str< 128 > status( "Frame time: {.1}ms FPS: {.1}", dt * 1000.0f, 1.0f / dt );
+ const str< 128 > status( "Frame time: {.1}ms FPS: {.1} {}", dt * 1000.0f, 1.0f / dt,
+ connected ? "connected!" : "connecting" );
draw_text( status.c_str(), 2, 2, 16.0f );
draw_text( str< 128 >( "pos = {.1}", game->pos ).c_str(), 2, 20, 16 );
draw_text( str< 128 >( "drawcalls = {}, verts = {}", renderer_num_draw_calls(), renderer_num_vertices() ).c_str(), 2, 38, 16 );
diff --git a/make.lua b/make.lua
@@ -49,7 +49,7 @@ require( "libs/squish" )
bin( "bsp", { "main", "bsp", "bsp_renderer", game_objs }, { game_libs } )
bin( "sm", { "main", "shadow_map", game_objs }, { game_libs } )
-bin( "clipmap", { "main", "clipmap", "heightmap", "skybox", "decompress_bc", game_objs }, { "lz4", game_libs } )
+bin( "clipmap", { "main", "clipmap", "heightmap", "skybox", "decompress_bc", "platform_network", game_objs }, { "lz4", game_libs } )
msvc_bin_ldflags( "bsp", "opengl32.lib gdi32.lib" )
gcc_bin_ldflags( "bsp", game_ldflags )