medfall

A super great game engine
Log | Files | Refs

bsp.cc (20060B)


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