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 }