medfall

A super great game engine
Log | Files | Refs

hm.cc (14960B)


      1 #include <float.h>
      2 
      3 #include "game.h"
      4 #include "intrinsics.h"
      5 #include "log.h"
      6 #include "heightmap.h"
      7 #include "terrain_manager.h"
      8 #include "linear_algebra.h"
      9 #include "skybox.h"
     10 #include "gl.h"
     11 #include "renderer.h"
     12 #include "shaders.h"
     13 #include "immediate.h"
     14 #include "text_renderer.h"
     15 #include "pool.h"
     16 #include "hashtable.h"
     17 #include "stream.h"
     18 #include "http.h"
     19 #include "obj.h"
     20 #include "profiler.h"
     21 #include "platform_io.h"
     22 #include "platform_network.h"
     23 
     24 #include "libs/lz4/lz4.h"
     25 
     26 static const float EYE_HEIGHT = 1.8f;
     27 
     28 static UDPSocket sock;
     29 static NetAddress server_addr;
     30 static u64 sid;
     31 static double last_connection_attempt = -5.0; // TODO
     32 static bool connected = false;
     33 static VB instance_data;
     34 static u32 num_trees;
     35 
     36 static Mesh tree_mesh;
     37 
     38 static void load_trees( MemoryArena * arena ) {
     39 	MEMARENA_SCOPED_CHECKPOINT( arena );
     40 
     41 	size_t compressed_len;
     42 	u8 * compressed_trees = file_get_contents( "terrains/gta16.png.parts/trees.lz4", &compressed_len );
     43 
     44 	// TODO: don't hardcode this!
     45 	array< v3 > trees = memarena_push_array( arena, v3, 300000 );
     46 	int decompressed_len = LZ4_decompress_safe( ( char * ) compressed_trees, ( char * ) trees.ptr(), compressed_len, trees.num_bytes() );
     47 	ASSERT( decompressed_len > 0 );
     48 
     49 	free( compressed_trees );
     50 
     51 	num_trees = decompressed_len / sizeof( v3 );
     52 	array< m4 > tree_matrices = memarena_push_array( arena, m4, num_trees );
     53 
     54 	m4 rot = m4_rotx( deg_to_rad( 90 ) );
     55 	for( size_t i = 0; i < num_trees; i++ ) {
     56 		tree_matrices[ i ] = m4_translation( trees[ i ] ) * rot;
     57 	}
     58 
     59 	instance_data = renderer_new_vb( tree_matrices );
     60 
     61 	tree_mesh = load_obj( "models/trees/PineTree.obj", arena );
     62 }
     63 
     64 GAME_INIT( game_init ) {
     65 	net_init();
     66 
     67 	game->pos = v3( 1500, 1500, 120 );
     68 	game->pos = v3( 1495, 1432, 180 );
     69 	game->noclip = false;
     70 	game->pitch = 0;
     71 	game->yaw = 45;
     72 
     73 	terrain_init( &game->tm, "terrains/gta16.png.parts", &mem->persistent_arena, &game->background_tasks );
     74 	terrain_teleport( &game->tm, game->pos );
     75 
     76 	game->sun_angle = 0.3f;
     77 
     78 	skybox_init( &game->skybox );
     79 
     80 	load_trees( &mem->persistent_arena );
     81 
     82 	sock = net_new_udp( NET_NONBLOCKING );
     83 
     84 	if( !dns_first( "localhost", &server_addr ) ) {
     85 		FATAL( "dns_first" );
     86 	}
     87 	server_addr.port = 13337;
     88 }
     89 
     90 static m4 camera_to_view( v3 position, v3 angles ) {
     91 	return m4_translation( -position ) * m4_rotz( -angles.y ) * m4_rotx( -angles.x );
     92 }
     93 
     94 struct Fireball {
     95 	v3 pos;
     96 	v3 velocity;
     97 };
     98 
     99 struct Explosion {
    100 	v3 pos;
    101 	double created_at;
    102 };
    103 
    104 struct Player {
    105 	u64 sid;
    106 	v3 pos;
    107 };
    108 
    109 static Pool< Fireball, 1024 > fireballs;
    110 static Pool< Explosion, 1024 > explosions;
    111 
    112 static Pool< Player, 1024 > players;
    113 static HashTable< Player *, 1024 * 2 > sid_to_player;
    114 
    115 GAME_FRAME( game_frame ) {
    116 	PROFILE_FUNCTION();
    117 
    118 	{
    119 		PROFILE_BLOCK( "renderer_begin_frame" );
    120 		renderer_begin_frame();
    121 		renderer_begin_pass( RENDERER_CLEAR_COLOUR_DONT, RENDERER_CLEAR_DEPTH_DO );
    122 	}
    123 
    124 	float fb = float( input->keys[ KEY_W ] - input->keys[ KEY_S ] );
    125 	float lr = float( input->keys[ KEY_D ] - input->keys[ KEY_A ] );
    126 	float dz = float( input->keys[ KEY_SPACE ] - input->keys[ KEY_LEFTSHIFT ] );
    127 
    128 	float dpitch = input->keys[ KEY_DOWNARROW ] - input->keys[ KEY_UPARROW ];
    129 	float dyaw = input->keys[ KEY_LEFTARROW ] - input->keys[ KEY_RIGHTARROW ];
    130 
    131 	dpitch = input->keys[ KEY_K ] - input->keys[ KEY_I ];
    132 	dyaw += input->keys[ KEY_J ] - input->keys[ KEY_L ];
    133 
    134 	dpitch -= float( input->mouse_dy * 0.25 );
    135 	dyaw -= float( input->mouse_dx * 0.25 );
    136 
    137 	game->pitch = clamp( game->pitch + dpitch * dt * 100, -89.9f, 89.9f );
    138 	game->yaw += dyaw * dt * 100;
    139 
    140 	const float dsun = ( input->keys[ KEY_EQUALS ] - input->keys[ KEY_MINUS ] ) * dt * 0.25f;
    141 	game->sun_angle += dsun;
    142 
    143 	if( input->keys[ KEY_N ] && input->key_edges[ KEY_N ] ) {
    144 		game->noclip = !game->noclip;
    145 		game->velocity = v3( 0 );
    146 	}
    147 
    148 	const v3 world_up = v3( 0, 0, 1 );
    149 	v3 forward = v3_forward( game->pitch, game->yaw );
    150 	v3 right = normalize( cross( forward, world_up ) );
    151 	v3 up = normalize( cross( right, forward ) );
    152 
    153 	if( game->noclip ) {
    154 		const float speed = 100.0f;
    155 		game->pos += forward * dt * fb * speed;
    156 		game->pos += right * dt * lr * speed;
    157 		game->pos.z += dt * dz * speed;
    158 	}
    159 	else {
    160 		if( game->on_ground ) {
    161 			if( input->keys[ KEY_SPACE ] ) {
    162 				game->velocity.z = 9.8;
    163 				game->on_ground = false;
    164 			}
    165 		}
    166 
    167 		const float acceleration = 12;
    168 		if( game->on_ground ) {
    169 			// apply friction
    170 			if( dot( game->velocity, game->velocity ) < 0.01 ) {
    171 				game->velocity.x = 0;
    172 				game->velocity.y = 0;
    173 			}
    174 			else {
    175 				float current_speed = length( game->velocity );
    176 				float drop = max( acceleration, current_speed ) * acceleration * dt / 2.0f;
    177 				float new_speed = max( 0.0f, current_speed - drop );
    178 				game->velocity = new_speed * ( game->velocity / current_speed );
    179 			}
    180 
    181 			if( fb != 0 || lr != 0 ) {
    182 				// apply acceleration
    183 				float max_speed = 10;
    184 
    185 				v3 forward_xy = v3_forward_xy( game->yaw );
    186 				v3 right_xy = normalize( cross( forward_xy, world_up ) );
    187 				v3 desired_direction = fb * forward_xy + lr * right_xy;
    188 
    189 				desired_direction = normalize( desired_direction );
    190 
    191 				float speed_in_desired_direction = dot( game->velocity, desired_direction );
    192 
    193 				if( speed_in_desired_direction < max_speed ) {
    194 					float accel_speed = min( acceleration * dt * max_speed, max_speed - speed_in_desired_direction );
    195 					game->velocity += accel_speed * desired_direction;
    196 				}
    197 			}
    198 		}
    199 
    200 		int spins = 0;
    201 		float remaining_t = dt;
    202 		while( remaining_t > 0 && spins < 5 ) {
    203 			spins++;
    204 			if( game->on_ground ) {
    205 				const float step_height = 0.1f;
    206 
    207 				v3 low_start = game->pos;
    208 				v3 low_end = low_start + game->velocity * remaining_t;
    209 
    210 				v3 high_start = game->pos + v3( 0, 0, step_height );
    211 				v3 high_end = high_start + game->velocity * remaining_t;
    212 
    213 				float low_t;
    214 				// initialise high_t to -1 so we never pick the high trace
    215 				// if we couldn't step up (fails the high_t > low_t test)
    216 				float high_t = -1;
    217 				v3 low_normal, high_normal;
    218 
    219 				bool low_hit = segment_vs_terrain( &game->tm, low_start, low_end, &low_t, &low_normal );
    220 				bool high_hit = false;
    221 
    222 				bool step_up_hit = segment_vs_terrain( &game->tm, low_start, high_start, NULL, NULL );
    223 				if( !step_up_hit ) {
    224 					high_hit = segment_vs_terrain( &game->tm, high_start, high_end, &high_t, &high_normal );
    225 				}
    226 
    227 				v3 step_down_start;
    228 				float step_down_distance;
    229 				bool should_step_down = false;
    230 
    231 				if( high_t > low_t ) {
    232 					if( high_hit ) {
    233 						game->pos = high_start + game->velocity * high_t * remaining_t;
    234 						remaining_t = ( 1.0f - high_t ) * remaining_t;
    235 					}
    236 					else {
    237 						step_down_start = high_end;
    238 						step_down_distance = step_height * 2;
    239 						should_step_down = true;
    240 						remaining_t = 0;
    241 					}
    242 				}
    243 				else {
    244 					if( low_hit ) {
    245 						game->pos = low_start + game->velocity * low_t * remaining_t;
    246 						remaining_t = ( 1.0f - low_t ) * remaining_t;
    247 					}
    248 					else {
    249 						step_down_start = low_end;
    250 						step_down_distance = step_height;
    251 						should_step_down = true;
    252 						remaining_t = 0;
    253 					}
    254 				}
    255 
    256 				if( should_step_down ) {
    257 					float step_down_t;
    258 					v3 step_down_end = step_down_start - v3( 0, 0, step_down_distance );
    259 
    260 					bool step_down_hit = segment_vs_terrain( &game->tm, step_down_start, step_down_end, &step_down_t, NULL );
    261 
    262 					game->pos = lerp( step_down_start, step_down_t, step_down_end );
    263 					game->on_ground = step_down_hit;
    264 				}
    265 			}
    266 
    267 			// TODO: this might be quite harmful
    268 			if( !game->on_ground ) {
    269 				// TODO: rk4 integration
    270 				const float gravity = -30.0f;
    271 				game->velocity += v3( 0, 0, gravity ) * remaining_t;
    272 
    273 				v3 end = game->pos + game->velocity * remaining_t;
    274 				float t;
    275 				v3 normal;
    276 				bool hit = segment_vs_terrain( &game->tm, game->pos, end, &t, &normal );
    277 				if( hit ) {
    278 					// TODO: check normal is flat enough, otherwise stay in the air
    279 					game->on_ground = true;
    280 					game->pos += game->velocity * t * remaining_t;
    281 					// TODO: truncate velocity
    282 					game->velocity.z = 0;
    283 
    284 					remaining_t = ( 1.0f - t ) * remaining_t;
    285 				}
    286 				else {
    287 					game->pos = end;
    288 					remaining_t = 0;
    289 				}
    290 			}
    291 
    292 			if( game->on_ground ) {
    293 				// move up 1mm so float imprecision doesn't make us fall through the world
    294 				game->pos.z += 0.001f;
    295 			}
    296 		}
    297 	}
    298 
    299 	terrain_update( &game->tm, game->pos );
    300 
    301 	v3 eye_pos = game->pos + v3( 0, 0, EYE_HEIGHT );
    302 	m4 P = m4_perspective( VERTICAL_FOV, get_aspect_ratio(), NEAR_PLANE_DEPTH, FAR_PLANE_DEPTH );
    303 	m4 V = m4_view( forward, right, up, eye_pos );
    304 	m4 Vsky = m4_view( forward, right, up, v3( 0 ) );
    305 	v3 sun_dir = v3( -cosf( game->sun_angle ), 0, sinf( game->sun_angle ) );
    306 
    307 	skybox_render( &game->skybox, Vsky, P, game->sun_angle, sun_dir );
    308 	terrain_render( &game->tm, V, P, game->sun_angle, sun_dir, current_time );
    309 
    310 	UniformBinding view_uniforms = renderer_uniforms( V, P );
    311 
    312 	{
    313 		RenderState render_state;
    314 		render_state.shader = get_shader( SHADER_TREE );
    315 		render_state.uniforms[ UNIFORMS_VIEW ] = view_uniforms;
    316 		render_state.uniforms[ UNIFORMS_SUN ] = renderer_uniforms( sun_dir );
    317 		render_state.textures[ 0 ] = renderer_blue_noise();
    318 
    319 		renderer_draw_instances( tree_mesh, render_state, num_trees, instance_data );
    320 	}
    321 
    322 	{
    323 		const float aspect = get_aspect_ratio();
    324 		const float crosshair_thickness = 0.0025f;
    325 		const float crosshair_length = 0.01f;
    326 
    327 		const v4 red( 1, 0, 0, 1 );
    328 		immediate_triangle(
    329 			v3( -crosshair_length,  crosshair_thickness, 0 ),
    330 			v3( -crosshair_length, -crosshair_thickness, 0 ),
    331 			v3(  crosshair_length,  crosshair_thickness, 0 ),
    332 			red
    333 		);
    334 		immediate_triangle(
    335 			v3(  crosshair_length, -crosshair_thickness, 0 ),
    336 			v3(  crosshair_length,  crosshair_thickness, 0 ),
    337 			v3( -crosshair_length, -crosshair_thickness, 0 ),
    338 			red
    339 		);
    340 		immediate_triangle(
    341 			v3(  crosshair_thickness / aspect,  crosshair_length * aspect, 0 ),
    342 			v3( -crosshair_thickness / aspect,  crosshair_length * aspect, 0 ),
    343 			v3(  crosshair_thickness / aspect, -crosshair_length * aspect, 0 ),
    344 			red
    345 		);
    346 		immediate_triangle(
    347 			v3( -crosshair_thickness / aspect, -crosshair_length * aspect, 0 ),
    348 			v3(  crosshair_thickness / aspect, -crosshair_length * aspect, 0 ),
    349 			v3( -crosshair_thickness / aspect,  crosshair_length * aspect, 0 ),
    350 			red
    351 		);
    352 		RenderState render_state;
    353 		render_state.shader = get_shader( SHADER_UI );
    354 		render_state.depth_func = DEPTHFUNC_DISABLED;
    355 		immediate_render( render_state );
    356 	}
    357 
    358 	if( input->keys[ KEY_F ] ) {
    359 		Fireball * fireball = fireballs.acquire();
    360 		fireball->pos = game->pos + v3( 0.0f, 0.0f, EYE_HEIGHT );
    361 		fireball->velocity = 128.0f * forward;
    362 	}
    363 
    364 	{
    365 		{
    366 			PROFILE_BLOCK( "Update fireballs" );
    367 			for( size_t i = 0; i < fireballs.elems.n; i++ ) {
    368 				Fireball * fireball = &fireballs.elems[ i ];
    369 				v3 new_pos = fireball->pos + fireball->velocity * dt;
    370 
    371 				float t;
    372 				if( segment_vs_terrain( &game->tm, fireball->pos, new_pos, &t, NULL ) ) {
    373 					Explosion * explosion = explosions.acquire();
    374 					explosion->pos = fireball->pos + t * ( new_pos - fireball->pos );
    375 					explosion->created_at = current_time;
    376 
    377 					fireballs.release( fireball );
    378 					i--;
    379 				}
    380 				else {
    381 					const float gravity = 9.81f;
    382 					fireball->pos = new_pos;
    383 					fireball->velocity.z -= dt * gravity;
    384 					immediate_sphere( fireball->pos, 4, v4( 1, 0, 0, 1 ), 4 );
    385 				}
    386 			}
    387 		}
    388 
    389 		RenderState fireball_render_state;
    390 		fireball_render_state.shader = get_shader( SHADER_FLAT_VERTEX_COLOURS );
    391 		fireball_render_state.uniforms[ UNIFORMS_VIEW ] = view_uniforms;
    392 		immediate_render( fireball_render_state );
    393 
    394 		for( size_t i = 0; i < explosions.elems.n; i++ ) {
    395 			Explosion * explosion = &explosions.elems[ i ];
    396 			float t = float( current_time - explosion->created_at );
    397 			if( t < 0.5f ) {
    398 				immediate_sphere( explosion->pos, t * 32.0f, v4( 1, 0.5, 0, 1 ), 4 );
    399 			}
    400 			else {
    401 				explosions.release( explosion );
    402 				i--;
    403 			}
    404 		}
    405 
    406 		RenderState explosion_render_state;
    407 		explosion_render_state.shader = get_shader( SHADER_FLAT_VERTEX_COLOURS );
    408 		explosion_render_state.uniforms[ UNIFORMS_VIEW ] = view_uniforms;
    409 		immediate_render( explosion_render_state );
    410 	}
    411 
    412 	{
    413 		char buf[ 1400 ];
    414 		NetAddress recv_addr;
    415 		size_t len;
    416 		if( !net_tryrecv( sock, buf, sizeof( buf ), &recv_addr, &len ) ) {
    417 			len = 0;
    418 		}
    419 		if( recv_addr != server_addr ) {
    420 			len = 0;
    421 		}
    422 
    423 		ReadStream rs( buf, len );
    424 
    425 		if( connected && len > 0 ) {
    426 			char write_buf[ 1400 ];
    427 			WriteStream ws( write_buf );
    428 			write( &ws, sid );
    429 			write( &ws, game->pos );
    430 
    431 			net_send( sock, ws.start, ws.len(), server_addr );
    432 
    433 			u16 player_count = read_u16( &rs );
    434 			ReadStreamCheckpoint rsc = rs.checkpoint();
    435 
    436 			for( u16 i = 0; i < player_count; i++ ) {
    437 				read_u64( &rs );
    438 				u8 player_state = read_u8( &rs );
    439 				if( player_state == 0 ) {
    440 					v3 pos;
    441 					read( &rs, &pos );
    442 				}
    443 			}
    444 
    445 			if( rs.done() ) {
    446 				rs.reset( &rsc );
    447 				for( u16 i = 0; i < player_count; i++ ) {
    448 					u64 player_sid = read_u64( &rs );
    449 					u8 player_state = read_u8( &rs );
    450 					v3 pos;
    451 					if( player_state == 0 ) {
    452 						read( &rs, &pos );
    453 					}
    454 
    455 					Player * player = NULL;
    456 					if( !sid_to_player.get( player_sid, &player ) && player_state == 0 ) {
    457 						player = players.acquire();
    458 						ASSERT( player != NULL ); // TODO
    459 						sid_to_player.add( player_sid, player );
    460 						player->sid = player_sid;
    461 					}
    462 
    463 					if( player_state == 0 ) {
    464 						player->pos = pos;
    465 					}
    466 					else if( player_state == 1 ) {
    467 						ggprint( "{08x} disconnected\n", player->sid );
    468 						players.release( player );
    469 					}
    470 				}
    471 
    472 				ASSERT( rs.done() );
    473 			}
    474 			else {
    475 				if( len > 0 ) {
    476 					FATAL( "bad message from the server" );
    477 				}
    478 			}
    479 		}
    480 		else if( len > 0 ) {
    481 			sid = read_u64( &rs );
    482 			if( rs.ok ) {
    483 				connected = true;
    484 			}
    485 		}
    486 
    487 		if( !connected && current_time - last_connection_attempt > 1.0 ) {
    488 			char write_buf[ 1400 ];
    489 			WriteStream ws( write_buf );
    490 
    491 			write_u64( &ws, ~u64( 0 ) );
    492 			write( &ws, game->pos );
    493 
    494 			net_send( sock, ws.start, ws.len(), server_addr );
    495 
    496 			last_connection_attempt = current_time;
    497 		}
    498 
    499 		for( const Player & player : players.elems ) {
    500 			if( player.sid != sid ) {
    501 				v3 mins = player.pos - v3( 0.4f, 0.4f, 0.0f );
    502 				v3 maxs = player.pos + v3( 0.4f, 0.4f, EYE_HEIGHT + 0.1f );
    503 				immediate_aabb( mins, maxs, v4( 1, 1, 0, 1 ) );
    504 			}
    505 		}
    506 
    507 		RenderState impact_render_state;
    508 		impact_render_state.shader = get_shader( SHADER_FLAT_VERTEX_COLOURS );
    509 		impact_render_state.uniforms[ UNIFORMS_VIEW ] = view_uniforms;
    510 		immediate_render( impact_render_state );
    511 	}
    512 
    513 	{
    514 		const str< 128 > status( "Frame time: {.1}ms FPS: {.1} {} {.1} noclip: {}",
    515 			dt * 1000.0f, 1.0f / dt,
    516 			connected ? "connected!" : "connecting", game->pos,
    517 			game->noclip );
    518 		draw_text( status.c_str(), 2, 2, 16.0f );
    519 
    520 		draw_text( str< 128 >( "velocity = {.1}, speed = {.2}, sun_angle = {.2}", game->velocity, length( game->velocity.xy() ), game->sun_angle ).c_str(), 2, 20, 16 );
    521 		draw_text( str< 128 >( "drawcalls = {}, verts = {}", renderer_num_draw_calls(), renderer_num_vertices() ).c_str(), 2, 38, 16 );
    522 	}
    523 
    524 	renderer_end_pass();
    525 	renderer_end_frame();
    526 }