medfall

A super great game engine
Log | Files | Refs

bsp.cc (19443B)


      1 #include <stddef.h>
      2 #include <math.h>
      3 
      4 #include "game.h"
      5 #include "intrinsics.h"
      6 #include "log.h"
      7 #include "gl.h"
      8 #include "renderer.h"
      9 #include "shaders.h"
     10 #include "obj.h"
     11 #include "immediate.h"
     12 #include "bsp.h"
     13 #include "bsp_renderer.h"
     14 #include "text_renderer.h"
     15 
     16 static bool fix = false;
     17 static v3 fix_start;
     18 static v3 fix_end;
     19 
     20 static FB world_depth_and_normals;
     21 static FB model_depth;
     22 static FB model_depth_outline;
     23 static Mesh tree_mesh;
     24 
     25 static const float EPSILON = 1.0 / 32.0;
     26 
     27 // TODO: bit twiddling?
     28 bool same_sign( const float a, const float b ) {
     29 	return a * b >= 0;
     30 }
     31 
     32 float point_plane_distance( v3 point, v3 normal, float d ) {
     33 	return dot( point, normal ) - d;
     34 }
     35 
     36 const char * lump_names[] = {
     37 	"LUMP_ENTITIES",
     38 	"LUMP_TEXTURES",
     39 	"LUMP_PLANES",
     40 	"LUMP_NODES",
     41 	"LUMP_LEAVES",
     42 	"LUMP_LEAFFACES",
     43 	"LUMP_LEAFBRUSHES",
     44 	"LUMP_MODELS",
     45 	"LUMP_BRUSHES",
     46 	"LUMP_BRUSHSIDES",
     47 	"LUMP_VERTICES",
     48 	"LUMP_MESHVERTS",
     49 	"LUMP_FOGS",
     50 	"LUMP_FACES",
     51 	"LUMP_LIGHTING",
     52 	"LUMP_LIGHTGRID",
     53 	"LUMP_VISIBILITY",
     54 	"LUMP_LIGHTARRAY",
     55 };
     56 
     57 void bsp_init( BSP * bsp, const char * filename ) {
     58 	bsp->contents = file_get_contents( filename, NULL );
     59 
     60 	const BSP_Header * header = ( const BSP_Header * ) bsp->contents;
     61 
     62 	bool ibsp = header->magic == 0x50534249;
     63 
     64 	for( int i = 0; i < LUMP_MAX; i++ ) {
     65 		u32 displayed_size = header->lumps[ i ].len / 1000;
     66 		const char * unit = "KB";
     67 		if( displayed_size >= 1000 ) {
     68 			displayed_size /= 1000;
     69 			unit = "MB";
     70 		}
     71 		ggprint( "{} size = {}{}\n", lump_names[ i ], displayed_size, unit );
     72 	}
     73 
     74 	bsp->load_lump( bsp->num_textures, bsp->textures, LUMP_TEXTURES );
     75 	bsp->load_lump( bsp->num_planes, bsp->planes, LUMP_PLANES );
     76 	bsp->load_lump( bsp->num_nodes, bsp->nodes, LUMP_NODES );
     77 	bsp->load_lump( bsp->num_leaves, bsp->leaves, LUMP_LEAVES );
     78 	bsp->load_lump( bsp->num_leaf_faces, bsp->leaf_faces, LUMP_LEAFFACES );
     79 	bsp->load_lump( bsp->num_leaf_brushes, bsp->leaf_brushes, LUMP_LEAFBRUSHES );
     80 	bsp->load_lump( bsp->num_brushes, bsp->brushes, LUMP_BRUSHES );
     81 	bsp->load_lump( bsp->num_mesh_verts, bsp->mesh_verts, LUMP_MESHVERTS );
     82 
     83 	if( ibsp ) {
     84 		bsp->load_lump( bsp->num_brush_sides, bsp->brush_sides, LUMP_BRUSHSIDES );
     85 		bsp->load_lump( bsp->num_vertices, bsp->vertices, LUMP_VERTICES );
     86 		bsp->load_lump( bsp->num_faces, bsp->faces, LUMP_FACES );
     87 		bsp->rbsp_brush_sides = NULL;
     88 		bsp->rbsp_vertices = NULL;
     89 		bsp->rbsp_faces = NULL;
     90 	}
     91 	else {
     92 		bsp->load_lump( bsp->num_brush_sides, bsp->rbsp_brush_sides, LUMP_BRUSHSIDES );
     93 		bsp->load_lump( bsp->num_vertices, bsp->rbsp_vertices, LUMP_VERTICES );
     94 		bsp->load_lump( bsp->num_faces, bsp->rbsp_faces, LUMP_FACES );
     95 		bsp->fixup_rbsp();
     96 	}
     97 
     98 	bsp->load_vis();
     99 }
    100 
    101 void bsp_destroy( BSP * bsp ) {
    102 	free( bsp->contents );
    103 	if( bsp->rbsp_brush_sides != NULL ) {
    104 		free( bsp->brush_sides );
    105 		free( bsp->vertices );
    106 		free( bsp->faces );
    107 	}
    108 }
    109 
    110 template< typename T >
    111 void BSP::load_lump( u32 & num_ts, T *& ts, const BSP_Lump lump ) {
    112 	const BSP_Header * header = reinterpret_cast< BSP_Header * >( contents );
    113 	const BSP_HeaderLump & hl = header->lumps[ lump ];
    114 
    115 	ASSERT( hl.len % sizeof( T ) == 0 );
    116 
    117 	num_ts = hl.len / sizeof( T );
    118 	ts = reinterpret_cast< T * >( contents + hl.off );
    119 
    120 	printf( "%d: ok. off %u len %u num %u\n", lump, hl.off, hl.len, num_ts );
    121 }
    122 
    123 void BSP::fixup_rbsp() {
    124 	vertices = malloc_array< BSP_Vertex >( num_vertices );
    125 	for( u32 i = 0; i < num_vertices; i++ ) {
    126 		vertices[ i ].pos = rbsp_vertices[ i ].pos;
    127 		vertices[ i ].normal = rbsp_vertices[ i ].normal;
    128 	}
    129 
    130 	brush_sides = malloc_array< BSP_BrushSide >( num_brush_sides );
    131 	for( u32 i = 0; i < num_brush_sides; i++ ) {
    132 		brush_sides[ i ].plane = rbsp_brush_sides[ i ].plane;
    133 	}
    134 
    135 	faces = malloc_array< BSP_Face >( num_faces );
    136 	for( u32 i = 0; i < num_faces; i++ ) {
    137 		faces[ i ].first_vert = rbsp_faces[ i ].first_vert;
    138 		faces[ i ].num_verts = rbsp_faces[ i ].num_verts;
    139 		faces[ i ].first_mesh_vert = rbsp_faces[ i ].first_mesh_vert;
    140 		faces[ i ].num_mesh_verts = rbsp_faces[ i ].num_mesh_verts;
    141 		faces[ i ].normal = rbsp_faces[ i ].normal;
    142 	}
    143 }
    144 
    145 void BSP::load_vis() {
    146 	const BSP_Header * header = reinterpret_cast< BSP_Header * >( contents );
    147 	const BSP_HeaderLump & hl = header->lumps[ LUMP_VISIBILITY ];
    148 
    149 	vis = reinterpret_cast< BSP_Vis * >( contents + hl.off );
    150 	vis->pvs = ( u8 * ) vis + offsetof( BSP_Vis, pvs );
    151 
    152 	ASSERT( hl.len == 2 * sizeof( u32 ) + vis->num_clusters * vis->cluster_size );
    153 
    154 	printf( "%d: ok. off %u len %u num %u\n", LUMP_VISIBILITY, hl.off, hl.len, vis->num_clusters * vis->cluster_size );
    155 }
    156 
    157 // Adapted from RTCD, which means I need this:
    158 //
    159 // from Real-Time Collision Detection by Christer Ericson, published by Morgan
    160 // Kaufmann Publishers, (c) 2005 Elsevier Inc
    161 bool BSP::trace_seg_brush( const BSP_Brush & brush, v3 start, v3 dir, float tmin, float tmax, float * tout ) const {
    162 	const v3 end = start + dir * tmax;
    163 	float tfar = tmin;
    164 	float tnear = tmax;
    165 	bool hit = false;
    166 	bool starts_inside = true;
    167 
    168 	for( u32 i = 0; i < brush.num_sides; i++ ) {
    169 		const BSP_BrushSide & side = brush_sides[ i + brush.first_side ];
    170 		const BSP_Plane & plane = planes[ side.plane ];
    171 
    172 		const float start_dist = point_plane_distance( start, plane.n, plane.d );
    173 		const float end_dist = point_plane_distance( end, plane.n, plane.d );
    174 
    175 		// both points infront of plane - we are outside the brush
    176 		if( start_dist > 0.0f && end_dist > 0.0f ) return false;
    177 		// both points behind plane - we intersect with another side
    178 		if( start_dist <= 0.0f && end_dist <= 0.0f ) continue;
    179 
    180 		if( start_dist >= 0.0f ) starts_inside = false;
    181 
    182 		const float denom = -dot( plane.n, dir );
    183 		const float t = start_dist / denom;
    184 
    185 		if( t >= tmin && t <= tmax ) {
    186 			v4 colour = v4( 0, 0, 1, 1 );
    187 			// TODO: if we are exiting the brush we want the nearest collision
    188 			if( start_dist >= 0.0f ) {
    189 				if( t >= tfar ) {
    190 					tfar = t;
    191 					hit = true;
    192 					colour = v4( 1, 1, 1, 1 );
    193 				}
    194 			}
    195 			else if( t <= tnear ) {
    196 				tnear = t;
    197 				hit = true;
    198 				colour = v4( 0, 0, 0, 1 );
    199 			}
    200 			immediate_sphere( start + t * dir, 8, colour, 8 );
    201 		}
    202 		else
    203 			immediate_sphere( start + t * dir, 8, v4( 0.5, 0.5, 0.5, 1 ), 8 );
    204 	}
    205 
    206 	v4 colour = v4( 1, 0, 1, 1 );
    207 	if( tnear >= tfar ) colour = v4( 1, 1, 0, 1 );
    208 
    209 	if( hit ) immediate_sphere( start + tfar * dir, 16, colour );
    210 	if( hit ) {
    211 		if( !starts_inside ) {
    212 			if( tfar <= tnear ) {
    213 				*tout = tfar;
    214 				return true;
    215 			}
    216 		}
    217 		else if( tnear < tfar ) {
    218 			*tout = tnear;
    219 			return true;
    220 		}
    221 	}
    222 
    223 	return false;
    224 }
    225 
    226 void BSP::trace_seg_leaf( u32 leaf_idx, v3 start, v3 dir, float tmin, float tmax, BSP_Intersection & bis ) const {
    227 	const BSP_Leaf & leaf = leaves[ leaf_idx ];
    228 
    229 	for( u32 i = 0; i < leaf.num_brushes; i++ ) {
    230 		const BSP_Brush & brush = brushes[ leaf_brushes[ i + leaf.first_brush ] ];
    231 		const BSP_Texture & texture = textures[ brush.texture ];
    232 
    233 		// TODO: magic number
    234 		if( texture.content_flags & 1 ) {
    235 			float t;
    236 			bool hit = trace_seg_brush( brush, start, dir, tmin, tmax, &t );
    237 
    238 			if( hit ) {
    239 				bis.hit = true;
    240 				if( t < bis.is.t ) bis.is.t = t;
    241 			}
    242 		}
    243 	}
    244 }
    245 
    246 void BSP::trace_seg_tree( s32 node_idx, v3 start, v3 dir, float tmin, float tmax, BSP_Intersection & bis ) const {
    247 	if( bis.hit ) return;
    248 
    249 	if( node_idx < 0 ) {
    250 		trace_seg_leaf( -( node_idx + 1 ), start, dir, tmin, tmax, bis );
    251 		return;
    252 	}
    253 
    254 	const BSP_Node & node = nodes[ node_idx ];
    255 	const BSP_Plane & plane = planes[ node.plane ];
    256 
    257 	// ( start + dir * t ) . plane.n = plane.d
    258 	// start . plane.n + t * dir . plane.n = plane.d
    259 	// t * dir . plane.n = plane.d - start . plane.n
    260 	// t * denom = dist
    261 	const float denom = dot( plane.n, dir );
    262 	const float dist = -point_plane_distance( start, plane.n, plane.d );
    263 	bool near_child = dist > 0.0f;
    264 	bool check_both_sides = false;
    265 	float t = tmax;
    266 
    267 	if( denom != 0.0f ) {
    268 		const float unchecked_t = dist / denom;
    269 
    270 		// if t > tmax, we hit the plane beyond the area we want to
    271 		// check so we only need to look at stuff on the near side
    272 		// if t < 0, we didn't even hit the plane
    273 		if( unchecked_t >= 0 && unchecked_t <= tmax ) {
    274 			// we hit the plane before our threshold, so the area
    275 			// we want to check is entirely on the other side
    276 			if( unchecked_t < tmin ) {
    277 				near_child = !near_child;
    278 			}
    279 			else {
    280 				// otherwise we straddle the plane
    281 				check_both_sides = true;
    282 				t = unchecked_t;
    283 				immediate_sphere( start + t * dir, 8, v4( 0, 1, 0, 1 ), 8 );
    284 			}
    285 		}
    286 	}
    287 
    288 	// TODO: if we hit on the near side we should early out
    289 	trace_seg_tree( node.children[ near_child ], start, dir, tmin, t, bis );
    290 
    291 	if( check_both_sides ) {
    292 		trace_seg_tree( node.children[ !near_child ], start, dir, t, tmax, bis );
    293 	}
    294 }
    295 
    296 bool BSP::trace_seg( v3 start, v3 end, Intersection & is ) const {
    297 	BSP_Intersection bis = { };
    298 	bis.is.t = 1.0f;
    299 	bis.start = start;
    300 	bis.end = end;
    301 
    302 	v3 dir = end - start;
    303 
    304 	trace_seg_tree( 0, start, dir, 0.0f, 1.0f, bis );
    305 
    306 	if( bis.hit ) {
    307 		bis.is.pos = start + bis.is.t * ( end - start );
    308 	}
    309 	is = bis.is;
    310 
    311 	return bis.hit;
    312 }
    313 
    314 BSP_Leaf & BSP::position_to_leaf( v3 pos ) const {
    315 	s32 node_idx = 0;
    316 
    317 	do {
    318 		const BSP_Node & node = nodes[ node_idx ];
    319 		const BSP_Plane & plane = planes[ node.plane ];
    320 
    321 		const float dist = point_plane_distance( pos, plane.n, plane.d );
    322 
    323 		node_idx = node.children[ dist < 0 ];
    324 	} while( node_idx >= 0 );
    325 
    326 	return leaves[ -( node_idx + 1 ) ];
    327 }
    328 
    329 static v2 screen_pos( const m4 & P, const m4 & V, v3 camera_pos, v3 target, v2 clamp, float near_plane_depth, bool * clamped ) {
    330 	*clamped = false;
    331 
    332 	v4 clip = P * V * v4( target, 1.0 );
    333 	if( abs( clip.z ) < near_plane_depth )
    334 		return v2( 0, 0 );
    335 
    336 	v2 res = clip.xy() / clip.z;
    337 
    338 	v3 forward = -V.row2().xyz();
    339 	float d = dot( target - camera_pos, forward );
    340 	if( d < 0 )
    341 		res = -res;
    342 
    343 	if( abs( res.x ) > clamp.x || abs( res.y ) > clamp.y || d < 0 ) {
    344 		float rx = clamp.x / abs( res.x );
    345 		float ry = clamp.y / abs( res.y );
    346 
    347 		res *= min( rx, ry );
    348 
    349 		*clamped = true;
    350 	}
    351 
    352 	return res;
    353 }
    354 
    355 GAME_INIT( game_init ) {
    356 	bsp_init( &game->bsp, "assets/cocaine_b2.bsp" );
    357 
    358 	game->pos = v3( 0, -100, 450 );
    359 	game->pitch = 0;
    360 	game->yaw = 0;
    361 
    362 	bspr_init( &game->bspr, &game->bsp );
    363 
    364 	{
    365 		v2u32 window_size = get_window_size();
    366 
    367 		TextureConfig texture_config;
    368 		texture_config.width = window_size.x;
    369 		texture_config.height = window_size.y;
    370 		texture_config.wrap = TEXWRAP_CLAMP;
    371 
    372 		FramebufferConfig fb_config;
    373 
    374 		// texture_config.format = TEXFMT_RGB_U8;
    375 		// fb_config.textures[ OUTPUT_ALBEDO ].config = texture_config;
    376 		// fb_config.textures[ OUTPUT_ALBEDO ].attachment = FB_COLOUR;
    377 
    378 		texture_config.format = TEXFMT_RGB_HALF;
    379 		fb_config.textures[ OUTPUT_NORMAL ].config = texture_config;
    380 		fb_config.textures[ OUTPUT_NORMAL ].attachment = FB_NORMAL;
    381 
    382 		texture_config.format = TEXFMT_DEPTH;
    383 		fb_config.textures[ OUTPUT_DEPTH ].config = texture_config;
    384 		fb_config.textures[ OUTPUT_DEPTH ].attachment = FB_DEPTH;
    385 
    386 		world_depth_and_normals = renderer_new_fb( fb_config );
    387 	}
    388 
    389 	{
    390 		v2u32 window_size = get_window_size();
    391 
    392 		TextureConfig texture_config;
    393 		texture_config.width = window_size.x;
    394 		texture_config.height = window_size.y;
    395 		texture_config.wrap = TEXWRAP_CLAMP;
    396 
    397 		FramebufferConfig fb_config;
    398 
    399 		texture_config.format = TEXFMT_DEPTH;
    400 		fb_config.textures[ OUTPUT_DEPTH ].config = texture_config;
    401 		fb_config.textures[ OUTPUT_DEPTH ].attachment = FB_DEPTH;
    402 
    403 		model_depth = renderer_new_fb( fb_config );
    404 		model_depth_outline = renderer_new_fb( fb_config );
    405 	}
    406 
    407 	tree_mesh = load_obj( "models/trees/PineTree.obj", &mem->persistent_arena );
    408 }
    409 
    410 GAME_FRAME( game_frame ) {
    411 	const int fb = input->keys[ KEY_W ] - input->keys[ KEY_S ];
    412 	const int lr = input->keys[ KEY_D ] - input->keys[ KEY_A ];
    413 	const int dz = input->keys[ KEY_SPACE ] - input->keys[ KEY_LEFTSHIFT ];
    414 
    415 	int dpitch = input->keys[ KEY_DOWNARROW ] - input->keys[ KEY_UPARROW ];
    416 	int dyaw = input->keys[ KEY_LEFTARROW ] - input->keys[ KEY_RIGHTARROW ];
    417 
    418 	dpitch += input->keys[ KEY_K ] - input->keys[ KEY_I ];
    419 	dyaw += input->keys[ KEY_J ] - input->keys[ KEY_L ];
    420 
    421 	game->pitch += dpitch * dt * 100;
    422 	game->yaw += dyaw * dt * 100;
    423 
    424 	const v3 world_up = v3( 0, 0, 1 );
    425 	const v3 forward = v3_forward( game->pitch, game->yaw );
    426 	const v3 right = normalize( cross( forward, world_up ) );
    427 	const v3 up = normalize( cross( right, forward ) );
    428 
    429 	float speed = input->keys[ KEY_LEFTCTRL ] ? 640 : 320;
    430 	if( input->keys[ KEY_LEFTALT ] )
    431 		speed = 100;
    432 	game->pos += forward * speed * dt * fb;
    433 	game->pos += right * speed * dt * lr;
    434 	game->pos.z += dz * speed * dt;
    435 
    436 	renderer_begin_frame();
    437 
    438 	m4 P = m4_perspective( 90, get_aspect_ratio(), NEAR_PLANE_DEPTH, FAR_PLANE_DEPTH );
    439 	m4 V = m4_view( forward, right, up, game->pos );
    440 
    441 	UniformBinding view_uniforms = renderer_uniforms( V, P );
    442 	UniformBinding world_model_uniforms = renderer_uniforms( m4_identity() );
    443 	UniformBinding tree_model_uniforms = renderer_uniforms( m4_translation( 100, -600, 340 + 5 * sinf( current_time * 2 ) ) * m4_rotz( current_time ) * m4_rotx( deg_to_rad( 90 ) ) * m4_scale( 32 ) );
    444 
    445 	// depth/normals pre pass
    446 	{
    447 		renderer_begin_pass( world_depth_and_normals, RENDERER_CLEAR_COLOUR_DO, RENDERER_CLEAR_DEPTH_DO );
    448 
    449 		RenderState render_state;
    450 		render_state.shader = get_shader( SHADER_FLAT_VERTEX_COLOURS_TO_GBUFFER );
    451 		render_state.uniforms[ UNIFORMS_VIEW ] = view_uniforms;
    452 		render_state.uniforms[ UNIFORMS_MODEL ] = world_model_uniforms;
    453 		render_state.cull_face = CULLFACE_FRONT;
    454 
    455 		bspr_render( &game->bspr, render_state );
    456 
    457 		render_state.cull_face = CULLFACE_BACK;
    458 
    459 		// immediate_sphere( v3( 0, 0, 0 ), 128, v4( 1, 1, 0, 1 ) );
    460                 //
    461 		// if( input->keys[ KEY_T ] ) {
    462 		// 	fix = true;
    463 		// 	fix_start = game->pos;
    464 		// 	fix_end = fix_start + forward * 1000.0f;
    465 		// }
    466                 //
    467 		// if( fix ) {
    468 		// 	Intersection is;
    469 		// 	bool hit = game->bspr.bsp->trace_seg( fix_start, fix_end, is );
    470                 //
    471 		// 	if( hit ) {
    472 		// 		immediate_sphere( is.pos, 16, v4( 1, 0, 0, 1 ) );
    473 		// 	}
    474 		// }
    475                 //
    476 		// immediate_render( render_state );
    477                 //
    478 		render_state.uniforms[ UNIFORMS_MODEL ] = tree_model_uniforms;
    479 		renderer_draw_mesh( tree_mesh, render_state );
    480 
    481 		renderer_end_pass();
    482 	}
    483 
    484 	// model depth
    485 	{
    486 		RenderPassConfig pass;
    487 		pass.target = model_depth;
    488 		pass.clear_depth = true;
    489 		pass.depth = 0.0f;
    490 		renderer_begin_pass( pass );
    491 
    492 		RenderState render_state;
    493 		render_state.shader = get_shader( SHADER_FLAT_VERTEX_COLOURS_TO_GBUFFER );
    494 		render_state.depth_func = DEPTHFUNC_ALWAYS;
    495 		render_state.uniforms[ UNIFORMS_VIEW ] = view_uniforms;
    496 		render_state.uniforms[ UNIFORMS_MODEL ] = tree_model_uniforms;
    497 
    498 		renderer_draw_mesh( tree_mesh, render_state );
    499 
    500 		renderer_end_pass();
    501 	}
    502 
    503 	// model depth edge detection
    504 	{
    505 		renderer_begin_pass( model_depth_outline, RENDERER_CLEAR_COLOUR_DONT, RENDERER_CLEAR_DEPTH_DONT );
    506 
    507 		RenderState render_state;
    508 		render_state.shader = get_shader( SHADER_DEPTH_EDGE );
    509 		render_state.depth_func = DEPTHFUNC_ALWAYS;
    510 		render_state.textures[ 0 ] = model_depth.textures[ OUTPUT_DEPTH ];
    511 
    512 		renderer_draw_fullscreen_triangle( render_state );
    513 
    514 		renderer_end_pass();
    515 	}
    516 
    517 	// compose
    518 	{
    519 		renderer_begin_pass( RENDERER_CLEAR_COLOUR_DO, RENDERER_CLEAR_DEPTH_DO );
    520 
    521 		RenderState render_state;
    522 		render_state.shader = get_shader( SHADER_GBUFFER );
    523 		render_state.uniforms[ UNIFORMS_VIEW ] = view_uniforms;
    524 		render_state.uniforms[ UNIFORMS_MODEL ] = world_model_uniforms;
    525 		render_state.cull_face = CULLFACE_FRONT;
    526 		// render_state.depth_func = DEPTHFUNC_EQUAL;
    527 		render_state.textures[ 0 ] = world_depth_and_normals.textures[ OUTPUT_NORMAL ];
    528 		render_state.textures[ 1 ] = world_depth_and_normals.textures[ OUTPUT_DEPTH ];
    529 		render_state.textures[ 2 ] = model_depth_outline.textures[ OUTPUT_DEPTH ];
    530 		render_state.textures[ 3 ] = renderer_blue_noise();
    531 
    532 		bspr_render( &game->bspr, render_state );
    533 
    534 		render_state.cull_face = CULLFACE_BACK;
    535 
    536 		// immediate_sphere( v3( 0, 0, 0 ), 128, v4( 1, 1, 0, 1 ) );
    537                 //
    538 		// if( input->keys[ KEY_T ] ) {
    539 		// 	fix = true;
    540 		// 	fix_start = game->pos;
    541 		// 	fix_end = fix_start + forward * 1000.0f;
    542 		// }
    543                 //
    544 		// if( fix ) {
    545 		// 	Intersection is;
    546 		// 	bool hit = game->bspr.bsp->trace_seg( fix_start, fix_end, is );
    547                 //
    548 		// 	if( hit ) {
    549 		// 		immediate_sphere( is.pos, 16, v4( 1, 0, 0, 1 ) );
    550 		// 	}
    551 		// }
    552                 //
    553 		// immediate_render( render_state );
    554 
    555 		render_state.uniforms[ UNIFORMS_MODEL ] = tree_model_uniforms;
    556 		renderer_draw_mesh( tree_mesh, render_state );
    557 
    558 		renderer_end_pass();
    559 	}
    560 
    561 	// UI pass
    562 	renderer_begin_pass( RENDERER_CLEAR_COLOUR_DONT, RENDERER_CLEAR_DEPTH_DONT );
    563 
    564 	// draw crosshair
    565 	{
    566 		const float aspect = get_aspect_ratio();
    567 		const float crosshair_thickness = 0.0025f;
    568 		const float crosshair_length = 0.01f;
    569 
    570 		const v4 red( 1, 0, 0, 1 );
    571 		immediate_triangle(
    572 			v3( -crosshair_length,  crosshair_thickness, 0 ),
    573 			v3( -crosshair_length, -crosshair_thickness, 0 ),
    574 			v3(  crosshair_length,  crosshair_thickness, 0 ),
    575 			red
    576 		);
    577 		immediate_triangle(
    578 			v3(  crosshair_length, -crosshair_thickness, 0 ),
    579 			v3(  crosshair_length,  crosshair_thickness, 0 ),
    580 			v3( -crosshair_length, -crosshair_thickness, 0 ),
    581 			red
    582 		);
    583 		immediate_triangle(
    584 			v3(  crosshair_thickness / aspect,  crosshair_length * aspect, 0 ),
    585 			v3( -crosshair_thickness / aspect,  crosshair_length * aspect, 0 ),
    586 			v3(  crosshair_thickness / aspect, -crosshair_length * aspect, 0 ),
    587 			red
    588 		);
    589 		immediate_triangle(
    590 			v3( -crosshair_thickness / aspect, -crosshair_length * aspect, 0 ),
    591 			v3(  crosshair_thickness / aspect, -crosshair_length * aspect, 0 ),
    592 			v3( -crosshair_thickness / aspect,  crosshair_length * aspect, 0 ),
    593 			red
    594 		);
    595 		RenderState render_state;
    596 		render_state.shader = get_shader( SHADER_UI );
    597 		render_state.depth_func = DEPTHFUNC_ALWAYS;
    598 		immediate_render( render_state );
    599 	}
    600 
    601 	// draw position indicator
    602 	{
    603 		v3 target = v3( 100, -600, 400 + 5 * sinf( current_time * 2 ) );
    604 
    605 		v2u32 window_size = get_window_size();
    606 		const float aspect = get_aspect_ratio();
    607 		const float crosshair_length = 0.01f;
    608 		float dist = length( target - game->pos );
    609 
    610 		int pxsize = lerp( 12, saturate( unlerp( 100.0f, dist, 500.0f ) ), 20 );
    611 		float w = float( pxsize ) / window_size.x;
    612 		float h = float( pxsize ) / window_size.y;
    613 
    614 		bool clamped;
    615 		v2 a = screen_pos( P, V, game->pos, target, v2( 1.0f - 2 * w, 1.0f - 2 * h ), NEAR_PLANE_DEPTH, &clamped );
    616 		v3 t = v3( a, 1 );
    617 
    618 		const v4 red( 0, 0.5, 0, 1 );
    619 		immediate_triangle(
    620 			t + v3( -w,  h, 0 ),
    621 			t + v3( -w, -h, 0 ),
    622 			t + v3(  w,  h, 0 ),
    623 			red
    624 		);
    625 		immediate_triangle(
    626 			t + v3(  w, -h, 0 ),
    627 			t + v3(  w,  h, 0 ),
    628 			t + v3( -w, -h, 0 ),
    629 			red
    630 		);
    631 		RenderState render_state;
    632 		render_state.shader = get_shader( SHADER_UI );
    633 		render_state.depth_func = DEPTHFUNC_ALWAYS;
    634 		immediate_render( render_state );
    635 
    636 		if( !clamped ) {
    637 			v2 p = ( a + 1.0f ) / 2.0f;
    638 			p.y = 1.0f - p.y;
    639 			p *= v2( window_size.x, window_size.y );
    640 			draw_centered_text( "DEFEND", p.x, p.y - 16 - pxsize / 2, 12 );
    641 		}
    642 	}
    643 
    644 	{
    645 		char buf[ 256 ];
    646 		snprintf( buf, sizeof( buf ), "pos: (%.2f %.2f %.2f) pitch: %.2f yaw: %.2f forward: (%.2f %.2f %.2f) right: (%.2f %.2f %.2f) up: (%.2f %.2f %.2f)", game->pos.x, game->pos.y, game->pos.z, game->pitch, game->yaw,
    647 			forward.x, forward.y, forward.z,
    648 			right.x, right.y, right.z,
    649 			up.x, up.y, up.z
    650 			);
    651 		draw_text( buf, 2, 2, 16 );
    652 		draw_text( str< 128 >( "drawcalls = {}, tris = {}", renderer_num_draw_calls(), renderer_num_vertices() / 3 ).c_str(), 2, 20, 16 );
    653 	}
    654 
    655 	renderer_end_pass();
    656 	renderer_end_frame();
    657 }