medfall

A super great game engine
Log | Files | Refs

clipmap.cc (34297B)


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