RenderWorld_portals.cpp (30705B)
1 /* 2 =========================================================================== 3 4 Doom 3 BFG Edition GPL Source Code 5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 6 7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). 8 9 Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify 10 it under the terms of the GNU General Public License as published by 11 the Free Software Foundation, either version 3 of the License, or 12 (at your option) any later version. 13 14 Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 GNU General Public License for more details. 18 19 You should have received a copy of the GNU General Public License 20 along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>. 21 22 In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. 23 24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. 25 26 =========================================================================== 27 */ 28 29 #pragma hdrstop 30 #include "../idlib/precompiled.h" 31 32 #include "tr_local.h" 33 34 // if we hit this many planes, we will just stop cropping the 35 // view down, which is still correct, just conservative 36 const int MAX_PORTAL_PLANES = 20; 37 38 struct portalStack_t { 39 const portal_t * p; 40 const portalStack_t * next; 41 // positive side is outside the visible frustum 42 int numPortalPlanes; 43 idPlane portalPlanes[MAX_PORTAL_PLANES+1]; 44 idScreenRect rect; 45 }; 46 47 /* 48 ======================================================================= 49 50 Create viewLights and viewEntitys for the lights and entities that are 51 visible in the portal areas that can be seen from the current viewpoint. 52 53 ======================================================================= 54 */ 55 56 /* 57 ============= 58 R_SetLightDefViewLight 59 60 If the lightDef is not already on the viewLight list, create 61 a viewLight and add it to the list with an empty scissor rect. 62 ============= 63 */ 64 viewLight_t *R_SetLightDefViewLight( idRenderLightLocal *light ) { 65 if ( light->viewCount == tr.viewCount ) { 66 // already set up for this frame 67 return light->viewLight; 68 } 69 light->viewCount = tr.viewCount; 70 71 // add to the view light chain 72 viewLight_t * vLight = (viewLight_t *)R_ClearedFrameAlloc( sizeof( *vLight ), FRAME_ALLOC_VIEW_LIGHT ); 73 vLight->lightDef = light; 74 75 // the scissorRect will be expanded as the light bounds is accepted into visible portal chains 76 // and the scissor will be reduced in R_AddSingleLight based on the screen space projection 77 vLight->scissorRect.Clear(); 78 79 // link the view light 80 vLight->next = tr.viewDef->viewLights; 81 tr.viewDef->viewLights = vLight; 82 83 light->viewLight = vLight; 84 85 return vLight; 86 } 87 88 /* 89 ============= 90 R_SetEntityDefViewEntity 91 92 If the entityDef is not already on the viewEntity list, create 93 a viewEntity and add it to the list with an empty scissor rect. 94 ============= 95 */ 96 viewEntity_t *R_SetEntityDefViewEntity( idRenderEntityLocal *def ) { 97 if ( def->viewCount == tr.viewCount ) { 98 // already set up for this frame 99 return def->viewEntity; 100 } 101 def->viewCount = tr.viewCount; 102 103 viewEntity_t * vModel = (viewEntity_t *)R_ClearedFrameAlloc( sizeof( *vModel ), FRAME_ALLOC_VIEW_ENTITY ); 104 vModel->entityDef = def; 105 106 // the scissorRect will be expanded as the model bounds is accepted into visible portal chains 107 // It will remain clear if the model is only needed for shadows. 108 vModel->scissorRect.Clear(); 109 110 vModel->next = tr.viewDef->viewEntitys; 111 tr.viewDef->viewEntitys = vModel; 112 113 def->viewEntity = vModel; 114 115 return vModel; 116 } 117 118 /* 119 ================ 120 CullEntityByPortals 121 122 Return true if the entity reference bounds do not intersect the current portal chain. 123 ================ 124 */ 125 bool idRenderWorldLocal::CullEntityByPortals( const idRenderEntityLocal *entity, const portalStack_t *ps ) { 126 if ( r_useEntityPortalCulling.GetInteger() == 1 ) { 127 128 ALIGNTYPE16 frustumCorners_t corners; 129 idRenderMatrix::GetFrustumCorners( corners, entity->inverseBaseModelProject, bounds_unitCube ); 130 for ( int i = 0; i < ps->numPortalPlanes; i++ ) { 131 if ( idRenderMatrix::CullFrustumCornersToPlane( corners, ps->portalPlanes[i] ) == FRUSTUM_CULL_FRONT ) { 132 return true; 133 } 134 } 135 136 } else if ( r_useEntityPortalCulling.GetInteger() >= 2 ) { 137 138 idRenderMatrix baseModelProject; 139 idRenderMatrix::Inverse( entity->inverseBaseModelProject, baseModelProject ); 140 141 idPlane frustumPlanes[6]; 142 idRenderMatrix::GetFrustumPlanes( frustumPlanes, baseModelProject, false, true ); 143 144 // exact clip of light faces against all planes 145 for ( int i = 0; i < 6; i++ ) { 146 // the entity frustum planes face inward, so the planes that have the 147 // view origin on the positive side will be the "back" faces of the entity, 148 // which must have some fragment inside the portal stack planes to be visible 149 if ( frustumPlanes[i].Distance( tr.viewDef->renderView.vieworg ) <= 0.0f ) { 150 continue; 151 } 152 153 // calculate a winding for this frustum side 154 idFixedWinding w; 155 w.BaseForPlane( frustumPlanes[i] ); 156 for ( int j = 0; j < 6; j++ ) { 157 if ( j == i ) { 158 continue; 159 } 160 if ( !w.ClipInPlace( frustumPlanes[j], ON_EPSILON ) ) { 161 break; 162 } 163 } 164 if ( w.GetNumPoints() <= 2 ) { 165 continue; 166 } 167 168 assert( ps->numPortalPlanes <= MAX_PORTAL_PLANES ); 169 assert( w.GetNumPoints() + ps->numPortalPlanes < MAX_POINTS_ON_WINDING ); 170 171 // now clip the winding against each of the portalStack planes 172 // skip the last plane which is the last portal itself 173 for ( int j = 0; j < ps->numPortalPlanes - 1; j++ ) { 174 if ( !w.ClipInPlace( -ps->portalPlanes[j], ON_EPSILON ) ) { 175 break; 176 } 177 } 178 179 if ( w.GetNumPoints() > 2 ) { 180 // part of the winding is visible through the portalStack, 181 // so the entity is not culled 182 return false; 183 } 184 } 185 186 // nothing was visible 187 return true; 188 189 } 190 191 return false; 192 } 193 194 /* 195 =================== 196 AddAreaViewEntities 197 198 Any models that are visible through the current portalStack will have their scissor rect updated. 199 =================== 200 */ 201 void idRenderWorldLocal::AddAreaViewEntities( int areaNum, const portalStack_t *ps ) { 202 portalArea_t * area = &portalAreas[ areaNum ]; 203 204 for ( areaReference_t * ref = area->entityRefs.areaNext; ref != &area->entityRefs; ref = ref->areaNext ) { 205 idRenderEntityLocal * entity = ref->entity; 206 207 // debug tool to allow viewing of only one entity at a time 208 if ( r_singleEntity.GetInteger() >= 0 && r_singleEntity.GetInteger() != entity->index ) { 209 continue; 210 } 211 212 // remove decals that are completely faded away 213 R_FreeEntityDefFadedDecals( entity, tr.viewDef->renderView.time[0] ); 214 215 // check for completely suppressing the model 216 if ( !r_skipSuppress.GetBool() ) { 217 if ( entity->parms.suppressSurfaceInViewID 218 && entity->parms.suppressSurfaceInViewID == tr.viewDef->renderView.viewID ) { 219 continue; 220 } 221 if ( entity->parms.allowSurfaceInViewID 222 && entity->parms.allowSurfaceInViewID != tr.viewDef->renderView.viewID ) { 223 continue; 224 } 225 } 226 227 // cull reference bounds 228 if ( CullEntityByPortals( entity, ps ) ) { 229 // we are culled out through this portal chain, but it might 230 // still be visible through others 231 continue; 232 } 233 234 viewEntity_t * vEnt = R_SetEntityDefViewEntity( entity ); 235 236 // possibly expand the scissor rect 237 vEnt->scissorRect.Union( ps->rect ); 238 } 239 } 240 241 /* 242 ================ 243 CullLightByPortals 244 245 Return true if the light frustum does not intersect the current portal chain. 246 ================ 247 */ 248 bool idRenderWorldLocal::CullLightByPortals( const idRenderLightLocal *light, const portalStack_t *ps ) { 249 if ( r_useLightPortalCulling.GetInteger() == 1 ) { 250 251 ALIGNTYPE16 frustumCorners_t corners; 252 idRenderMatrix::GetFrustumCorners( corners, light->inverseBaseLightProject, bounds_zeroOneCube ); 253 for ( int i = 0; i < ps->numPortalPlanes; i++ ) { 254 if ( idRenderMatrix::CullFrustumCornersToPlane( corners, ps->portalPlanes[i] ) == FRUSTUM_CULL_FRONT ) { 255 return true; 256 } 257 } 258 259 } else if ( r_useLightPortalCulling.GetInteger() >= 2 ) { 260 261 idPlane frustumPlanes[6]; 262 idRenderMatrix::GetFrustumPlanes( frustumPlanes, light->baseLightProject, true, true ); 263 264 // exact clip of light faces against all planes 265 for ( int i = 0; i < 6; i++ ) { 266 // the light frustum planes face inward, so the planes that have the 267 // view origin on the positive side will be the "back" faces of the light, 268 // which must have some fragment inside the the portal stack planes to be visible 269 if ( frustumPlanes[i].Distance( tr.viewDef->renderView.vieworg ) <= 0.0f ) { 270 continue; 271 } 272 273 // calculate a winding for this frustum side 274 idFixedWinding w; 275 w.BaseForPlane( frustumPlanes[i] ); 276 for ( int j = 0; j < 6; j++ ) { 277 if ( j == i ) { 278 continue; 279 } 280 if ( !w.ClipInPlace( frustumPlanes[j], ON_EPSILON ) ) { 281 break; 282 } 283 } 284 if ( w.GetNumPoints() <= 2 ) { 285 continue; 286 } 287 288 assert( ps->numPortalPlanes <= MAX_PORTAL_PLANES ); 289 assert( w.GetNumPoints() + ps->numPortalPlanes < MAX_POINTS_ON_WINDING ); 290 291 // now clip the winding against each of the portalStack planes 292 // skip the last plane which is the last portal itself 293 for ( int j = 0; j < ps->numPortalPlanes - 1; j++ ) { 294 if ( !w.ClipInPlace( -ps->portalPlanes[j], ON_EPSILON ) ) { 295 break; 296 } 297 } 298 299 if ( w.GetNumPoints() > 2 ) { 300 // part of the winding is visible through the portalStack, 301 // so the light is not culled 302 return false; 303 } 304 } 305 306 // nothing was visible 307 return true; 308 } 309 310 return false; 311 } 312 313 /* 314 =================== 315 AddAreaViewLights 316 317 This is the only point where lights get added to the viewLights list. 318 Any lights that are visible through the current portalStack will have their scissor rect updated. 319 =================== 320 */ 321 void idRenderWorldLocal::AddAreaViewLights( int areaNum, const portalStack_t *ps ) { 322 portalArea_t * area = &portalAreas[ areaNum ]; 323 324 for ( areaReference_t * lref = area->lightRefs.areaNext; lref != &area->lightRefs; lref = lref->areaNext ) { 325 idRenderLightLocal * light = lref->light; 326 327 // debug tool to allow viewing of only one light at a time 328 if ( r_singleLight.GetInteger() >= 0 && r_singleLight.GetInteger() != light->index ) { 329 continue; 330 } 331 332 // check for being closed off behind a door 333 // a light that doesn't cast shadows will still light even if it is behind a door 334 if ( r_useLightAreaCulling.GetBool() && !light->LightCastsShadows() 335 && light->areaNum != -1 && !tr.viewDef->connectedAreas[ light->areaNum ] ) { 336 continue; 337 } 338 339 // cull frustum 340 if ( CullLightByPortals( light, ps ) ) { 341 // we are culled out through this portal chain, but it might 342 // still be visible through others 343 continue; 344 } 345 346 viewLight_t * vLight = R_SetLightDefViewLight( light ); 347 348 // expand the scissor rect 349 vLight->scissorRect.Union( ps->rect ); 350 } 351 } 352 353 /* 354 =================== 355 AddAreaToView 356 357 This may be entered multiple times with different planes 358 if more than one portal sees into the area 359 =================== 360 */ 361 void idRenderWorldLocal::AddAreaToView( int areaNum, const portalStack_t *ps ) { 362 // mark the viewCount, so r_showPortals can display the considered portals 363 portalAreas[ areaNum ].viewCount = tr.viewCount; 364 365 // add the models and lights, using more precise culling to the planes 366 AddAreaViewEntities( areaNum, ps ); 367 AddAreaViewLights( areaNum, ps ); 368 } 369 370 /* 371 =================== 372 idRenderWorldLocal::ScreenRectForWinding 373 =================== 374 */ 375 idScreenRect idRenderWorldLocal::ScreenRectFromWinding( const idWinding * w, const viewEntity_t * space ) { 376 const float viewWidth = (float) tr.viewDef->viewport.x2 - (float) tr.viewDef->viewport.x1; 377 const float viewHeight = (float) tr.viewDef->viewport.y2 - (float) tr.viewDef->viewport.y1; 378 379 idScreenRect r; 380 r.Clear(); 381 for ( int i = 0; i < w->GetNumPoints(); i++ ) { 382 idVec3 v; 383 idVec3 ndc; 384 R_LocalPointToGlobal( space->modelMatrix, (*w)[i].ToVec3(), v ); 385 R_GlobalToNormalizedDeviceCoordinates( v, ndc ); 386 387 float windowX = ( ndc[0] * 0.5f + 0.5f ) * viewWidth; 388 float windowY = ( ndc[1] * 0.5f + 0.5f ) * viewHeight; 389 390 r.AddPoint( windowX, windowY ); 391 } 392 393 r.Expand(); 394 395 return r; 396 } 397 398 /* 399 =================== 400 idRenderWorldLocal::PortalIsFoggedOut 401 =================== 402 */ 403 bool idRenderWorldLocal::PortalIsFoggedOut( const portal_t *p ) { 404 idRenderLightLocal * ldef = p->doublePortal->fogLight; 405 if ( ldef == NULL ) { 406 return false; 407 } 408 409 // find the current density of the fog 410 const idMaterial * lightShader = ldef->lightShader; 411 const int size = lightShader->GetNumRegisters() * sizeof( float ); 412 float * regs = (float *)_alloca( size ); 413 414 lightShader->EvaluateRegisters( regs, ldef->parms.shaderParms, 415 tr.viewDef->renderView.shaderParms, tr.viewDef->renderView.time[0] * 0.001f, ldef->parms.referenceSound ); 416 417 const shaderStage_t *stage = lightShader->GetStage(0); 418 419 const float alpha = regs[ stage->color.registers[3] ]; 420 421 // if they left the default value on, set a fog distance of 500 422 float a; 423 if ( alpha <= 1.0f ) { 424 a = -0.5f / DEFAULT_FOG_DISTANCE; 425 } else { 426 // otherwise, distance = alpha color 427 a = -0.5f / alpha; 428 } 429 430 idPlane forward; 431 forward[0] = a * tr.viewDef->worldSpace.modelViewMatrix[0*4+2]; 432 forward[1] = a * tr.viewDef->worldSpace.modelViewMatrix[1*4+2]; 433 forward[2] = a * tr.viewDef->worldSpace.modelViewMatrix[2*4+2]; 434 forward[3] = a * tr.viewDef->worldSpace.modelViewMatrix[3*4+2]; 435 436 const idWinding * w = p->w; 437 for ( int i = 0; i < w->GetNumPoints(); i++ ) { 438 const float d = forward.Distance( (*w)[i].ToVec3() ); 439 if ( d < 0.5f ) { 440 return false; // a point not clipped off 441 } 442 } 443 444 return true; 445 } 446 447 /* 448 =================== 449 idRenderWorldLocal::FloodViewThroughArea_r 450 =================== 451 */ 452 void idRenderWorldLocal::FloodViewThroughArea_r( const idVec3 & origin, int areaNum, const portalStack_t *ps ) { 453 portalArea_t * area = &portalAreas[ areaNum ]; 454 455 // cull models and lights to the current collection of planes 456 AddAreaToView( areaNum, ps ); 457 458 if ( areaScreenRect[areaNum].IsEmpty() ) { 459 areaScreenRect[areaNum] = ps->rect; 460 } else { 461 areaScreenRect[areaNum].Union( ps->rect ); 462 } 463 464 // go through all the portals 465 for ( const portal_t * p = area->portals; p != NULL; p = p->next ) { 466 // an enclosing door may have sealed the portal off 467 if ( p->doublePortal->blockingBits & PS_BLOCK_VIEW ) { 468 continue; 469 } 470 471 // make sure this portal is facing away from the view 472 const float d = p->plane.Distance( origin ); 473 if ( d < -0.1f ) { 474 continue; 475 } 476 477 // make sure the portal isn't in our stack trace, 478 // which would cause an infinite loop 479 const portalStack_t * check = ps; 480 for ( ; check != NULL; check = check->next ) { 481 if ( check->p == p ) { 482 break; // don't recursively enter a stack 483 } 484 } 485 if ( check ) { 486 continue; // already in stack 487 } 488 489 // if we are very close to the portal surface, don't bother clipping 490 // it, which tends to give epsilon problems that make the area vanish 491 if ( d < 1.0f ) { 492 493 // go through this portal 494 portalStack_t newStack; 495 newStack = *ps; 496 newStack.p = p; 497 newStack.next = ps; 498 FloodViewThroughArea_r( origin, p->intoArea, &newStack ); 499 continue; 500 } 501 502 // clip the portal winding to all of the planes 503 idFixedWinding w; // we won't overflow because MAX_PORTAL_PLANES = 20 504 w = *p->w; 505 for ( int j = 0; j < ps->numPortalPlanes; j++ ) { 506 if ( !w.ClipInPlace( -ps->portalPlanes[j], 0 ) ) { 507 break; 508 } 509 } 510 if ( !w.GetNumPoints() ) { 511 continue; // portal not visible 512 } 513 514 // see if it is fogged out 515 if ( PortalIsFoggedOut( p ) ) { 516 continue; 517 } 518 519 // go through this portal 520 portalStack_t newStack; 521 newStack.p = p; 522 newStack.next = ps; 523 524 // find the screen pixel bounding box of the remaining portal 525 // so we can scissor things outside it 526 newStack.rect = ScreenRectFromWinding( &w, &tr.identitySpace ); 527 528 // slop might have spread it a pixel outside, so trim it back 529 newStack.rect.Intersect( ps->rect ); 530 531 // generate a set of clipping planes that will further restrict 532 // the visible view beyond just the scissor rect 533 534 int addPlanes = w.GetNumPoints(); 535 if ( addPlanes > MAX_PORTAL_PLANES ) { 536 addPlanes = MAX_PORTAL_PLANES; 537 } 538 539 newStack.numPortalPlanes = 0; 540 for ( int i = 0; i < addPlanes; i++ ) { 541 int j = i + 1; 542 if ( j == w.GetNumPoints() ) { 543 j = 0; 544 } 545 546 const idVec3 & v1 = origin - w[i].ToVec3(); 547 const idVec3 & v2 = origin - w[j].ToVec3(); 548 549 newStack.portalPlanes[newStack.numPortalPlanes].Normal().Cross( v2, v1 ); 550 551 // if it is degenerate, skip the plane 552 if ( newStack.portalPlanes[newStack.numPortalPlanes].Normalize() < 0.01f ) { 553 continue; 554 } 555 newStack.portalPlanes[newStack.numPortalPlanes].FitThroughPoint( origin ); 556 557 newStack.numPortalPlanes++; 558 } 559 560 // the last stack plane is the portal plane 561 newStack.portalPlanes[newStack.numPortalPlanes] = p->plane; 562 newStack.numPortalPlanes++; 563 564 FloodViewThroughArea_r( origin, p->intoArea, &newStack ); 565 } 566 } 567 568 /* 569 ======================= 570 idRenderWorldLocal::FlowViewThroughPortals 571 572 Finds viewLights and viewEntities by flowing from an origin through the visible 573 portals that the origin point can see into. The planes array defines a volume with 574 the planes pointing outside the volume. Zero planes assumes an unbounded volume. 575 ======================= 576 */ 577 void idRenderWorldLocal::FlowViewThroughPortals( const idVec3 & origin, int numPlanes, const idPlane *planes ) { 578 portalStack_t ps; 579 ps.next = NULL; 580 ps.p = NULL; 581 582 assert( numPlanes <= MAX_PORTAL_PLANES ); 583 for ( int i = 0; i < numPlanes; i++ ) { 584 ps.portalPlanes[i] = planes[i]; 585 } 586 587 ps.numPortalPlanes = numPlanes; 588 ps.rect = tr.viewDef->scissor; 589 590 // if outside the world, mark everything 591 if ( tr.viewDef->areaNum < 0 ){ 592 for ( int i = 0; i < numPortalAreas; i++ ) { 593 areaScreenRect[i] = tr.viewDef->scissor; 594 AddAreaToView( i, &ps ); 595 } 596 } else { 597 // flood out through portals, setting area viewCount 598 FloodViewThroughArea_r( origin, tr.viewDef->areaNum, &ps ); 599 } 600 } 601 602 /* 603 =================== 604 idRenderWorldLocal::BuildConnectedAreas_r 605 =================== 606 */ 607 void idRenderWorldLocal::BuildConnectedAreas_r( int areaNum ) { 608 if ( tr.viewDef->connectedAreas[areaNum] ) { 609 return; 610 } 611 612 tr.viewDef->connectedAreas[areaNum] = true; 613 614 // flood through all non-blocked portals 615 portalArea_t * area = &portalAreas[ areaNum ]; 616 for ( const portal_t * portal = area->portals; portal; portal = portal->next ) { 617 if ( ( portal->doublePortal->blockingBits & PS_BLOCK_VIEW ) == 0 ) { 618 BuildConnectedAreas_r( portal->intoArea ); 619 } 620 } 621 } 622 623 /* 624 =================== 625 idRenderWorldLocal::BuildConnectedAreas 626 627 This is only valid for a given view, not all views in a frame 628 =================== 629 */ 630 void idRenderWorldLocal::BuildConnectedAreas() { 631 tr.viewDef->connectedAreas = (bool *)R_FrameAlloc( numPortalAreas * sizeof( tr.viewDef->connectedAreas[0] ) ); 632 633 // if we are outside the world, we can see all areas 634 if ( tr.viewDef->areaNum == -1 ) { 635 for ( int i = 0; i < numPortalAreas; i++ ) { 636 tr.viewDef->connectedAreas[i] = true; 637 } 638 return; 639 } 640 641 // start with none visible, and flood fill from the current area 642 memset( tr.viewDef->connectedAreas, 0, numPortalAreas * sizeof( tr.viewDef->connectedAreas[0] ) ); 643 BuildConnectedAreas_r( tr.viewDef->areaNum ); 644 } 645 646 /* 647 ============= 648 idRenderWorldLocal::FindViewLightsAndEntites 649 650 All the modelrefs and lightrefs that are in visible areas 651 will have viewEntitys and viewLights created for them. 652 653 The scissorRects on the viewEntitys and viewLights may be empty if 654 they were considered, but not actually visible. 655 656 Entities and lights can have cached viewEntities / viewLights that 657 will be used if the viewCount variable matches. 658 ============= 659 */ 660 void idRenderWorldLocal::FindViewLightsAndEntities() { 661 SCOPED_PROFILE_EVENT( "FindViewLightsAndEntities" ); 662 663 // bumping this counter invalidates cached viewLights / viewEntities, 664 // when a light or entity is next considered, it will create a new 665 // viewLight / viewEntity 666 tr.viewCount++; 667 668 // clear the visible lightDef and entityDef lists 669 tr.viewDef->viewLights = NULL; 670 tr.viewDef->viewEntitys = NULL; 671 672 // all areas are initially not visible, but each portal 673 // chain that leads to them will expand the visible rectangle 674 for ( int i = 0; i < numPortalAreas; i++ ) { 675 areaScreenRect[i].Clear(); 676 } 677 678 // find the area to start the portal flooding in 679 if ( !r_usePortals.GetBool() ) { 680 // debug tool to force no portal culling 681 tr.viewDef->areaNum = -1; 682 } else { 683 tr.viewDef->areaNum = PointInArea( tr.viewDef->initialViewAreaOrigin ); 684 } 685 686 // determine all possible connected areas for 687 // light-behind-door culling 688 BuildConnectedAreas(); 689 690 // flow through all the portals and add models / lights 691 if ( r_singleArea.GetBool() ) { 692 // if debugging, only mark this area 693 // if we are outside the world, don't draw anything 694 if ( tr.viewDef->areaNum >= 0 ) { 695 static int lastPrintedAreaNum; 696 if ( tr.viewDef->areaNum != lastPrintedAreaNum ) { 697 lastPrintedAreaNum = tr.viewDef->areaNum; 698 common->Printf( "entering portal area %i\n", tr.viewDef->areaNum ); 699 } 700 701 portalStack_t ps; 702 for ( int i = 0; i < 5; i++ ) { 703 ps.portalPlanes[i] = tr.viewDef->frustum[i]; 704 } 705 ps.numPortalPlanes = 5; 706 ps.rect = tr.viewDef->scissor; 707 708 AddAreaToView( tr.viewDef->areaNum, &ps ); 709 } 710 } else { 711 // note that the center of projection for flowing through portals may 712 // be a different point than initialViewAreaOrigin for subviews that 713 // may have the viewOrigin in a solid/invalid area 714 FlowViewThroughPortals( tr.viewDef->renderView.vieworg, 5, tr.viewDef->frustum ); 715 } 716 } 717 718 /* 719 ======================================================================= 720 721 Light linking into the BSP tree by flooding through portals 722 723 ======================================================================= 724 */ 725 726 /* 727 =================== 728 idRenderWorldLocal::FloodLightThroughArea_r 729 =================== 730 */ 731 void idRenderWorldLocal::FloodLightThroughArea_r( idRenderLightLocal *light, int areaNum, 732 const portalStack_t *ps ) { 733 assert( ps != NULL ); // compiler warning 734 portal_t* p = NULL; 735 float d; 736 portalArea_t * area = NULL; 737 const portalStack_t *check = NULL, *firstPortalStack = NULL; 738 portalStack_t newStack; 739 int i, j; 740 idVec3 v1, v2; 741 int addPlanes; 742 idFixedWinding w; // we won't overflow because MAX_PORTAL_PLANES = 20 743 744 area = &portalAreas[ areaNum ]; 745 746 // add an areaRef 747 AddLightRefToArea( light, area ); 748 749 // go through all the portals 750 for ( p = area->portals; p; p = p->next ) { 751 // make sure this portal is facing away from the view 752 d = p->plane.Distance( light->globalLightOrigin ); 753 if ( d < -0.1f ) { 754 continue; 755 } 756 757 // make sure the portal isn't in our stack trace, 758 // which would cause an infinite loop 759 for ( check = ps; check; check = check->next ) { 760 firstPortalStack = check; 761 if ( check->p == p ) { 762 break; // don't recursively enter a stack 763 } 764 } 765 if ( check ) { 766 continue; // already in stack 767 } 768 769 // if we are very close to the portal surface, don't bother clipping 770 // it, which tends to give epsilon problems that make the area vanish 771 if ( d < 1.0f ) { 772 // go through this portal 773 newStack = *ps; 774 newStack.p = p; 775 newStack.next = ps; 776 FloodLightThroughArea_r( light, p->intoArea, &newStack ); 777 continue; 778 } 779 780 // clip the portal winding to all of the planes 781 w = *p->w; 782 for ( j = 0; j < ps->numPortalPlanes; j++ ) { 783 if ( !w.ClipInPlace( -ps->portalPlanes[j], 0 ) ) { 784 break; 785 } 786 } 787 if ( !w.GetNumPoints() ) { 788 continue; // portal not visible 789 } 790 // also always clip to the original light planes, because they aren't 791 // necessarily extending to infinitiy like a view frustum 792 for ( j = 0; j < firstPortalStack->numPortalPlanes; j++ ) { 793 if ( !w.ClipInPlace( -firstPortalStack->portalPlanes[j], 0 ) ) { 794 break; 795 } 796 } 797 if ( !w.GetNumPoints() ) { 798 continue; // portal not visible 799 } 800 801 // go through this portal 802 newStack.p = p; 803 newStack.next = ps; 804 805 // generate a set of clipping planes that will further restrict 806 // the visible view beyond just the scissor rect 807 808 addPlanes = w.GetNumPoints(); 809 if ( addPlanes > MAX_PORTAL_PLANES ) { 810 addPlanes = MAX_PORTAL_PLANES; 811 } 812 813 newStack.numPortalPlanes = 0; 814 for ( i = 0; i < addPlanes; i++ ) { 815 j = i+1; 816 if ( j == w.GetNumPoints() ) { 817 j = 0; 818 } 819 820 v1 = light->globalLightOrigin - w[i].ToVec3(); 821 v2 = light->globalLightOrigin - w[j].ToVec3(); 822 823 newStack.portalPlanes[newStack.numPortalPlanes].Normal().Cross( v2, v1 ); 824 825 // if it is degenerate, skip the plane 826 if ( newStack.portalPlanes[newStack.numPortalPlanes].Normalize() < 0.01f ) { 827 continue; 828 } 829 newStack.portalPlanes[newStack.numPortalPlanes].FitThroughPoint( light->globalLightOrigin ); 830 831 newStack.numPortalPlanes++; 832 } 833 834 FloodLightThroughArea_r( light, p->intoArea, &newStack ); 835 } 836 } 837 838 /* 839 ======================= 840 idRenderWorldLocal::FlowLightThroughPortals 841 842 Adds an arearef in each area that the light center flows into. 843 This can only be used for shadow casting lights that have a generated 844 prelight, because shadows are cast from back side which may not be in visible areas. 845 ======================= 846 */ 847 void idRenderWorldLocal::FlowLightThroughPortals( idRenderLightLocal *light ) { 848 // if the light origin areaNum is not in a valid area, 849 // the light won't have any area refs 850 if ( light->areaNum == -1 ) { 851 return; 852 } 853 854 idPlane frustumPlanes[6]; 855 idRenderMatrix::GetFrustumPlanes( frustumPlanes, light->baseLightProject, true, true ); 856 857 portalStack_t ps; 858 memset( &ps, 0, sizeof( ps ) ); 859 ps.numPortalPlanes = 6; 860 for ( int i = 0; i < 6; i++ ) { 861 ps.portalPlanes[i] = -frustumPlanes[i]; 862 } 863 864 FloodLightThroughArea_r( light, light->areaNum, &ps ); 865 } 866 867 /* 868 ======================================================================= 869 870 Portal State Management 871 872 ======================================================================= 873 */ 874 875 /* 876 ============== 877 NumPortals 878 ============== 879 */ 880 int idRenderWorldLocal::NumPortals() const { 881 return numInterAreaPortals; 882 } 883 884 /* 885 ============== 886 FindPortal 887 888 Game code uses this to identify which portals are inside doors. 889 Returns 0 if no portal contacts the bounds 890 ============== 891 */ 892 qhandle_t idRenderWorldLocal::FindPortal( const idBounds &b ) const { 893 int i, j; 894 idBounds wb; 895 doublePortal_t *portal; 896 idWinding *w; 897 898 for ( i = 0; i < numInterAreaPortals; i++ ) { 899 portal = &doublePortals[i]; 900 w = portal->portals[0]->w; 901 902 wb.Clear(); 903 for ( j = 0; j < w->GetNumPoints(); j++ ) { 904 wb.AddPoint( (*w)[j].ToVec3() ); 905 } 906 if ( wb.IntersectsBounds( b ) ) { 907 return i + 1; 908 } 909 } 910 911 return 0; 912 } 913 914 /* 915 ============= 916 FloodConnectedAreas 917 ============= 918 */ 919 void idRenderWorldLocal::FloodConnectedAreas( portalArea_t *area, int portalAttributeIndex ) { 920 if ( area->connectedAreaNum[portalAttributeIndex] == connectedAreaNum ) { 921 return; 922 } 923 area->connectedAreaNum[portalAttributeIndex] = connectedAreaNum; 924 925 for ( portal_t *p = area->portals; p != NULL; p = p->next ) { 926 if ( !(p->doublePortal->blockingBits & (1<<portalAttributeIndex) ) ) { 927 FloodConnectedAreas( &portalAreas[p->intoArea], portalAttributeIndex ); 928 } 929 } 930 } 931 932 /* 933 ============== 934 AreasAreConnected 935 ============== 936 */ 937 bool idRenderWorldLocal::AreasAreConnected( int areaNum1, int areaNum2, portalConnection_t connection ) const { 938 if ( areaNum1 == -1 || areaNum2 == -1 ) { 939 return false; 940 } 941 if ( areaNum1 > numPortalAreas || areaNum2 > numPortalAreas || areaNum1 < 0 || areaNum2 < 0 ) { 942 common->Error( "idRenderWorldLocal::AreAreasConnected: bad parms: %i, %i", areaNum1, areaNum2 ); 943 } 944 945 int attribute = 0; 946 947 int intConnection = (int)connection; 948 949 while ( intConnection > 1 ) { 950 attribute++; 951 intConnection >>= 1; 952 } 953 if ( attribute >= NUM_PORTAL_ATTRIBUTES || ( 1 << attribute ) != (int)connection ) { 954 common->Error( "idRenderWorldLocal::AreasAreConnected: bad connection number: %i\n", (int)connection ); 955 } 956 957 return portalAreas[areaNum1].connectedAreaNum[attribute] == portalAreas[areaNum2].connectedAreaNum[attribute]; 958 } 959 960 /* 961 ============== 962 SetPortalState 963 964 doors explicitly close off portals when shut 965 ============== 966 */ 967 void idRenderWorldLocal::SetPortalState( qhandle_t portal, int blockTypes ) { 968 if ( portal == 0 ) { 969 return; 970 } 971 972 if ( portal < 1 || portal > numInterAreaPortals ) { 973 common->Error( "SetPortalState: bad portal number %i", portal ); 974 } 975 int old = doublePortals[portal-1].blockingBits; 976 if ( old == blockTypes ) { 977 return; 978 } 979 doublePortals[portal-1].blockingBits = blockTypes; 980 981 // leave the connectedAreaGroup the same on one side, 982 // then flood fill from the other side with a new number for each changed attribute 983 for ( int i = 0; i < NUM_PORTAL_ATTRIBUTES; i++ ) { 984 if ( ( old ^ blockTypes ) & ( 1 << i ) ) { 985 connectedAreaNum++; 986 FloodConnectedAreas( &portalAreas[doublePortals[portal-1].portals[1]->intoArea], i ); 987 } 988 } 989 990 if ( common->WriteDemo() ) { 991 common->WriteDemo()->WriteInt( DS_RENDER ); 992 common->WriteDemo()->WriteInt( DC_SET_PORTAL_STATE ); 993 common->WriteDemo()->WriteInt( portal ); 994 common->WriteDemo()->WriteInt( blockTypes ); 995 } 996 } 997 998 /* 999 ============== 1000 GetPortalState 1001 ============== 1002 */ 1003 int idRenderWorldLocal::GetPortalState( qhandle_t portal ) { 1004 if ( portal == 0 ) { 1005 return 0; 1006 } 1007 1008 if ( portal < 1 || portal > numInterAreaPortals ) { 1009 common->Error( "GetPortalState: bad portal number %i", portal ); 1010 } 1011 1012 return doublePortals[portal-1].blockingBits; 1013 } 1014 1015 /* 1016 ===================== 1017 idRenderWorldLocal::ShowPortals 1018 1019 Debugging tool, won't work correctly with SMP or when mirrors are present 1020 ===================== 1021 */ 1022 void idRenderWorldLocal::ShowPortals() { 1023 int i, j; 1024 portalArea_t *area; 1025 portal_t *p; 1026 idWinding *w; 1027 1028 // flood out through portals, setting area viewCount 1029 for ( i = 0; i < numPortalAreas; i++ ) { 1030 area = &portalAreas[i]; 1031 if ( area->viewCount != tr.viewCount ) { 1032 continue; 1033 } 1034 for ( p = area->portals; p; p = p->next ) { 1035 w = p->w; 1036 if ( !w ) { 1037 continue; 1038 } 1039 1040 if ( portalAreas[ p->intoArea ].viewCount != tr.viewCount ) { 1041 // red = can't see 1042 GL_Color( 1, 0, 0 ); 1043 } else { 1044 // green = see through 1045 GL_Color( 0, 1, 0 ); 1046 } 1047 1048 qglBegin( GL_LINE_LOOP ); 1049 for ( j = 0; j < w->GetNumPoints(); j++ ) { 1050 qglVertex3fv( (*w)[j].ToFloatPtr() ); 1051 } 1052 qglEnd(); 1053 } 1054 } 1055 }