medfall

A super great game engine
Log | Files | Refs

clipmap.cc (34211B)


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