medfall

A super great game engine
Log | Files | Refs

clipmap.cc (35529B)


      1 #include "intrinsics.h"
      2 #include "int_conversions.h"
      3 #include "linear_algebra.h"
      4 #include "aabb.h"
      5 #include "game.h"
      6 #include "renderer.h"
      7 #include "shaders.h"
      8 #include "immediate.h"
      9 #include "text_renderer.h"
     10 #include "decompress_bc.h"
     11 #include "gl.h"
     12 #include "obj.h"
     13 #include "skybox.h"
     14 #include "stream.h"
     15 #include "hashtable.h"
     16 #include "pool.h"
     17 #include "profiler.h"
     18 #include "platform_network.h"
     19 
     20 #include "libs/lz4/lz4.h"
     21 
     22 struct ClipmapTerrain {
     23 	struct {
     24 		Mesh tile;
     25 		Mesh empty_tile;
     26 		Mesh filler;
     27 		Mesh trim;
     28 		Mesh cross;
     29 		Mesh seam;
     30 		Mesh skirt;
     31 		Texture heightmap;
     32 		Texture normalmap;
     33 		Texture horizonmap;
     34 	} gpu;
     35 
     36 	array2d< u16 > heightmap;
     37 	QuadTree quadtree;
     38 };
     39 
     40 static const float EYE_HEIGHT = 1.8f;
     41 
     42 static ClipmapTerrain clipmap;
     43 
     44 struct Fireball {
     45 	v3 pos;
     46 	v3 velocity;
     47 };
     48 
     49 struct Explosion {
     50 	v3 pos;
     51 	double created_at;
     52 };
     53 
     54 struct Player {
     55 	u64 sid;
     56 	v3 pos;
     57 };
     58 
     59 static FB msaa_fb;
     60 
     61 static UDPSocket sock;
     62 static NetAddress server_addr;
     63 static u64 sid;
     64 static double last_connection_attempt = -5.0; // TODO
     65 static bool connected = false;
     66 
     67 static Pool< Fireball, 1024 > fireballs;
     68 static Pool< Explosion, 1024 > explosions;
     69 
     70 static Pool< Player, 1024 > players;
     71 static HashTable< Player *, 1024 * 2 > sid_to_player;
     72 
     73 static VB trees_instance_data;
     74 static u32 num_trees;
     75 static Mesh tree_mesh;
     76 
     77 static constexpr u32 TILE_RESOLUTION = 48;
     78 static constexpr u32 PATCH_VERT_RESOLUTION = TILE_RESOLUTION + 1;
     79 static constexpr u32 CLIPMAP_RESOLUTION = TILE_RESOLUTION * 4 + 1;
     80 static constexpr u32 CLIPMAP_VERT_RESOLUTION = CLIPMAP_RESOLUTION + 1;
     81 static constexpr u32 NUM_CLIPMAP_LEVELS = 7;
     82 
     83 static void load_trees( MemoryArena * arena ) {
     84 	MEMARENA_SCOPED_CHECKPOINT( arena );
     85 
     86 	size_t compressed_len;
     87 	u8 * compressed_trees = file_get_contents( "terrains/gta16.png.parts/trees.lz4", &compressed_len );
     88 
     89 	// TODO: don't hardcode this!
     90 	array< v3 > trees = memarena_push_array( arena, v3, 300000 );
     91 	int decompressed_len = LZ4_decompress_safe( ( char * ) compressed_trees, ( char * ) trees.ptr(), compressed_len, trees.num_bytes() );
     92 	ASSERT( decompressed_len > 0 );
     93 
     94 	free( compressed_trees );
     95 
     96 	num_trees = decompressed_len / sizeof( v3 );
     97 	array< m4 > tree_matrices = memarena_push_array( arena, m4, num_trees );
     98 
     99 	m4 rot = m4_rotx( deg_to_rad( 90 ) );
    100 	for( size_t i = 0; i < num_trees; i++ ) {
    101 		tree_matrices[ i ] = m4_translation( trees[ i ] ) * rot;
    102 	}
    103 
    104 	trees_instance_data = renderer_new_vb( tree_matrices );
    105 
    106 	tree_mesh = load_obj( "models/trees/PineTree.obj", arena );
    107 }
    108 
    109 static size_t patch2d( size_t x, size_t y ) {
    110 	return y * PATCH_VERT_RESOLUTION + x;
    111 }
    112 
    113 static void file_get_contents_and_decompress( const char * path, u8 * decompressed, size_t decompressed_size ) {
    114 	size_t compressed_size;
    115 	u8 * compressed = file_get_contents( path, &compressed_size );
    116 	int ok = LZ4_decompress_safe( ( const char * ) compressed, ( char * ) decompressed, compressed_size, decompressed_size );
    117 	ASSERT( ok > 0 && size_t( ok ) == decompressed_size );
    118 	free( compressed );
    119 }
    120 
    121 struct RGBA {
    122 	u8 r, g, b, a;
    123 };
    124 
    125 GAME_INIT( game_init ) {
    126 	net_init();
    127 
    128 	msaa_fb = { };
    129 
    130 	// load quadtree
    131 	{
    132 		clipmap.heightmap = alloc_array2d< u16 >( &mem->persistent_arena, 4096, 4096 );
    133 
    134 		array< QuadTreeNode > nodes = alloc_array< QuadTreeNode >( &mem->persistent_arena, 4096 * 4096 / 3 );
    135 		file_get_contents_and_decompress( "terrains/gta16.png.parts/quadtree.lz4", ( u8 * ) nodes.ptr(), nodes.num_bytes() );
    136 
    137 		clipmap.quadtree.nodes = nodes;
    138 		clipmap.quadtree.heightmap = clipmap.heightmap;
    139 	}
    140 
    141 	// load heightmap
    142 	{
    143 		MEMARENA_SCOPED_CHECKPOINT( &mem->persistent_arena );
    144 
    145 		u8 * heightmap_data = memarena_push_size( &mem->persistent_arena, 4096 * 4096 );
    146 		file_get_contents_and_decompress( "terrains/gta16.png.parts/heightmap.bc5.lz4", heightmap_data, 4096 * 4096 );
    147 
    148 		TextureConfig texture_config;
    149 		texture_config.width = 4096;
    150 		texture_config.height = 4096;
    151 		texture_config.data = heightmap_data;
    152 		texture_config.format = TEXFMT_BC5;
    153 		texture_config.wrap = TEXWRAP_BORDER;
    154 		texture_config.border_colour = v4( 0 );
    155 		clipmap.gpu.heightmap = renderer_new_texture( texture_config );
    156 
    157 		bc5_to_heightmap( &mem->persistent_arena, clipmap.heightmap, heightmap_data );
    158 	}
    159 
    160 	// load normalmap
    161 	{
    162 		MEMARENA_SCOPED_CHECKPOINT( &mem->persistent_arena );
    163 
    164 		u8 * normalmap_data = memarena_push_size( &mem->persistent_arena, 4096 * 4096 );
    165 		file_get_contents_and_decompress( "terrains/gta16.png.parts/normalmap.bc5.lz4", normalmap_data, 4096 * 4096 );
    166 
    167 		TextureConfig texture_config;
    168 		texture_config.width = 4096;
    169 		texture_config.height = 4096;
    170 		texture_config.data = normalmap_data;
    171 		texture_config.format = TEXFMT_BC5;
    172 		texture_config.wrap = TEXWRAP_BORDER;
    173 		texture_config.border_colour = v4( 0.5f );
    174 		clipmap.gpu.normalmap = renderer_new_texture( texture_config );
    175 	}
    176 
    177 	// load horizonmap
    178 	{
    179 		MEMARENA_SCOPED_CHECKPOINT( &mem->persistent_arena );
    180 
    181 		u8 * horizonmap_data = memarena_push_size( &mem->persistent_arena, 4096 * 4096 / 2 );
    182 		file_get_contents_and_decompress( "terrains/gta16.png.parts/horizonmap.bc4.lz4", horizonmap_data, 4096 * 4096 / 2 );
    183 
    184 		TextureConfig texture_config;
    185 		texture_config.width = 4096;
    186 		texture_config.height = 4096;
    187 		texture_config.data = horizonmap_data;
    188 		texture_config.format = TEXFMT_BC4;
    189 		texture_config.wrap = TEXWRAP_BORDER;
    190 		texture_config.border_colour = v4( 0 );
    191 		clipmap.gpu.horizonmap = renderer_new_texture( texture_config );
    192 	}
    193 
    194 	// generate tile mesh
    195 	{
    196 		MEMARENA_SCOPED_CHECKPOINT( &mem->persistent_arena );
    197 
    198 		array< v3 > vertices = alloc_array< v3 >( &mem->persistent_arena, PATCH_VERT_RESOLUTION * PATCH_VERT_RESOLUTION );
    199 		size_t n = 0;
    200 
    201 		for( u32 y = 0; y < PATCH_VERT_RESOLUTION; y++ ) {
    202 			for( u32 x = 0; x < PATCH_VERT_RESOLUTION; x++ ) {
    203 				vertices[ n++ ] = v3( x, y, 0 );
    204 			}
    205 		}
    206 
    207 		ASSERT( n == vertices.n );
    208 
    209 		array< u32 > indices = alloc_array< u32 >( &mem->persistent_arena, TILE_RESOLUTION * TILE_RESOLUTION * 6 );
    210 		n = 0;
    211 
    212 		for( u32 y = 0; y < TILE_RESOLUTION; y++ ) {
    213 			for( u32 x = 0; x < TILE_RESOLUTION; x++ ) {
    214 				indices[ n++ ] = patch2d( x, y );
    215 				indices[ n++ ] = patch2d( x + 1, y + 1 );
    216 				indices[ n++ ] = patch2d( x, y + 1 );
    217 
    218 				indices[ n++ ] = patch2d( x, y );
    219 				indices[ n++ ] = patch2d( x + 1, y );
    220 				indices[ n++ ] = patch2d( x + 1, y + 1 );
    221 			}
    222 		}
    223 
    224 		ASSERT( n == indices.n );
    225 
    226 		MeshConfig mesh_config;
    227 		mesh_config.positions = renderer_new_vb( vertices );
    228 		mesh_config.indices = renderer_new_ib( indices );
    229 		mesh_config.num_vertices = indices.n;
    230 		clipmap.gpu.tile = renderer_new_mesh( mesh_config );
    231 	}
    232 
    233 	// generate empty tile mesh
    234 	{
    235 		MEMARENA_SCOPED_CHECKPOINT( &mem->persistent_arena );
    236 
    237 		array< v3 > vertices = alloc_array< v3 >( &mem->persistent_arena, TILE_RESOLUTION * 4 + 1 );
    238 
    239 		// vertices[ 0 ] is the centre of the fan
    240 		vertices[ 0 ] = v3( v2( TILE_RESOLUTION / 2.0f ), 0 );
    241 
    242 		for( u32 i = 0; i < TILE_RESOLUTION; i++ ) {
    243 			vertices[ i + 1 + 0 * TILE_RESOLUTION ] = v3( i, 0, 0 );
    244 			vertices[ i + 1 + 1 * TILE_RESOLUTION ] = v3( TILE_RESOLUTION, i, 0 );
    245 			vertices[ i + 1 + 2 * TILE_RESOLUTION ] = v3( TILE_RESOLUTION - i, TILE_RESOLUTION, 0 );
    246 			vertices[ i + 1 + 3 * TILE_RESOLUTION ] = v3( 0, TILE_RESOLUTION - i, 0 );
    247 		}
    248 
    249 		array< u32 > indices = alloc_array< u32 >( &mem->persistent_arena, TILE_RESOLUTION * 12 );
    250 
    251 		for( u32 i = 0; i < TILE_RESOLUTION * 4; i++ ) {
    252 			indices[ i * 3 + 0 ] = 0;
    253 			indices[ i * 3 + 1 ] = i + 1;
    254 			indices[ i * 3 + 2 ] = i + 2;
    255 		}
    256 
    257 		// make the last triangle wrap around
    258 		indices[ indices.n - 1 ] = 1;
    259 
    260 		MeshConfig mesh_config;
    261 		mesh_config.positions = renderer_new_vb( vertices );
    262 		mesh_config.indices = renderer_new_ib( indices );
    263 		mesh_config.num_vertices = indices.n;
    264 		clipmap.gpu.empty_tile = renderer_new_mesh( mesh_config );
    265 	}
    266 
    267 	// generate filler mesh
    268 	{
    269 		MEMARENA_SCOPED_CHECKPOINT( &mem->persistent_arena );
    270 
    271 		array< v3 > vertices = alloc_array< v3 >( &mem->persistent_arena, PATCH_VERT_RESOLUTION * 8 );
    272 		size_t n = 0;
    273 		u32 offset = TILE_RESOLUTION;
    274 
    275 		for( u32 i = 0; i < PATCH_VERT_RESOLUTION; i++ ) {
    276 			vertices[ n++ ] = v3( offset + i + 1, 0, 0 );
    277 			vertices[ n++ ] = v3( offset + i + 1, 1, 0 );
    278 		}
    279 
    280 		for( u32 i = 0; i < PATCH_VERT_RESOLUTION; i++ ) {
    281 			vertices[ n++ ] = v3( 1, offset + i + 1, 0 );
    282 			vertices[ n++ ] = v3( 0, offset + i + 1, 0 );
    283 		}
    284 
    285 		for( u32 i = 0; i < PATCH_VERT_RESOLUTION; i++ ) {
    286 			vertices[ n++ ] = v3( -float( offset + i ), 1, 0 );
    287 			vertices[ n++ ] = v3( -float( offset + i ), 0, 0 );
    288 		}
    289 
    290 		for( u32 i = 0; i < PATCH_VERT_RESOLUTION; i++ ) {
    291 			vertices[ n++ ] = v3( 0, -float( offset + i ), 0 );
    292 			vertices[ n++ ] = v3( 1, -float( offset + i ), 0 );
    293 		}
    294 
    295 		ASSERT( n == vertices.n );
    296 
    297 		array< u32 > indices = alloc_array< u32 >( &mem->persistent_arena, TILE_RESOLUTION * 24 );
    298 		n = 0;
    299 
    300 		for( u32 i = 0; i < TILE_RESOLUTION * 4; i++ ) {
    301 			// the arms shouldn't be connected to each other
    302 			u32 arm = i / TILE_RESOLUTION;
    303 
    304 			u32 bl = ( arm + i ) * 2 + 0;
    305 			u32 br = ( arm + i ) * 2 + 1;
    306 			u32 tl = ( arm + i ) * 2 + 2;
    307 			u32 tr = ( arm + i ) * 2 + 3;
    308 
    309 			if( arm % 2 == 0 ) {
    310 				indices[ n++ ] = br;
    311 				indices[ n++ ] = bl;
    312 				indices[ n++ ] = tr;
    313 				indices[ n++ ] = bl;
    314 				indices[ n++ ] = tl;
    315 				indices[ n++ ] = tr;
    316 			}
    317 			else {
    318 				indices[ n++ ] = br;
    319 				indices[ n++ ] = bl;
    320 				indices[ n++ ] = tl;
    321 				indices[ n++ ] = br;
    322 				indices[ n++ ] = tl;
    323 				indices[ n++ ] = tr;
    324 			}
    325 		}
    326 
    327 		ASSERT( n == indices.n );
    328 
    329 		MeshConfig mesh_config;
    330 		mesh_config.positions = renderer_new_vb( vertices );
    331 		mesh_config.indices = renderer_new_ib( indices );
    332 		mesh_config.num_vertices = indices.n;
    333 		clipmap.gpu.filler = renderer_new_mesh( mesh_config );
    334 	}
    335 
    336 	// generate trim mesh
    337 	{
    338 		MEMARENA_SCOPED_CHECKPOINT( &mem->persistent_arena );
    339 
    340 		array< v3 > vertices = alloc_array< v3 >( &mem->persistent_arena, ( CLIPMAP_VERT_RESOLUTION * 2 + 1 ) * 2 );
    341 		size_t n = 0;
    342 
    343 		// vertical part of L
    344 		for( u32 i = 0; i < CLIPMAP_VERT_RESOLUTION + 1; i++ ) {
    345 			vertices[ n++ ] = v3( 0, CLIPMAP_VERT_RESOLUTION - i, 0 );
    346 			vertices[ n++ ] = v3( 1, CLIPMAP_VERT_RESOLUTION - i, 0 );
    347 		}
    348 
    349 		size_t start_of_horizontal = n;
    350 
    351 		// horizontal part of L
    352 		for( u32 i = 0; i < CLIPMAP_VERT_RESOLUTION; i++ ) {
    353 			vertices[ n++ ] = v3( i + 1, 0, 0 );
    354 			vertices[ n++ ] = v3( i + 1, 1, 0 );
    355 		}
    356 
    357 		ASSERT( n == vertices.n );
    358 
    359 		for( v3 & v : vertices ) {
    360 			v -= v3( 0.5f * v2( CLIPMAP_VERT_RESOLUTION + 1 ), 0 );
    361 		}
    362 
    363 		array< u32 > indices = alloc_array< u32 >( &mem->persistent_arena, ( CLIPMAP_VERT_RESOLUTION * 2 - 1 ) * 6 );
    364 		n = 0;
    365 
    366 		for( u32 i = 0; i < CLIPMAP_VERT_RESOLUTION; i++ ) {
    367 			indices[ n++ ] = ( i + 0 ) * 2 + 1;
    368 			indices[ n++ ] = ( i + 0 ) * 2 + 0;
    369 			indices[ n++ ] = ( i + 1 ) * 2 + 0;
    370 
    371 			indices[ n++ ] = ( i + 1 ) * 2 + 1;
    372 			indices[ n++ ] = ( i + 0 ) * 2 + 1;
    373 			indices[ n++ ] = ( i + 1 ) * 2 + 0;
    374 		}
    375 
    376 		for( u32 i = 0; i < CLIPMAP_VERT_RESOLUTION - 1; i++ ) {
    377 			indices[ n++ ] = start_of_horizontal + ( i + 0 ) * 2 + 1;
    378 			indices[ n++ ] = start_of_horizontal + ( i + 0 ) * 2 + 0;
    379 			indices[ n++ ] = start_of_horizontal + ( i + 1 ) * 2 + 0;
    380 
    381 			indices[ n++ ] = start_of_horizontal + ( i + 1 ) * 2 + 1;
    382 			indices[ n++ ] = start_of_horizontal + ( i + 0 ) * 2 + 1;
    383 			indices[ n++ ] = start_of_horizontal + ( i + 1 ) * 2 + 0;
    384 		}
    385 
    386 		ASSERT( n == indices.n );
    387 
    388 		MeshConfig mesh_config;
    389 		mesh_config.positions = renderer_new_vb( vertices );
    390 		mesh_config.indices = renderer_new_ib( indices );
    391 		mesh_config.num_vertices = indices.n;
    392 		clipmap.gpu.trim = renderer_new_mesh( mesh_config );
    393 	}
    394 
    395 	// generate cross mesh
    396 	{
    397 		MEMARENA_SCOPED_CHECKPOINT( &mem->persistent_arena );
    398 
    399 		array< v3 > vertices = alloc_array< v3 >( &mem->persistent_arena, PATCH_VERT_RESOLUTION * 8 );
    400 		size_t n = 0;
    401 
    402 		// horizontal vertices
    403 		for( u32 i = 0; i < PATCH_VERT_RESOLUTION * 2; i++ ) {
    404 			vertices[ n++ ] = v3( i - float( TILE_RESOLUTION ), 0, 0 );
    405 			vertices[ n++ ] = v3( i - float( TILE_RESOLUTION ), 1, 0 );
    406 		}
    407 
    408 		size_t start_of_vertical = n;
    409 
    410 		// vertical vertices
    411 		for( u32 i = 0; i < PATCH_VERT_RESOLUTION * 2; i++ ) {
    412 			vertices[ n++ ] = v3( 0, i - float( TILE_RESOLUTION ), 0 );
    413 			vertices[ n++ ] = v3( 1, i - float( TILE_RESOLUTION ), 0 );
    414 		}
    415 
    416 		ASSERT( n == vertices.n );
    417 
    418 		array< u32 > indices = alloc_array< u32 >( &mem->persistent_arena, TILE_RESOLUTION * 24 + 6 );
    419 		n = 0;
    420 
    421 		// horizontal indices
    422 		for( u32 i = 0; i < TILE_RESOLUTION * 2 + 1; i++ ) {
    423 			u32 bl = i * 2 + 0;
    424 			u32 br = i * 2 + 1;
    425 			u32 tl = i * 2 + 2;
    426 			u32 tr = i * 2 + 3;
    427 
    428 			indices[ n++ ] = br;
    429 			indices[ n++ ] = bl;
    430 			indices[ n++ ] = tr;
    431 			indices[ n++ ] = bl;
    432 			indices[ n++ ] = tl;
    433 			indices[ n++ ] = tr;
    434 		}
    435 
    436 		// vertical indices
    437 		for( u32 i = 0; i < TILE_RESOLUTION * 2 + 1; i++ ) {
    438 			if( i == TILE_RESOLUTION )
    439 				continue;
    440 
    441 			u32 bl = i * 2 + 0;
    442 			u32 br = i * 2 + 1;
    443 			u32 tl = i * 2 + 2;
    444 			u32 tr = i * 2 + 3;
    445 
    446 			indices[ n++ ] = start_of_vertical + br;
    447 			indices[ n++ ] = start_of_vertical + tr;
    448 			indices[ n++ ] = start_of_vertical + bl;
    449 			indices[ n++ ] = start_of_vertical + bl;
    450 			indices[ n++ ] = start_of_vertical + tr;
    451 			indices[ n++ ] = start_of_vertical + tl;
    452 		}
    453 
    454 		ASSERT( n == indices.n );
    455 
    456 		MeshConfig mesh_config;
    457 		mesh_config.positions = renderer_new_vb( vertices );
    458 		mesh_config.indices = renderer_new_ib( indices );
    459 		mesh_config.num_vertices = indices.n;
    460 		clipmap.gpu.cross = renderer_new_mesh( mesh_config );
    461 	}
    462 
    463 	// generate seam mesh
    464 	{
    465 		MEMARENA_SCOPED_CHECKPOINT( &mem->persistent_arena );
    466 
    467 		array< v3 > vertices = alloc_array< v3 >( &mem->persistent_arena, CLIPMAP_VERT_RESOLUTION * 4 );
    468 
    469 		for( u32 i = 0; i < CLIPMAP_VERT_RESOLUTION; i++ ) {
    470 			vertices[ CLIPMAP_VERT_RESOLUTION * 0 + i ] = v3( i, 0, 0 );
    471 			vertices[ CLIPMAP_VERT_RESOLUTION * 1 + i ] = v3( CLIPMAP_VERT_RESOLUTION, i, 0 );
    472 			vertices[ CLIPMAP_VERT_RESOLUTION * 2 + i ] = v3( CLIPMAP_VERT_RESOLUTION - i, CLIPMAP_VERT_RESOLUTION, 0 );
    473 			vertices[ CLIPMAP_VERT_RESOLUTION * 3 + i ] = v3( 0, CLIPMAP_VERT_RESOLUTION - i, 0 );
    474 		}
    475 
    476 		array< u32 > indices = alloc_array< u32 >( &mem->persistent_arena, CLIPMAP_VERT_RESOLUTION * 6 );
    477 		size_t n = 0;
    478 
    479 		for( u32 i = 0; i < CLIPMAP_VERT_RESOLUTION * 4; i += 2 ) {
    480 			indices[ n++ ] = i + 1;
    481 			indices[ n++ ] = i;
    482 			indices[ n++ ] = i + 2;
    483 		}
    484 
    485 		// make the last triangle wrap around
    486 		indices[ indices.n - 1 ] = 0;
    487 
    488 		ASSERT( n == indices.n );
    489 
    490 		MeshConfig mesh_config;
    491 		mesh_config.positions = renderer_new_vb( vertices );
    492 		mesh_config.indices = renderer_new_ib( indices );
    493 		mesh_config.num_vertices = indices.n;
    494 		clipmap.gpu.seam = renderer_new_mesh( mesh_config );
    495 	}
    496 
    497 	// generate skirt mesh
    498 	{
    499 		float scale = checked_cast< float >( u32( 1 ) << ( NUM_CLIPMAP_LEVELS - 1 ) );
    500 		v2 base = -v2( checked_cast< float >( TILE_RESOLUTION << NUM_CLIPMAP_LEVELS ) );
    501 
    502 		v2 clipmap_tl = base;
    503 		v2 clipmap_br = clipmap_tl + v2( CLIPMAP_RESOLUTION * scale );
    504 
    505 		StaticArray< v3, 8 > vertices;
    506 		float big = 10000000.0;
    507 		vertices[ 0 ] = v3( -1, -1, 0 ) * big;
    508 		vertices[ 1 ] = v3( +1, -1, 0 ) * big;
    509 		vertices[ 2 ] = v3( -1, +1, 0 ) * big;
    510 		vertices[ 3 ] = v3( +1, +1, 0 ) * big;
    511 		vertices[ 4 ] = v3( clipmap_tl.x, clipmap_tl.y, 0 );
    512 		vertices[ 5 ] = v3( clipmap_br.x, clipmap_tl.y, 0 );
    513 		vertices[ 6 ] = v3( clipmap_tl.x, clipmap_br.y, 0 );
    514 		vertices[ 7 ] = v3( clipmap_br.x, clipmap_br.y, 0 );
    515 
    516 		u32 indices[] = {
    517 			0, 1, 4, 4, 1, 5,
    518 			1, 3, 5, 5, 3, 7,
    519 			3, 2, 7, 7, 2, 6,
    520 			4, 6, 0, 0, 6, 2,
    521 		};
    522 
    523 		MeshConfig mesh_config;
    524 		mesh_config.positions = renderer_new_vb( vertices.ptr(), vertices.num_bytes() );
    525 		mesh_config.indices = renderer_new_ib( indices, sizeof( indices ) );
    526 		mesh_config.num_vertices = ARRAY_COUNT( indices );
    527 		clipmap.gpu.skirt = renderer_new_mesh( mesh_config );
    528 	}
    529 
    530 	game->pos = v3( 2056, 2056, 70 );
    531 	game->pitch = 0;
    532 	game->yaw = 250;
    533 	game->sun_angle = 0.3f;
    534 
    535 	skybox_init( &game->skybox );
    536 
    537 	load_trees( &mem->persistent_arena );
    538 
    539 	sock = net_new_udp( NET_NONBLOCKING );
    540 
    541 	if( !dns_first( "localhost", &server_addr ) ) {
    542 		FATAL( "dns_first" );
    543 	}
    544 	server_addr.port = 13337;
    545 }
    546 
    547 static void draw_qt( MinMaxu32 aabb, const array< QuadTreeNode > nodes, size_t node_idx ) {
    548 	if( aabb.maxs.x - aabb.mins.x < 64 ) {
    549 		return;
    550 	}
    551 
    552 	v3 mins = v3( aabb.mins.x, aabb.mins.y, aabb.mins.z / 256.0f );
    553 	v3 maxs = v3( aabb.maxs.x, aabb.maxs.y, aabb.maxs.z / 256.0f );
    554 	immediate_aabb( mins, maxs, v4( 0, 0, 1, 1 ) );
    555 
    556 	for( size_t i = 0; i < 4; i++ ) {
    557 		MinMaxu32 child_aabb = aabb.quadrant( i ).clamp_z( nodes[ node_idx * 4 + i + 1 ].min_z, nodes[ node_idx * 4 + i + 1 ].max_z );
    558 		draw_qt( child_aabb, nodes, node_idx * 4 + i + 1 );
    559 	}
    560 }
    561 
    562 static bool intervals_overlap( float a0, float a1, float b0, float b1 ) {
    563 	ASSERT( a0 <= a1 );
    564 	ASSERT( b0 <= b1 );
    565 	return a0 <= b1 && b0 <= a1;
    566 }
    567 
    568 static void recreate_framebuffers() {
    569 	renderer_delete_fb( msaa_fb );
    570 
    571 	v2u32 window_size = get_window_size();
    572 
    573 	TextureConfig texture_config;
    574 	texture_config.width = window_size.x;
    575 	texture_config.height = window_size.y;
    576 	texture_config.wrap = TEXWRAP_CLAMP;
    577 
    578 	FramebufferConfig fb_config;
    579 
    580 	texture_config.format = TEXFMT_RGB_U8_SRGB;
    581 	fb_config.textures[ OUTPUT_ALBEDO ].config = texture_config;
    582 	fb_config.textures[ OUTPUT_ALBEDO ].attachment = FB_COLOUR;
    583 
    584 	texture_config.format = TEXFMT_DEPTH;
    585 	fb_config.textures[ OUTPUT_DEPTH ].config = texture_config;
    586 	fb_config.textures[ OUTPUT_DEPTH ].attachment = FB_DEPTH;
    587 
    588 	fb_config.msaa_samples = 0;
    589 
    590 	msaa_fb = renderer_new_fb( fb_config );
    591 }
    592 
    593 GAME_FRAME( game_frame ) {
    594 	if( input->resized ) {
    595 		recreate_framebuffers();
    596 	}
    597 
    598 	float fb = float( input->keys[ KEY_W ] - input->keys[ KEY_S ] );
    599 	float lr = float( input->keys[ KEY_D ] - input->keys[ KEY_A ] );
    600 	float dz = float( input->keys[ KEY_SPACE ] - input->keys[ KEY_LEFTSHIFT ] );
    601 
    602 	float dpitch = input->keys[ KEY_DOWNARROW ] - input->keys[ KEY_UPARROW ];
    603 	float dyaw = input->keys[ KEY_LEFTARROW ] - input->keys[ KEY_RIGHTARROW ];
    604 
    605 	dpitch = input->keys[ KEY_K ] - input->keys[ KEY_I ];
    606 	dyaw += input->keys[ KEY_J ] - input->keys[ KEY_L ];
    607 
    608 	if( input->keys[ KEY_M ] && input->key_edges[ KEY_M ] )
    609 		game->draw_wireframe = !game->draw_wireframe;
    610 	if( input->keys[ KEY_T ] && input->key_edges[ KEY_T ] )
    611 		game->draw_quadtree = !game->draw_quadtree;
    612 	if( input->keys[ KEY_N ] && input->key_edges[ KEY_N ] ) {
    613 		game->noclip = !game->noclip;
    614 		game->velocity = v3( 0 );
    615 		game->on_ground = false;
    616 	}
    617 	if( input->keys[ KEY_C ] && input->key_edges[ KEY_C ] ) {
    618 		game->frozen = !game->frozen;
    619 		game->frozen_pos = game->pos;
    620 	}
    621 
    622 	dpitch -= float( input->mouse_dy * 0.25 );
    623 	dyaw -= float( input->mouse_dx * 0.25 );
    624 
    625 	game->pitch = clamp( game->pitch + dpitch * dt * 100, -89.9f, 89.9f );
    626 	game->yaw += dyaw * dt * 100;
    627 
    628 	const float dsun = ( input->keys[ KEY_EQUALS ] - input->keys[ KEY_MINUS ] ) * dt * 0.25f;
    629 	game->sun_angle += dsun;
    630 
    631 	const v3 world_up = v3( 0, 0, 1 );
    632 	v3 forward = v3_forward( game->pitch, game->yaw );
    633 	v3 right = normalize( cross( forward, world_up ) );
    634 	v3 up = normalize( cross( right, forward ) );
    635 
    636 	// player movement
    637 	if( game->noclip ) {
    638 		const float speed = 100.0f;
    639 		game->pos += forward * dt * fb * speed;
    640 		game->pos += right * dt * lr * speed;
    641 		game->pos.z += dt * dz * speed;
    642 	}
    643 	else {
    644 		if( game->on_ground ) {
    645 			if( input->keys[ KEY_SPACE ] ) {
    646 				game->velocity.z = 9.8;
    647 				game->on_ground = false;
    648 			}
    649 		}
    650 
    651 		const float acceleration = 20.0f;
    652 		const float deceleration = 1.2f;
    653 		if( game->on_ground ) {
    654 			// apply friction
    655 			if( dot( game->velocity, game->velocity ) < 0.01f ) {
    656 				game->velocity = v3( 0 );
    657 			}
    658 			else {
    659 				float current_speed = length( game->velocity );
    660 				float drop = max( acceleration, current_speed ) * deceleration * dt;
    661 				float new_speed = max( 0.0f, current_speed - drop );
    662 				game->velocity = new_speed * ( game->velocity / current_speed );
    663 			}
    664 
    665 			if( fb != 0 || lr != 0 ) {
    666 				// apply acceleration
    667 				float max_speed = 5;
    668 
    669 				v3 forward_xy = v3_forward_xy( game->yaw );
    670 				v3 right_xy = normalize( cross( forward_xy, world_up ) );
    671 				v3 desired_direction = fb * forward_xy + lr * right_xy;
    672 
    673 				desired_direction = normalize( desired_direction );
    674 
    675 				float speed_in_desired_direction = dot( game->velocity, desired_direction );
    676 
    677 				if( speed_in_desired_direction < max_speed ) {
    678 					float accel_speed = min( acceleration * dt * max_speed, max_speed - speed_in_desired_direction );
    679 					game->velocity += accel_speed * desired_direction;
    680 				}
    681 			}
    682 		}
    683 
    684 		int spins = 0;
    685 		float remaining_t = dt;
    686 		while( remaining_t > 0 && spins < 5 ) {
    687 			spins++;
    688 			if( game->on_ground ) {
    689 				const float step_height = 0.1f;
    690 
    691 				v3 low_start = game->pos;
    692 				v3 low_end = low_start + game->velocity * remaining_t;
    693 
    694 				v3 high_start = game->pos + v3( 0, 0, step_height );
    695 				v3 high_end = high_start + game->velocity * remaining_t;
    696 
    697 				float low_t;
    698 				// initialise high_t to -1 so we never pick the high trace
    699 				// if we couldn't step up (fails the high_t > low_t test)
    700 				float high_t = -1;
    701 				v3 low_normal, high_normal;
    702 
    703 				bool low_hit = segment_vs_terrain( clipmap.quadtree, low_start, low_end, &low_t, &low_normal );
    704 				bool high_hit = false;
    705 
    706 				bool step_up_hit = segment_vs_terrain( clipmap.quadtree, low_start, high_start, NULL, NULL );
    707 				if( !step_up_hit ) {
    708 					high_hit = segment_vs_terrain( clipmap.quadtree, high_start, high_end, &high_t, &high_normal );
    709 				}
    710 
    711 				v3 step_down_start;
    712 				float step_down_distance;
    713 				bool should_step_down = false;
    714 
    715 				if( high_t > low_t ) {
    716 					if( high_hit ) {
    717 						game->pos = high_start + game->velocity * high_t * remaining_t;
    718 						remaining_t = ( 1.0f - high_t ) * remaining_t;
    719 					}
    720 					else {
    721 						step_down_start = high_end;
    722 						step_down_distance = step_height * 2;
    723 						should_step_down = true;
    724 						remaining_t = 0;
    725 					}
    726 				}
    727 				else {
    728 					if( low_hit ) {
    729 						game->pos = low_start + game->velocity * low_t * remaining_t;
    730 						remaining_t = ( 1.0f - low_t ) * remaining_t;
    731 					}
    732 					else {
    733 						step_down_start = low_end;
    734 						step_down_distance = step_height;
    735 						should_step_down = true;
    736 						remaining_t = 0;
    737 					}
    738 				}
    739 
    740 				if( should_step_down ) {
    741 					float step_down_t;
    742 					v3 step_down_end = step_down_start - v3( 0, 0, step_down_distance );
    743 
    744 					bool step_down_hit = segment_vs_terrain( clipmap.quadtree, step_down_start, step_down_end, &step_down_t, NULL );
    745 
    746 					game->pos = lerp( step_down_start, step_down_t, step_down_end );
    747 					game->on_ground = step_down_hit;
    748 				}
    749 			}
    750 
    751 			// TODO: this might be quite harmful
    752 			if( !game->on_ground ) {
    753 				const float gravity = -30.0f;
    754 				game->velocity += v3( 0, 0, gravity ) * remaining_t;
    755 
    756 				v3 start = game->pos;
    757 				v3 end = start + game->velocity * remaining_t;
    758 				float t;
    759 				v3 normal;
    760 				bool hit = segment_vs_terrain( clipmap.quadtree, start, end, &t, &normal );
    761 				if( hit ) {
    762 					// TODO: check normal is flat enough, otherwise stay in the air
    763 					game->on_ground = true;
    764 					game->pos += game->velocity * t * remaining_t;
    765 					// TODO: truncate velocity
    766 					game->velocity.z = 0;
    767 
    768 					remaining_t = ( 1.0f - t ) * remaining_t;
    769 				}
    770 				else {
    771 					game->pos = end;
    772 					remaining_t = 0;
    773 				}
    774 			}
    775 
    776 			if( game->on_ground ) {
    777 				// move up 1mm so float imprecision doesn't make us fall through the world
    778 				game->pos.z += 0.001f;
    779 			}
    780 		}
    781 	}
    782 
    783 	v3 eye_pos = game->pos + v3( 0, 0, EYE_HEIGHT );
    784 	m4 P = m4_perspective( VERTICAL_FOV, get_aspect_ratio(), NEAR_PLANE_DEPTH, FAR_PLANE_DEPTH );
    785 	m4 Pinf = m4_perspective_inf( VERTICAL_FOV, get_aspect_ratio(), NEAR_PLANE_DEPTH );
    786 	m4 V = m4_view( forward, right, up, eye_pos );
    787 	m4 Vsky = m4_view( forward, right, up, v3( 0 ) );
    788 	v3 sun_dir = v3( -cosf( game->sun_angle ), 0, sinf( game->sun_angle ) );
    789 
    790 	renderer_begin_frame();
    791 
    792 	u8 world_pass = renderer_add_pass( "Render world", msaa_fb, RENDERER_CLEAR_COLOUR_DO, RENDERER_CLEAR_DEPTH_DO );
    793 	u8 ents_pass = renderer_add_pass( "Render entities", msaa_fb );
    794 	u8 sky_pass = renderer_add_pass( "Render sky", msaa_fb );
    795 	renderer_resolve_msaa( msaa_fb );
    796 	u8 ui_pass = renderer_add_pass( "Render UI" );
    797 
    798 	UniformBinding view_uniforms = renderer_uniforms( V, P, game->pos );
    799 	UniformBinding inf_view_uniforms = renderer_uniforms( V, Pinf, game->pos );
    800 	UniformBinding sun_uniforms = renderer_uniforms( sun_dir, game->sun_angle );
    801 
    802 	StaticArray< UniformBinding, 4 > rotation_uniforms;
    803 	rotation_uniforms[ 0 ] = renderer_uniforms( m4_identity() );
    804 	rotation_uniforms[ 1 ] = renderer_uniforms( m4_rotz270() );
    805 	rotation_uniforms[ 2 ] = renderer_uniforms( m4_rotz90() );
    806 	rotation_uniforms[ 3 ] = renderer_uniforms( m4_rotz180() );
    807 
    808 	if( !game->frozen ) {
    809 		game->frozen_pos = game->pos;
    810 	}
    811 
    812 	// draw terrain
    813 	{
    814 		RenderState render_state;
    815 		render_state.pass = world_pass;
    816 		render_state.shader = get_shader( SHADER_CLIPMAP );
    817 		render_state.set_uniform( "view", view_uniforms );
    818 		render_state.set_uniform( "sun", sun_uniforms );
    819 		render_state.set_texture( "heightmap", clipmap.gpu.heightmap );
    820 		render_state.set_texture( "normalmap", clipmap.gpu.normalmap );
    821 		render_state.set_texture( "horizonmap", clipmap.gpu.horizonmap );
    822 		render_state.set_texture( "blue_noise", renderer_blue_noise() );
    823 		render_state.wireframe = game->draw_wireframe;
    824 
    825 		// draw cross
    826 		{
    827 			v2 snapped_pos = floorf( game->frozen_pos.xy() );
    828 			render_state.set_uniform( "model", rotation_uniforms[ 0 ] );
    829 			render_state.set_uniform( "clipmap", renderer_uniforms( snapped_pos, 1.0f ) );
    830 			renderer_draw_mesh( clipmap.gpu.cross, render_state );
    831 		}
    832 
    833 		for( u32 l = 0; l < NUM_CLIPMAP_LEVELS; l++ ) {
    834 			float scale = checked_cast< float >( u32( 1 ) << l );
    835 			v2 snapped_pos = floorf( game->frozen_pos.xy() / scale ) * scale;
    836 
    837 			// draw tiles
    838 			v2 tile_size = v2( checked_cast< float >( TILE_RESOLUTION << l ) );
    839 			v2 base = snapped_pos - v2( checked_cast< float >( TILE_RESOLUTION << ( l + 1 ) ) );
    840 
    841 			for( int x = 0; x < 4; x++ ) {
    842 				for( int y = 0; y < 4; y++ ) {
    843 					// draw a 4x4 set of tiles. cut out the middle 2x2 unless we're at the finest level
    844 					if( l != 0 && ( x == 1 || x == 2 ) && ( y == 1 || y == 2 ) ) {
    845 						continue;
    846 					}
    847 
    848 					v2 fill = v2( x >= 2 ? 1 : 0, y >= 2 ? 1 : 0 ) * scale;
    849 					v2 tile_tl = base + v2( x, y ) * tile_size + fill;
    850 
    851 					// draw a low poly tile if the tile is entirely outside the world
    852 					v2 tile_br = tile_tl + tile_size;
    853 					bool inside = true;
    854 					if( !intervals_overlap( tile_tl.x, tile_br.x, 0, clipmap.heightmap.w ) )
    855 						inside = false;
    856 					if( !intervals_overlap( tile_tl.y, tile_br.y, 0, clipmap.heightmap.h ) )
    857 						inside = false;
    858 
    859 					Mesh mesh = inside ? clipmap.gpu.tile : clipmap.gpu.empty_tile;
    860 					render_state.set_uniform( "model", rotation_uniforms[ 0 ] );
    861 					render_state.set_uniform( "clipmap", renderer_uniforms( tile_tl, scale ) );
    862 					renderer_draw_mesh( mesh, render_state );
    863 				}
    864 			}
    865 
    866 			// draw filler
    867 			{
    868 				render_state.set_uniform( "model", rotation_uniforms[ 0 ] );
    869 				render_state.set_uniform( "clipmap", renderer_uniforms( snapped_pos, scale ) );
    870 				renderer_draw_mesh( clipmap.gpu.filler, render_state );
    871 			}
    872 
    873 			if( l != NUM_CLIPMAP_LEVELS - 1 ) {
    874 				float next_scale = scale * 2.0f;
    875 				v2 next_snapped_pos = floorf( game->frozen_pos.xy() / next_scale ) * next_scale;
    876 
    877 				// draw trim
    878 				{
    879 					v2 tile_centre = snapped_pos + v2( scale * 0.5f );
    880 
    881 					v2 d = game->frozen_pos.xy() - next_snapped_pos;
    882 					u32 r = 0;
    883 					r |= d.x >= scale ? 0 : 2;
    884 					r |= d.y >= scale ? 0 : 1;
    885 
    886 					render_state.set_uniform( "model", rotation_uniforms[ r ] );
    887 					render_state.set_uniform( "clipmap", renderer_uniforms( tile_centre, scale ) );
    888 					renderer_draw_mesh( clipmap.gpu.trim, render_state );
    889 				}
    890 
    891 				// draw seam
    892 				{
    893 					v2 next_base = next_snapped_pos - v2( checked_cast< float >( TILE_RESOLUTION << ( l + 1 ) ) );
    894 
    895 					render_state.set_uniform( "model", rotation_uniforms[ 0 ] );
    896 					render_state.set_uniform( "clipmap", renderer_uniforms( next_base, scale ) );
    897 					renderer_draw_mesh( clipmap.gpu.seam, render_state );
    898 				}
    899 			}
    900 		}
    901 	}
    902 
    903 	// draw skirts
    904 	{
    905 		float scale = checked_cast< float >( u32( 1 ) << ( NUM_CLIPMAP_LEVELS - 1 ) );
    906 
    907 		RenderState skirt_render_state;
    908 		skirt_render_state.pass = world_pass;
    909 		skirt_render_state.shader = get_shader( SHADER_CLIPMAP_SKIRT );
    910 		skirt_render_state.set_uniform( "view", inf_view_uniforms );
    911 		skirt_render_state.set_uniform( "sun", sun_uniforms );
    912 		skirt_render_state.set_uniform( "clipmap_skirt", renderer_uniforms( scale ) );
    913 		skirt_render_state.set_texture( "blue_noise", renderer_blue_noise() );
    914 		skirt_render_state.wireframe = game->draw_wireframe;
    915 		renderer_draw_mesh( clipmap.gpu.skirt, skirt_render_state );
    916 	}
    917 
    918 	// draw trees
    919 	{
    920 		RenderState render_state;
    921 		render_state.pass = ents_pass;
    922 		render_state.shader = get_shader( SHADER_TREE );
    923 		render_state.set_uniform( "view", view_uniforms );
    924 		render_state.set_uniform( "sun", renderer_uniforms( sun_dir ) );
    925 		render_state.set_texture( "blue_noise", renderer_blue_noise() );
    926 
    927 		renderer_draw_instances( tree_mesh, render_state, num_trees, trees_instance_data );
    928 	}
    929 
    930 	// draw crosshair
    931 	{
    932 		const float aspect = get_aspect_ratio();
    933 		const float crosshair_thickness = 0.0025f;
    934 		const float crosshair_length = 0.01f;
    935 
    936 		const v4 red( 1, 0, 0, 1 );
    937 		immediate_triangle(
    938 			v3( -crosshair_length,  crosshair_thickness, 0 ),
    939 			v3( -crosshair_length, -crosshair_thickness, 0 ),
    940 			v3(  crosshair_length,  crosshair_thickness, 0 ),
    941 			red
    942 		);
    943 		immediate_triangle(
    944 			v3(  crosshair_length, -crosshair_thickness, 0 ),
    945 			v3(  crosshair_length,  crosshair_thickness, 0 ),
    946 			v3( -crosshair_length, -crosshair_thickness, 0 ),
    947 			red
    948 		);
    949 		immediate_triangle(
    950 			v3(  crosshair_thickness / aspect,  crosshair_length * aspect, 0 ),
    951 			v3( -crosshair_thickness / aspect,  crosshair_length * aspect, 0 ),
    952 			v3(  crosshair_thickness / aspect, -crosshair_length * aspect, 0 ),
    953 			red
    954 		);
    955 		immediate_triangle(
    956 			v3( -crosshair_thickness / aspect, -crosshair_length * aspect, 0 ),
    957 			v3(  crosshair_thickness / aspect, -crosshair_length * aspect, 0 ),
    958 			v3( -crosshair_thickness / aspect,  crosshair_length * aspect, 0 ),
    959 			red
    960 		);
    961 		RenderState render_state;
    962 		render_state.pass = ui_pass;
    963 		render_state.shader = get_shader( SHADER_UI );
    964 		render_state.depth_func = DEPTHFUNC_ALWAYS;
    965 		immediate_render( render_state );
    966 	}
    967 
    968 	// fireballs
    969 	if( input->keys[ KEY_F ] ) {
    970 		Fireball * fireball = fireballs.acquire();
    971 		fireball->pos = game->pos + v3( 0.0f, 0.0f, EYE_HEIGHT );
    972 		fireball->velocity = 128.0f * forward;
    973 	}
    974 
    975 	{
    976 		{
    977 			PROFILE_BLOCK( "Update fireballs" );
    978 			for( size_t i = 0; i < fireballs.elems.n; i++ ) {
    979 				Fireball * fireball = &fireballs.elems[ i ];
    980 
    981 				const float gravity = 30;
    982 				fireball->velocity.z -= dt * gravity;
    983 
    984 				Ray3 ray( fireball->pos, fireball->velocity );
    985 				v3 end = fireball->pos + fireball->velocity * dt;
    986 
    987 				float t;
    988 				v3 normal;
    989 				if( segment_vs_terrain( clipmap.quadtree, fireball->pos, end, &t, &normal ) ) {
    990 					Explosion * explosion = explosions.acquire();
    991 					explosion->pos = lerp( fireball->pos, t, end );
    992 					explosion->created_at = current_time;
    993 
    994 					fireballs.release( fireball );
    995 					i--;
    996 				}
    997 				else {
    998 					fireball->pos = end;
    999 					immediate_sphere( fireball->pos, 4, v4( 1, 0, 0, 1 ), 4 );
   1000 				}
   1001 			}
   1002 		}
   1003 
   1004 		RenderState fireball_render_state;
   1005 		fireball_render_state.pass = ents_pass;
   1006 		fireball_render_state.shader = get_shader( SHADER_FLAT_VERTEX_COLOURS );
   1007 		fireball_render_state.set_uniform( "view", view_uniforms );
   1008 		immediate_render( fireball_render_state );
   1009 
   1010 		for( size_t i = 0; i < explosions.elems.n; i++ ) {
   1011 			Explosion * explosion = &explosions.elems[ i ];
   1012 			float t = float( current_time - explosion->created_at );
   1013 			if( t < 0.5f ) {
   1014 				immediate_sphere( explosion->pos, t * 32.0f, v4( 1, 0.5, 0, 1 ), 4 );
   1015 			}
   1016 			else {
   1017 				explosions.release( explosion );
   1018 				i--;
   1019 			}
   1020 		}
   1021 
   1022 		RenderState explosion_render_state;
   1023 		explosion_render_state.pass = ents_pass;
   1024 		explosion_render_state.shader = get_shader( SHADER_FLAT_VERTEX_COLOURS );
   1025 		explosion_render_state.set_uniform( "view", view_uniforms );
   1026 		immediate_render( explosion_render_state );
   1027 	}
   1028 
   1029 	skybox_render( &game->skybox, sky_pass, Vsky, Pinf, game->sun_angle, sun_dir );
   1030 
   1031 	// networking
   1032 	{
   1033 		char buf[ 1400 ];
   1034 		NetAddress recv_addr;
   1035 		size_t len;
   1036 		if( !net_tryrecv( sock, buf, sizeof( buf ), &recv_addr, &len ) ) {
   1037 			len = 0;
   1038 		}
   1039 		if( recv_addr != server_addr ) {
   1040 			len = 0;
   1041 		}
   1042 
   1043 		ReadStream rs( buf, len );
   1044 
   1045 		if( connected && len > 0 ) {
   1046 			char write_buf[ 1400 ];
   1047 			WriteStream ws( write_buf );
   1048 			write( &ws, sid );
   1049 			write( &ws, game->pos );
   1050 
   1051 			net_send( sock, ws.start, ws.len(), server_addr );
   1052 
   1053 			u16 player_count = read_u16( &rs );
   1054 			ReadStreamCheckpoint rsc = rs.checkpoint();
   1055 
   1056 			for( u16 i = 0; i < player_count; i++ ) {
   1057 				read_u64( &rs );
   1058 				u8 player_state = read_u8( &rs );
   1059 				if( player_state == 0 ) {
   1060 					v3 pos;
   1061 					read( &rs, &pos );
   1062 				}
   1063 			}
   1064 
   1065 			if( rs.done() ) {
   1066 				rs.reset( &rsc );
   1067 				for( u16 i = 0; i < player_count; i++ ) {
   1068 					u64 player_sid = read_u64( &rs );
   1069 					u8 player_state = read_u8( &rs );
   1070 					v3 pos;
   1071 					if( player_state == 0 ) {
   1072 						read( &rs, &pos );
   1073 					}
   1074 
   1075 					Player * player = NULL;
   1076 					if( !sid_to_player.get( player_sid, &player ) && player_state == 0 ) {
   1077 						player = players.acquire();
   1078 						ASSERT( player != NULL ); // TODO
   1079 						sid_to_player.add( player_sid, player );
   1080 						player->sid = player_sid;
   1081 					}
   1082 
   1083 					if( player_state == 0 ) {
   1084 						player->pos = pos;
   1085 					}
   1086 					else if( player_state == 1 ) {
   1087 						ggprint( "{08x} disconnected\n", player->sid );
   1088 						players.release( player );
   1089 					}
   1090 				}
   1091 
   1092 				ASSERT( rs.done() );
   1093 			}
   1094 			else {
   1095 				if( len > 0 ) {
   1096 					FATAL( "bad message from the server" );
   1097 				}
   1098 			}
   1099 		}
   1100 		else if( len > 0 ) {
   1101 			sid = read_u64( &rs );
   1102 			if( rs.ok ) {
   1103 				connected = true;
   1104 			}
   1105 		}
   1106 
   1107 		if( !connected && current_time - last_connection_attempt > 1.0 ) {
   1108 			char write_buf[ 1400 ];
   1109 			WriteStream ws( write_buf );
   1110 
   1111 			write_u64( &ws, ~u64( 0 ) );
   1112 			write( &ws, game->pos );
   1113 
   1114 			net_send( sock, ws.start, ws.len(), server_addr );
   1115 
   1116 			last_connection_attempt = current_time;
   1117 		}
   1118 
   1119 		for( const Player & player : players ) {
   1120 			if( player.sid != sid ) {
   1121 				v3 mins = player.pos - v3( 0.4f, 0.4f, 0.0f );
   1122 				v3 maxs = player.pos + v3( 0.4f, 0.4f, EYE_HEIGHT + 0.1f );
   1123 				immediate_aabb( mins, maxs, v4( 1, 1, 0, 1 ) );
   1124 			}
   1125 		}
   1126 
   1127 		RenderState impact_render_state;
   1128 		impact_render_state.pass = world_pass;
   1129 		impact_render_state.shader = get_shader( SHADER_FLAT_VERTEX_COLOURS );
   1130 		impact_render_state.set_uniform( "view", view_uniforms );
   1131 		immediate_render( impact_render_state );
   1132 	}
   1133 
   1134 	if( game->draw_quadtree ) {
   1135 		v3u32 mins = v3u32( 0, 0, clipmap.quadtree.nodes[ 0 ].min_z );
   1136 		v3u32 maxs = v3u32( clipmap.heightmap.w, clipmap.heightmap.h, clipmap.quadtree.nodes[ 0 ].max_z );
   1137 		draw_qt( MinMaxu32( mins, maxs ), clipmap.quadtree.nodes, 0 );
   1138 
   1139 		RenderState render_state;
   1140 		render_state.pass = world_pass;
   1141 		render_state.shader = get_shader( SHADER_WIREFRAME );
   1142 		render_state.set_uniform( "view", view_uniforms );
   1143 		render_state.wireframe = true;
   1144 		immediate_render( render_state );
   1145 	}
   1146 
   1147 	// info text
   1148 	{
   1149 		const str< 128 > status( "Frame time: {.1}ms FPS: {.1} {}", dt * 1000.0f, 1.0f / dt,
   1150 			connected ? "connected!" : "connecting" );
   1151 		draw_text( ui_pass, status.c_str(), 2, 2, 16.0f );
   1152 		draw_text( ui_pass, str< 128 >( "pos = {.1} vel = {.1}", game->pos, game->velocity ).c_str(), 2, 20, 16 );
   1153 		draw_text( ui_pass, str< 128 >( "drawcalls = {}, tris = {}", renderer_num_draw_calls(), renderer_num_vertices() / 3 ).c_str(), 2, 38, 16 );
   1154 	}
   1155 
   1156 	renderer_end_frame();
   1157 }