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 }