medfall

A super great game engine
Log | Files | Refs

bsp.cc (19307B)


      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/wbomb6.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 
    371 		FramebufferConfig fb_config;
    372 
    373 		// texture_config.format = TEXFMT_RGB_U8;
    374 		// fb_config.textures[ OUTPUT_ALBEDO ].config = texture_config;
    375 		// fb_config.textures[ OUTPUT_ALBEDO ].attachment = FB_COLOUR;
    376 
    377 		texture_config.format = TEXFMT_RGB_HALF;
    378 		fb_config.textures[ OUTPUT_NORMAL ].config = texture_config;
    379 		fb_config.textures[ OUTPUT_NORMAL ].attachment = FB_NORMAL;
    380 
    381 		texture_config.format = TEXFMT_DEPTH;
    382 		fb_config.textures[ OUTPUT_DEPTH ].config = texture_config;
    383 		fb_config.textures[ OUTPUT_DEPTH ].attachment = FB_DEPTH;
    384 
    385 		world_depth_and_normals = renderer_new_fb( fb_config );
    386 	}
    387 
    388 	{
    389 		v2u32 window_size = get_window_size();
    390 
    391 		TextureConfig texture_config;
    392 		texture_config.width = window_size.x;
    393 		texture_config.height = window_size.y;
    394 
    395 		FramebufferConfig fb_config;
    396 
    397 		texture_config.format = TEXFMT_DEPTH;
    398 		fb_config.textures[ OUTPUT_DEPTH ].config = texture_config;
    399 		fb_config.textures[ OUTPUT_DEPTH ].attachment = FB_DEPTH;
    400 
    401 		model_depth = renderer_new_fb( fb_config );
    402 		model_depth_outline = renderer_new_fb( fb_config );
    403 	}
    404 
    405 	tree_mesh = load_obj( "models/trees/PineTree.obj", &mem->persistent_arena );
    406 }
    407 
    408 GAME_FRAME( game_frame ) {
    409 	const int fb = input->keys[ KEY_W ] - input->keys[ KEY_S ];
    410 	const int lr = input->keys[ KEY_D ] - input->keys[ KEY_A ];
    411 	const int dz = input->keys[ KEY_SPACE ] - input->keys[ KEY_LEFTSHIFT ];
    412 
    413 	int dpitch = input->keys[ KEY_DOWNARROW ] - input->keys[ KEY_UPARROW ];
    414 	int dyaw = input->keys[ KEY_LEFTARROW ] - input->keys[ KEY_RIGHTARROW ];
    415 
    416 	dpitch += input->keys[ KEY_K ] - input->keys[ KEY_I ];
    417 	dyaw += input->keys[ KEY_J ] - input->keys[ KEY_L ];
    418 
    419 	game->pitch += dpitch * dt * 100;
    420 	game->yaw += dyaw * dt * 100;
    421 
    422 	const v3 world_up = v3( 0, 0, 1 );
    423 	const v3 forward = v3_forward( game->pitch, game->yaw );
    424 	const v3 right = normalize( cross( forward, world_up ) );
    425 	const v3 up = normalize( cross( right, forward ) );
    426 
    427 	float speed = input->keys[ KEY_LEFTCTRL ] ? 640 : 320;
    428 	if( input->keys[ KEY_LEFTALT ] )
    429 		speed = 100;
    430 	game->pos += forward * speed * dt * fb;
    431 	game->pos += right * speed * dt * lr;
    432 	game->pos.z += dz * speed * dt;
    433 
    434 	renderer_begin_frame();
    435 
    436 	m4 P = m4_perspective( 90, get_aspect_ratio(), NEAR_PLANE_DEPTH, FAR_PLANE_DEPTH );
    437 	m4 V = m4_view( forward, right, up, game->pos );
    438 
    439 	UniformBinding view_uniforms = renderer_uniforms( V, P );
    440 	UniformBinding world_model_uniforms = renderer_uniforms( m4_identity() );
    441 	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 ) );
    442 
    443 	// depth/normals pre pass
    444 	{
    445 		renderer_begin_pass( world_depth_and_normals, RENDERER_CLEAR_COLOUR_DO, RENDERER_CLEAR_DEPTH_DO );
    446 
    447 		RenderState render_state;
    448 		render_state.shader = get_shader( SHADER_FLAT_VERTEX_COLOURS_TO_GBUFFER );
    449 		render_state.uniforms[ UNIFORMS_VIEW ] = view_uniforms;
    450 		render_state.uniforms[ UNIFORMS_MODEL ] = world_model_uniforms;
    451 		render_state.cull_face = CULLFACE_FRONT;
    452 
    453 		bspr_render( &game->bspr, render_state );
    454 
    455 		render_state.cull_face = CULLFACE_BACK;
    456 
    457 		// immediate_sphere( v3( 0, 0, 0 ), 128, v4( 1, 1, 0, 1 ) );
    458                 //
    459 		// if( input->keys[ KEY_T ] ) {
    460 		// 	fix = true;
    461 		// 	fix_start = game->pos;
    462 		// 	fix_end = fix_start + forward * 1000.0f;
    463 		// }
    464                 //
    465 		// if( fix ) {
    466 		// 	Intersection is;
    467 		// 	bool hit = game->bspr.bsp->trace_seg( fix_start, fix_end, is );
    468                 //
    469 		// 	if( hit ) {
    470 		// 		immediate_sphere( is.pos, 16, v4( 1, 0, 0, 1 ) );
    471 		// 	}
    472 		// }
    473                 //
    474 		// immediate_render( render_state );
    475                 //
    476 		render_state.uniforms[ UNIFORMS_MODEL ] = tree_model_uniforms;
    477 		renderer_draw_mesh( tree_mesh, render_state );
    478 
    479 		renderer_end_pass();
    480 	}
    481 
    482 	// model depth
    483 	{
    484 		RenderPassConfig pass;
    485 		pass.target = model_depth;
    486 		pass.clear_depth = true;
    487 		pass.depth = 0.0f;
    488 		renderer_begin_pass( pass );
    489 
    490 		RenderState render_state;
    491 		render_state.shader = get_shader( SHADER_FLAT_VERTEX_COLOURS_TO_GBUFFER );
    492 		render_state.depth_func = DEPTHFUNC_ALWAYS;
    493 		render_state.uniforms[ UNIFORMS_VIEW ] = view_uniforms;
    494 		render_state.uniforms[ UNIFORMS_MODEL ] = tree_model_uniforms;
    495 
    496 		renderer_draw_mesh( tree_mesh, render_state );
    497 
    498 		renderer_end_pass();
    499 	}
    500 
    501 	// model depth edge detection
    502 	{
    503 		renderer_begin_pass( model_depth_outline, RENDERER_CLEAR_COLOUR_DONT, RENDERER_CLEAR_DEPTH_DONT );
    504 
    505 		RenderState render_state;
    506 		render_state.shader = get_shader( SHADER_DEPTH_EDGE );
    507 		render_state.depth_func = DEPTHFUNC_ALWAYS;
    508 		render_state.textures[ 0 ] = model_depth.textures[ OUTPUT_DEPTH ];
    509 
    510 		renderer_draw_fullscreen_triangle( render_state );
    511 
    512 		renderer_end_pass();
    513 	}
    514 
    515 	// compose
    516 	{
    517 		renderer_begin_pass( RENDERER_CLEAR_COLOUR_DO, RENDERER_CLEAR_DEPTH_DO );
    518 
    519 		RenderState render_state;
    520 		render_state.shader = get_shader( SHADER_GBUFFER );
    521 		render_state.uniforms[ UNIFORMS_VIEW ] = view_uniforms;
    522 		render_state.uniforms[ UNIFORMS_MODEL ] = world_model_uniforms;
    523 		render_state.cull_face = CULLFACE_FRONT;
    524 		// render_state.depth_func = DEPTHFUNC_EQUAL;
    525 		render_state.textures[ 0 ] = world_depth_and_normals.textures[ OUTPUT_NORMAL ];
    526 		render_state.textures[ 1 ] = world_depth_and_normals.textures[ OUTPUT_DEPTH ];
    527 		render_state.textures[ 2 ] = model_depth_outline.textures[ OUTPUT_DEPTH ];
    528 
    529 		bspr_render( &game->bspr, render_state );
    530 
    531 		render_state.cull_face = CULLFACE_BACK;
    532 
    533 		// immediate_sphere( v3( 0, 0, 0 ), 128, v4( 1, 1, 0, 1 ) );
    534                 //
    535 		// if( input->keys[ KEY_T ] ) {
    536 		// 	fix = true;
    537 		// 	fix_start = game->pos;
    538 		// 	fix_end = fix_start + forward * 1000.0f;
    539 		// }
    540                 //
    541 		// if( fix ) {
    542 		// 	Intersection is;
    543 		// 	bool hit = game->bspr.bsp->trace_seg( fix_start, fix_end, is );
    544                 //
    545 		// 	if( hit ) {
    546 		// 		immediate_sphere( is.pos, 16, v4( 1, 0, 0, 1 ) );
    547 		// 	}
    548 		// }
    549                 //
    550 		// immediate_render( render_state );
    551 
    552 		render_state.uniforms[ UNIFORMS_MODEL ] = tree_model_uniforms;
    553 		renderer_draw_mesh( tree_mesh, render_state );
    554 
    555 		renderer_end_pass();
    556 	}
    557 
    558 	// UI pass
    559 	renderer_begin_pass( RENDERER_CLEAR_COLOUR_DONT, RENDERER_CLEAR_DEPTH_DONT );
    560 
    561 	// draw crosshair
    562 	{
    563 		const float aspect = get_aspect_ratio();
    564 		const float crosshair_thickness = 0.0025f;
    565 		const float crosshair_length = 0.01f;
    566 
    567 		const v4 red( 1, 0, 0, 1 );
    568 		immediate_triangle(
    569 			v3( -crosshair_length,  crosshair_thickness, 0 ),
    570 			v3( -crosshair_length, -crosshair_thickness, 0 ),
    571 			v3(  crosshair_length,  crosshair_thickness, 0 ),
    572 			red
    573 		);
    574 		immediate_triangle(
    575 			v3(  crosshair_length, -crosshair_thickness, 0 ),
    576 			v3(  crosshair_length,  crosshair_thickness, 0 ),
    577 			v3( -crosshair_length, -crosshair_thickness, 0 ),
    578 			red
    579 		);
    580 		immediate_triangle(
    581 			v3(  crosshair_thickness / aspect,  crosshair_length * aspect, 0 ),
    582 			v3( -crosshair_thickness / aspect,  crosshair_length * aspect, 0 ),
    583 			v3(  crosshair_thickness / aspect, -crosshair_length * aspect, 0 ),
    584 			red
    585 		);
    586 		immediate_triangle(
    587 			v3( -crosshair_thickness / aspect, -crosshair_length * aspect, 0 ),
    588 			v3(  crosshair_thickness / aspect, -crosshair_length * aspect, 0 ),
    589 			v3( -crosshair_thickness / aspect,  crosshair_length * aspect, 0 ),
    590 			red
    591 		);
    592 		RenderState render_state;
    593 		render_state.shader = get_shader( SHADER_UI );
    594 		render_state.depth_func = DEPTHFUNC_ALWAYS;
    595 		immediate_render( render_state );
    596 	}
    597 
    598 	// draw position indicator
    599 	{
    600 		v3 target = v3( 100, -600, 400 + 5 * sinf( current_time * 2 ) );
    601 
    602 		v2u32 window_size = get_window_size();
    603 		const float aspect = get_aspect_ratio();
    604 		const float crosshair_length = 0.01f;
    605 		float dist = length( target - game->pos );
    606 
    607 		int pxsize = lerp( 12, saturate( unlerp( 100.0f, dist, 500.0f ) ), 20 );
    608 		float w = float( pxsize ) / window_size.x;
    609 		float h = float( pxsize ) / window_size.y;
    610 
    611 		bool clamped;
    612 		v2 a = screen_pos( P, V, game->pos, target, v2( 1.0f - 2 * w, 1.0f - 2 * h ), NEAR_PLANE_DEPTH, &clamped );
    613 		v3 t = v3( a, 1 );
    614 
    615 		const v4 red( 0, 0.5, 0, 1 );
    616 		immediate_triangle(
    617 			t + v3( -w,  h, 0 ),
    618 			t + v3( -w, -h, 0 ),
    619 			t + v3(  w,  h, 0 ),
    620 			red
    621 		);
    622 		immediate_triangle(
    623 			t + v3(  w, -h, 0 ),
    624 			t + v3(  w,  h, 0 ),
    625 			t + v3( -w, -h, 0 ),
    626 			red
    627 		);
    628 		RenderState render_state;
    629 		render_state.shader = get_shader( SHADER_UI );
    630 		render_state.depth_func = DEPTHFUNC_ALWAYS;
    631 		immediate_render( render_state );
    632 
    633 		if( !clamped ) {
    634 			v2 p = ( a + 1.0f ) / 2.0f;
    635 			p.y = 1.0f - p.y;
    636 			p *= v2( window_size.x, window_size.y );
    637 			draw_centered_text( "DEFEND", p.x, p.y - 16 - pxsize / 2, 12 );
    638 		}
    639 	}
    640 
    641 	{
    642 		char buf[ 256 ];
    643 		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,
    644 			forward.x, forward.y, forward.z,
    645 			right.x, right.y, right.z,
    646 			up.x, up.y, up.z
    647 			);
    648 		draw_text( buf, 2, 2, 16 );
    649 		draw_text( str< 128 >( "drawcalls = {}, tris = {}", renderer_num_draw_calls(), renderer_num_vertices() / 3 ).c_str(), 2, 20, 16 );
    650 	}
    651 
    652 	renderer_end_pass();
    653 	renderer_end_frame();
    654 }