sceneCullingState.cpp
Engine/source/scene/culling/sceneCullingState.cpp
Public Variables
bool
For frame signal.
Detailed Description
Public Variables
bool gEditingMission
For frame signal.
1 2//----------------------------------------------------------------------------- 3// Copyright (c) 2012 GarageGames, LLC 4// 5// Permission is hereby granted, free of charge, to any person obtaining a copy 6// of this software and associated documentation files (the "Software"), to 7// deal in the Software without restriction, including without limitation the 8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9// sell copies of the Software, and to permit persons to whom the Software is 10// furnished to do so, subject to the following conditions: 11// 12// The above copyright notice and this permission notice shall be included in 13// all copies or substantial portions of the Software. 14// 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21// IN THE SOFTWARE. 22//----------------------------------------------------------------------------- 23 24#include "platform/platform.h" 25#include "scene/culling/sceneCullingState.h" 26 27#include "scene/sceneManager.h" 28#include "scene/sceneObject.h" 29#include "scene/zones/sceneZoneSpace.h" 30#include "math/mathUtils.h" 31#include "platform/profiler.h" 32#include "terrain/terrData.h" 33#include "util/tempAlloc.h" 34#include "gfx/sim/debugDraw.h" 35 36 37extern bool gEditingMission; 38 39 40bool SceneCullingState::smDisableTerrainOcclusion = true; 41bool SceneCullingState::smDisableZoneCulling = false; 42U32 SceneCullingState::smMaxOccludersPerZone = 4; 43F32 SceneCullingState::smOccluderMinWidthPercentage = 0.1f; 44F32 SceneCullingState::smOccluderMinHeightPercentage = 0.1f; 45 46 47 48//----------------------------------------------------------------------------- 49 50SceneCullingState::SceneCullingState( SceneManager* sceneManager, const SceneCameraState& viewState ) 51 : mSceneManager( sceneManager ), 52 mCameraState( viewState ), 53 mDisableTerrainOcclusion( smDisableTerrainOcclusion ), 54 mDisableZoneCulling( smDisableZoneCulling ) 55{ 56 AssertFatal( sceneManager->getZoneManager(), "SceneCullingState::SceneCullingState - SceneManager must have a zone manager!" ); 57 58 VECTOR_SET_ASSOCIATION( mZoneStates ); 59 VECTOR_SET_ASSOCIATION( mAddedOccluderObjects ); 60 61 // Allocate zone states. 62 63 const U32 numZones = sceneManager->getZoneManager()->getNumZones(); 64 mZoneStates.setSize( numZones ); 65 dMemset( mZoneStates.address(), 0, sizeof( SceneZoneCullingState ) * numZones ); 66 67 // Allocate the zone visibility flags. 68 69 mZoneVisibilityFlags.setSize( numZones ); 70 mZoneVisibilityFlags.clear(); 71 72 // Culling frustum 73 74 mCullingFrustum = mCameraState.getFrustum(); 75 mCullingFrustum.bakeProjectionOffset(); 76 77 // Construct the root culling volume from 78 // the culling frustum. Omit the frustum's 79 // near and far plane so we don't test it repeatedly. 80 81 PlaneF* planes = allocateData< PlaneF >( 4 ); 82 83 planes[ 0 ] = mCullingFrustum.getPlanes()[ Frustum::PlaneLeft ]; 84 planes[ 1 ] = mCullingFrustum.getPlanes()[ Frustum::PlaneRight ]; 85 planes[ 2 ] = mCullingFrustum.getPlanes()[ Frustum::PlaneTop]; 86 planes[ 3 ] = mCullingFrustum.getPlanes()[ Frustum::PlaneBottom ]; 87 88 mRootVolume = SceneCullingVolume( 89 SceneCullingVolume::Includer, 90 PlaneSetF( planes, 4 ) 91 ); 92 93 clearExtraPlanesCull(); 94} 95 96//----------------------------------------------------------------------------- 97 98bool SceneCullingState::isWithinVisibleZone( SceneObject* object ) const 99{ 100 for( SceneObject::ZoneRef* ref = object->_getZoneRefHead(); 101 ref != NULL; ref = ref->nextInObj ) 102 if( mZoneVisibilityFlags.test( ref->zone ) ) 103 return true; 104 105 return false; 106} 107 108//----------------------------------------------------------------------------- 109 110void SceneCullingState::addOccluder( SceneObject* object ) 111{ 112 PROFILE_SCOPE( SceneCullingState_addOccluder ); 113 114 // If the occluder is itself occluded, don't add it. 115 // 116 // NOTE: We do allow near plane intersections here. Silhouette extraction 117 // should take that into account. 118 119 if( cullObjects( &object, 1, DontCullRenderDisabled ) != 1 ) 120 return; 121 122 // If the occluder has already been added, do nothing. Check this 123 // after the culling check since the same occluder can be added by 124 // two separate zones and not be visible in one yet be visible in the 125 // other. 126 127 if( mAddedOccluderObjects.contains( object ) ) 128 return; 129 mAddedOccluderObjects.push_back( object ); 130 131 // Let the object build a silhouette. If it doesn't 132 // return one, abort. 133 134 Vector< Point3F> silhouette; 135 object->buildSilhouette( getCameraState(), silhouette ); 136 137 if( silhouette.empty() || silhouette.size() < 3 ) 138 return; 139 140 // Generate the culling volume. 141 142 SceneCullingVolume volume; 143 if( !createCullingVolume( 144 silhouette.address(), 145 silhouette.size(), 146 SceneCullingVolume::Occluder, 147 volume ) ) 148 return; 149 150 // Add the frustum to all zones that the object is assigned to. 151 152 for( SceneObject::ZoneRef* ref = object->_getZoneRefHead(); ref != NULL; ref = ref->nextInObj ) 153 addCullingVolumeToZone( ref->zone, volume ); 154} 155 156//----------------------------------------------------------------------------- 157 158bool SceneCullingState::addCullingVolumeToZone( U32 zoneId, const SceneCullingVolume& volume ) 159{ 160 PROFILE_SCOPE( SceneCullingState_addCullingVolumeToZone ); 161 162 AssertFatal( zoneId < mZoneStates.size(), "SceneCullingState::addCullingVolumeToZone - Zone ID out of range" ); 163 SceneZoneCullingState& zoneState = mZoneStates[ zoneId ]; 164 165 // [rene, 07-Apr-10] I previously used to attempt to merge things here and detect whether 166 // the visibility state of the zone has changed at all. Since we allow polyhedra to be 167 // degenerate here and since polyhedra cannot be merged easily like frustums, I have opted 168 // to remove this for now. I'm also convinced that with the current traversal system it 169 // adds little benefit. 170 171 // Link the volume to the zone state. 172 173 typedef SceneZoneCullingState::CullingVolumeLink LinkType; 174 175 LinkType* link = reinterpret_cast< LinkType* >( allocateData( sizeof( LinkType ) ) ); 176 177 link->mVolume = volume; 178 link->mNext = zoneState.mCullingVolumes; 179 zoneState.mCullingVolumes = link; 180 181 if( volume.isOccluder() ) 182 zoneState.mHaveOccluders = true; 183 else 184 zoneState.mHaveIncluders = true; 185 186 // Mark sorting state as dirty. 187 188 zoneState.mHaveSortedVolumes = false; 189 190 // Set the visibility flag for the zone. 191 192 if( volume.isIncluder() ) 193 mZoneVisibilityFlags.set( zoneId ); 194 195 return true; 196} 197 198//----------------------------------------------------------------------------- 199 200bool SceneCullingState::addCullingVolumeToZone( U32 zoneId, SceneCullingVolume::Type type, const AnyPolyhedron& polyhedron ) 201{ 202 // Allocate space on our chunker. 203 204 const U32 numPlanes = polyhedron.getNumPlanes(); 205 PlaneF* planes = allocateData< PlaneF >( numPlanes ); 206 207 // Copy the planes over. 208 209 dMemcpy( planes, polyhedron.getPlanes(), numPlanes * sizeof( planes[ 0 ] ) ); 210 211 // Create a culling volume. 212 213 SceneCullingVolume volume( 214 type, 215 PlaneSetF( planes, numPlanes ) 216 ); 217 218 // And add it. 219 220 return addCullingVolumeToZone( zoneId, volume ); 221} 222 223//----------------------------------------------------------------------------- 224 225bool SceneCullingState::createCullingVolume( const Point3F* vertices, U32 numVertices, SceneCullingVolume::Type type, SceneCullingVolume& outVolume ) 226{ 227 const Point3F& viewPos = getCameraState().getViewPosition(); 228 const Point3F& viewDir = getCameraState().getViewDirection(); 229 const bool isOrtho = getCullingFrustum().isOrtho(); 230 231 //TODO: check if we need to handle penetration of the near plane for occluders specially 232 233 // Allocate space for the clipping planes we generate. Assume the worst case 234 // of every edge generating a plane and, for includers, all edges meeting at 235 // steep angles so we need to insert extra planes (the latter is not possible, 236 // of course, but it makes things less complicated here). For occluders, add 237 // an extra plane for the near cap. 238 239 const U32 maxPlanes = ( type == SceneCullingVolume::Occluder ? numVertices + 1 : numVertices * 2 ); 240 PlaneF* planes = allocateData< PlaneF >( maxPlanes ); 241 242 // Keep track of the world-space bounds of the polygon. We use this later 243 // to derive some metrics. 244 245 Box3F wsPolyBounds; 246 247 wsPolyBounds.minExtents = Point3F( TypeTraits< F32 >::MAX, TypeTraits< F32 >::MAX, TypeTraits< F32 >::MAX ); 248 wsPolyBounds.maxExtents = Point3F( TypeTraits< F32 >::MIN, TypeTraits< F32 >::MIN, TypeTraits< F32 >::MIN ); 249 250 // For occluders, also keep track of the nearest, and two farthest silhouette points. We use 251 // this later to construct a near capping plane. 252 F32 minVertexDistanceSquared = TypeTraits< F32 >::MAX; 253 U32 leastDistantVert = 0; 254 255 F32 maxVertexDistancesSquared[ 2 ] = { TypeTraits< F32 >::MIN, TypeTraits< F32 >::MIN }; 256 U32 mostDistantVertices[ 2 ] = { 0, 0 }; 257 258 // Generate the extrusion volume. For orthographic projections, extrude 259 // parallel to the view direction whereas for parallel projections, extrude 260 // from the viewpoint. 261 262 U32 numPlanes = 0; 263 U32 lastVertex = numVertices - 1; 264 bool invert = false; 265 266 for( U32 i = 0; i < numVertices; lastVertex = i, ++ i ) 267 { 268 AssertFatal( numPlanes < maxPlanes, "SceneCullingState::createCullingVolume - Did not allocate enough planes!" ); 269 270 const Point3F& v1 = vertices[ i ]; 271 const Point3F& v2 = vertices[ lastVertex ]; 272 273 // Keep track of bounds. 274 275 wsPolyBounds.minExtents.setMin( v1 ); 276 wsPolyBounds.maxExtents.setMax( v1 ); 277 278 // Skip the edge if it's length is really short. 279 280 const Point3F edgeVector = v2 - v1; 281 const F32 edgeVectorLenSquared = edgeVector.lenSquared(); 282 if( edgeVectorLenSquared < 0.025f ) 283 continue; 284 285 //TODO: might need to do additional checks here for non-planar polygons used by occluders 286 //TODO: test for colinearity of edge vector with view vector (occluders only) 287 288 // Create a plane for the edge. 289 290 if( isOrtho ) 291 { 292 // Compute a plane through the two edge vertices and one 293 // of the vertices extended along the view direction. 294 295 if( !invert ) 296 planes[ numPlanes ] = PlaneF( v1, v1 + viewDir, v2 ); 297 else 298 planes[ numPlanes ] = PlaneF( v2, v1 + viewDir, v1 ); 299 } 300 else 301 { 302 // Compute a plane going through the viewpoint and the two 303 // edge vertices. 304 305 if( !invert ) 306 planes[ numPlanes ] = PlaneF( v1, viewPos, v2 ); 307 else 308 planes[ numPlanes ] = PlaneF( v2, viewPos, v1 ); 309 } 310 311 numPlanes ++; 312 313 // If this is the first plane that we have created, find out whether 314 // the vertex ordering is giving us the plane orientations that we want 315 // (facing inside). If not, invert vertex order from now on. 316 317 if( numPlanes == 1 ) 318 { 319 Point3F center( 0, 0, 0 ); 320 for( U32 n = 0; n < numVertices; ++ n ) 321 center += vertices[n]; 322 center /= numVertices; 323 324 if( planes[numPlanes - 1].whichSide( center ) == PlaneF::Back ) 325 { 326 invert = true; 327 planes[ numPlanes - 1 ].invert(); 328 } 329 } 330 331 // For occluders, keep tabs of the nearest, and two farthest vertices. 332 333 if( type == SceneCullingVolume::Occluder ) 334 { 335 const F32 distSquared = ( v1 - viewPos ).lenSquared(); 336 if( distSquared < minVertexDistanceSquared ) 337 { 338 minVertexDistanceSquared = distSquared; 339 leastDistantVert = i; 340 } 341 if( distSquared > maxVertexDistancesSquared[ 0 ] ) 342 { 343 // Move 0 to 1. 344 maxVertexDistancesSquared[ 1 ] = maxVertexDistancesSquared[ 0 ]; 345 mostDistantVertices[ 1 ] = mostDistantVertices[ 0 ]; 346 347 // Replace 0. 348 maxVertexDistancesSquared[ 0 ] = distSquared; 349 mostDistantVertices[ 0 ] = i; 350 } 351 else if( distSquared > maxVertexDistancesSquared[ 1 ] ) 352 { 353 // Replace 1. 354 maxVertexDistancesSquared[ 1 ] = distSquared; 355 mostDistantVertices[ 1 ] = i; 356 } 357 } 358 } 359 360 // If the extrusion produced no useful result, abort. 361 362 if( numPlanes < 3 ) 363 return false; 364 365 // For includers, test the angle of the edges at the current vertex. 366 // If too steep, add an extra plane to improve culling efficiency. 367 368 if( false )//type == SceneCullingVolume::Includer ) 369 { 370 const U32 numOriginalPlanes = numPlanes; 371 U32 lastPlaneIndex = numPlanes - 1; 372 373 for( U32 i = 0; i < numOriginalPlanes; lastPlaneIndex = i, ++ i ) 374 { 375 const PlaneF& currentPlane = planes[ i ]; 376 const PlaneF& lastPlane = planes[ lastPlaneIndex ]; 377 378 // Compute the cosine of the angle between the two plane normals. 379 380 const F32 cosAngle = mFabs( mDot( currentPlane, lastPlane ) ); 381 382 // The planes meet at increasingly steep angles the more they point 383 // in opposite directions, i.e the closer the angle of their normals 384 // is to 180 degrees. Skip any two planes that don't get near that. 385 386 if( cosAngle > 0.1f ) 387 continue; 388 389 Point3F newNormal = currentPlane + lastPlane;//addNormals - mDot( addNormals, crossNormals ) * crossNormals; 390 391 // 392 393 planes[ numPlanes ] = PlaneF( currentPlane.getPosition(), newNormal ); 394 numPlanes ++; 395 } 396 } 397 398 // Compute the metrics of the culling volume in relation to the view frustum. 399 // 400 // For this, we are short-circuiting things slightly. The correct way (other than doing 401 // full screen projections) would be to transform all the polygon points into camera 402 // space, lay an AABB around those points, and then find the X and Z extents on the near plane. 403 // 404 // However, while not as accurate, a faster way is to just project the axial vectors 405 // of the bounding box onto both the camera right and up vector. This gives us a rough 406 // estimate of the camera-space size of the polygon we're looking at. 407 408 const MatrixF& cameraTransform = getCameraState().getViewWorldMatrix(); 409 const Point3F cameraRight = cameraTransform.getRightVector(); 410 const Point3F cameraUp = cameraTransform.getUpVector(); 411 412 const Point3F wsPolyBoundsExtents = wsPolyBounds.getExtents(); 413 414 F32 widthEstimate = 415 getMax( mFabs( wsPolyBoundsExtents.x * cameraRight.x ), 416 getMax( mFabs( wsPolyBoundsExtents.y * cameraRight.y ), 417 mFabs( wsPolyBoundsExtents.z * cameraRight.z ) ) ); 418 419 F32 heightEstimate = 420 getMax( mFabs( wsPolyBoundsExtents.x * cameraUp.x ), 421 getMax( mFabs( wsPolyBoundsExtents.y * cameraUp.y ), 422 mFabs( wsPolyBoundsExtents.z * cameraUp.z ) ) ); 423 424 // If the current camera is a perspective one, divide the two estimates 425 // by the distance of the nearest bounding box vertex to the camera 426 // to account for perspective distortion. 427 428 if( !isOrtho ) 429 { 430 const Point3F nearestVertex = wsPolyBounds.computeVertex( 431 Box3F::getPointIndexFromOctant( - viewDir ) 432 ); 433 434 const F32 distance = ( nearestVertex - viewPos ).len(); 435 436 widthEstimate /= distance; 437 heightEstimate /= distance; 438 } 439 440 // If we are creating an occluder, check to see if the estimates fit 441 // our minimum requirements. 442 443 if( type == SceneCullingVolume::Occluder ) 444 { 445 const F32 widthEstimatePercentage = widthEstimate / getCullingFrustum().getWidth(); 446 const F32 heightEstimatePercentage = heightEstimate / getCullingFrustum().getHeight(); 447 448 if( widthEstimatePercentage < smOccluderMinWidthPercentage || 449 heightEstimatePercentage < smOccluderMinHeightPercentage ) 450 return false; // Reject. 451 } 452 453 // Use the area estimate as the volume's sort point. 454 455 const F32 sortPoint = widthEstimate * heightEstimate; 456 457 // Finally, if it's an occluder, compute a near cap. The near cap prevents objects 458 // in front of the occluder from testing positive. The same could be achieved by 459 // manually comparing distances before testing objects but since that would amount 460 // to the same checks the plane/AABB tests do, it's easier to just add another plane. 461 // Additionally, it gives the benefit of being able to create more precise culling 462 // results by angling the plane. 463 464 //NOTE: Could consider adding a near cap for includers too when generating a volume 465 // for the outdoor zone as that may prevent quite a bit of space from being included. 466 // However, given that this space will most likely just be filled with interior 467 // stuff anyway, it's probably not worth it. 468 469 if( type == SceneCullingVolume::Occluder ) 470 { 471 const U32 nearCapIndex = numPlanes; 472 planes[ nearCapIndex ] = PlaneF( 473 vertices[ mostDistantVertices[ 0 ] ], 474 vertices[ mostDistantVertices[ 1 ] ], 475 vertices[ leastDistantVert ] ); 476 477 // Invert the plane, if necessary. 478 if( planes[ nearCapIndex ].whichSide( viewPos ) == PlaneF::Front ) 479 planes[ nearCapIndex ].invert(); 480 481 numPlanes ++; 482 } 483 484 // Create the volume from the planes. 485 486 outVolume = SceneCullingVolume( 487 type, 488 PlaneSetF( planes, numPlanes ) 489 ); 490 outVolume.setSortPoint( sortPoint ); 491 492 // Done. 493 494 return true; 495} 496 497//----------------------------------------------------------------------------- 498 499namespace { 500 struct ZoneArrayIterator 501 { 502 U32 mCurrent; 503 U32 mNumZones; 504 const U32* mZones; 505 506 ZoneArrayIterator( const U32* zones, U32 numZones ) 507 : mCurrent( 0 ), 508 mNumZones( numZones ), 509 mZones( zones ) {} 510 511 bool isValid() const 512 { 513 return ( mCurrent < mNumZones ); 514 } 515 ZoneArrayIterator& operator ++() 516 { 517 mCurrent ++; 518 return *this; 519 } 520 U32 operator*() const 521 { 522 return mZones[ mCurrent ]; 523 } 524 }; 525} 526 527template< typename T, typename Iter > 528inline SceneZoneCullingState::CullingTestResult SceneCullingState::_testOccludersOnly( const T& bounds, Iter zoneIter ) const 529{ 530 // Test the culling states of all zones that the object 531 // is assigned to. 532 533 for( ; zoneIter.isValid(); ++ zoneIter ) 534 { 535 const SceneZoneCullingState& zoneState = getZoneState( *zoneIter ); 536 537 // Skip zone if there are no occluders. 538 539 if( !zoneState.hasOccluders() ) 540 continue; 541 542 // If the object's world bounds overlaps any of the volumes 543 // for this zone, it's rendered. 544 545 if( zoneState.testVolumes( bounds, true ) == SceneZoneCullingState::CullingTestPositiveByOcclusion ) 546 return SceneZoneCullingState::CullingTestPositiveByOcclusion; 547 } 548 549 return SceneZoneCullingState::CullingTestNegative; 550} 551 552template< typename T, typename Iter > 553inline SceneZoneCullingState::CullingTestResult SceneCullingState::_test( const T& bounds, Iter zoneIter, 554 const PlaneF& nearPlane, const PlaneF& farPlane ) const 555{ 556 // Defer test of near and far plane until we've hit a zone 557 // which actually has visible space. This prevents us from 558 // doing near/far tests on objects that were included in the 559 // potential render list but aren't actually in any visible 560 // zone. 561 bool haveTestedNearAndFar = false; 562 563 // Test the culling states of all zones that the object 564 // is assigned to. 565 566 for( ; zoneIter.isValid(); ++ zoneIter ) 567 { 568 const SceneZoneCullingState& zoneState = getZoneState( *zoneIter ); 569 570 // Skip zone if there are no positive culling volumes. 571 572 if( !zoneState.hasIncluders() ) 573 continue; 574 575 // If we haven't tested the near and far plane yet, do so 576 // now. 577 578 if( !haveTestedNearAndFar ) 579 { 580 // Test near plane. 581 582 PlaneF::Side nearSide = nearPlane.whichSide( bounds ); 583 if( nearSide == PlaneF::Back ) 584 return SceneZoneCullingState::CullingTestNegative; 585 586 // Test far plane. 587 588 PlaneF::Side farSide = farPlane.whichSide( bounds ); 589 if( farSide == PlaneF::Back ) 590 return SceneZoneCullingState::CullingTestNegative; 591 592 haveTestedNearAndFar = true; 593 } 594 595 // If the object's world bounds overlaps any of the volumes 596 // for this zone, it's rendered. 597 598 SceneZoneCullingState::CullingTestResult result = zoneState.testVolumes( bounds ); 599 600 if( result == SceneZoneCullingState::CullingTestPositiveByInclusion ) 601 return result; 602 else if( result == SceneZoneCullingState::CullingTestPositiveByOcclusion ) 603 return result; 604 } 605 606 return SceneZoneCullingState::CullingTestNegative; 607} 608 609//----------------------------------------------------------------------------- 610 611template< bool OCCLUDERS_ONLY, typename T > 612inline SceneZoneCullingState::CullingTestResult SceneCullingState::_test( const T& bounds, const U32* zones, U32 numZones ) const 613{ 614 // If zone culling is disabled, only test against 615 // the root frustum. 616 617 if( disableZoneCulling() ) 618 { 619 if( !OCCLUDERS_ONLY && !getCullingFrustum().isCulled( bounds ) ) 620 return SceneZoneCullingState::CullingTestPositiveByInclusion; 621 622 return SceneZoneCullingState::CullingTestNegative; 623 } 624 625 // Otherwise test each of the zones. 626 627 if( OCCLUDERS_ONLY ) 628 { 629 return _testOccludersOnly( 630 bounds, 631 ZoneArrayIterator( zones, numZones ) 632 ); 633 } 634 else 635 { 636 const PlaneF* frustumPlanes = getCullingFrustum().getPlanes(); 637 638 return _test( 639 bounds, 640 ZoneArrayIterator( zones, numZones ), 641 frustumPlanes[ Frustum::PlaneNear ], 642 frustumPlanes[ Frustum::PlaneFar ] 643 ); 644 } 645} 646 647//----------------------------------------------------------------------------- 648 649bool SceneCullingState::isCulled( const Box3F& aabb, const U32* zones, U32 numZones ) const 650{ 651 SceneZoneCullingState::CullingTestResult result = _test< false >( aabb, zones, numZones ); 652 return ( result == SceneZoneCullingState::CullingTestNegative || 653 result == SceneZoneCullingState::CullingTestPositiveByOcclusion ); 654} 655 656//----------------------------------------------------------------------------- 657 658bool SceneCullingState::isCulled( const OrientedBox3F& obb, const U32* zones, U32 numZones ) const 659{ 660 SceneZoneCullingState::CullingTestResult result = _test< false >( obb, zones, numZones ); 661 return ( result == SceneZoneCullingState::CullingTestNegative || 662 result == SceneZoneCullingState::CullingTestPositiveByOcclusion ); 663} 664 665//----------------------------------------------------------------------------- 666 667bool SceneCullingState::isCulled( const SphereF& sphere, const U32* zones, U32 numZones ) const 668{ 669 SceneZoneCullingState::CullingTestResult result = _test< false >( sphere, zones, numZones ); 670 return ( result == SceneZoneCullingState::CullingTestNegative || 671 result == SceneZoneCullingState::CullingTestPositiveByOcclusion ); 672} 673 674//----------------------------------------------------------------------------- 675 676bool SceneCullingState::isOccluded( SceneObject* object ) const 677{ 678 if( disableZoneCulling() ) 679 return false; 680 681 CullingTestResult result = _testOccludersOnly( 682 object->getWorldBox(), 683 SceneObject::ObjectZonesIterator( object ) 684 ); 685 686 return ( result == SceneZoneCullingState::CullingTestPositiveByOcclusion ); 687} 688 689//----------------------------------------------------------------------------- 690 691bool SceneCullingState::isOccluded( const Box3F& aabb, const U32* zones, U32 numZones ) const 692{ 693 return ( _test< true >( aabb, zones, numZones ) == SceneZoneCullingState::CullingTestPositiveByOcclusion ); 694} 695 696//----------------------------------------------------------------------------- 697 698bool SceneCullingState::isOccluded( const OrientedBox3F& obb, const U32* zones, U32 numZones ) const 699{ 700 return ( _test< true >( obb, zones, numZones ) == SceneZoneCullingState::CullingTestPositiveByOcclusion ); 701} 702 703//----------------------------------------------------------------------------- 704 705bool SceneCullingState::isOccluded( const SphereF& sphere, const U32* zones, U32 numZones ) const 706{ 707 return ( _test< true >( sphere, zones, numZones ) == SceneZoneCullingState::CullingTestPositiveByOcclusion ); 708} 709 710//----------------------------------------------------------------------------- 711 712U32 SceneCullingState::cullObjects( SceneObject** objects, U32 numObjects, U32 cullOptions ) const 713{ 714 PROFILE_SCOPE( SceneCullingState_cullObjects ); 715 716 U32 numRemainingObjects = 0; 717 718 // We test near and far planes separately in order to not do the tests 719 // repeatedly, so fetch the planes now. 720 const PlaneF& nearPlane = getCullingFrustum().getPlanes()[ Frustum::PlaneNear ]; 721 const PlaneF& farPlane = getCullingFrustum().getPlanes()[ Frustum::PlaneFar ]; 722 723 for( U32 i = 0; i < numObjects; ++ i ) 724 { 725 SceneObject* object = objects[ i ]; 726 bool isCulled = true; 727 728 // If we should respect editor overrides, test that now. 729 730 if( !( cullOptions & CullEditorOverrides ) && 731 gEditingMission && 732 ( ( object->isCullingDisabledInEditor() && object->isRenderEnabled() ) || object->isSelected() ) ) 733 { 734 isCulled = false; 735 } 736 737 // If the object is render-disabled, it gets culled. The only 738 // way around this is the editor override above. 739 740 else if( !( cullOptions & DontCullRenderDisabled ) && 741 !object->isRenderEnabled() ) 742 { 743 isCulled = true; 744 } 745 746 // Global bounds objects are never culled. Note that this means 747 // that if these objects are to respect zoning, they need to manually 748 // trigger the respective culling checks for whatever they want to 749 // batch. 750 751 else if( object->isGlobalBounds() ) 752 isCulled = false; 753 754 // If terrain occlusion checks are enabled, run them now. 755 756 else if( !mDisableTerrainOcclusion && 757 object->getWorldBox().minExtents.x > -1e5 && 758 isOccludedByTerrain( object ) ) 759 { 760 // Occluded by terrain. 761 isCulled = true; 762 } 763 764 // If the object shouldn't be subjected to more fine-grained culling 765 // or if zone culling is disabled, just test against the root frustum. 766 767 else if( !( object->getTypeMask() & CULLING_INCLUDE_TYPEMASK ) || 768 ( object->getTypeMask() & CULLING_EXCLUDE_TYPEMASK ) || 769 disableZoneCulling() ) 770 { 771 isCulled = getCullingFrustum().isCulled( object->getWorldBox() ); 772 } 773 774 // Go through the zones that the object is assigned to and 775 // test the object against the frustums of each of the zones. 776 777 else 778 { 779 CullingTestResult result = _test( 780 object->getWorldBox(), 781 SceneObject::ObjectZonesIterator( object ), 782 nearPlane, 783 farPlane 784 ); 785 786 isCulled = ( result == SceneZoneCullingState::CullingTestNegative || 787 result == SceneZoneCullingState::CullingTestPositiveByOcclusion ); 788 } 789 790 if( !isCulled ) 791 isCulled = isOccludedWithExtraPlanesCull( object->getWorldBox() ); 792 793 if( !isCulled ) 794 objects[ numRemainingObjects ++ ] = object; 795 } 796 797 return numRemainingObjects; 798} 799 800//----------------------------------------------------------------------------- 801 802bool SceneCullingState::isOccludedByTerrain( SceneObject* object ) const 803{ 804 PROFILE_SCOPE( SceneCullingState_isOccludedByTerrain ); 805 806 // Don't try to occlude globally bounded objects. 807 if( object->isGlobalBounds() ) 808 return false; 809 810 const Vector< SceneObject*>& terrains = getSceneManager()->getContainer()->getTerrains(); 811 const U32 numTerrains = terrains.size(); 812 813 for( U32 terrainIdx = 0; terrainIdx < numTerrains; ++ terrainIdx ) 814 { 815 TerrainBlock* terrain = dynamic_cast< TerrainBlock* >( terrains[ terrainIdx ] ); 816 if( !terrain ) 817 continue; 818 819 MatrixF terrWorldTransform = terrain->getWorldTransform(); 820 821 Point3F localCamPos = getCameraState().getViewPosition(); 822 terrWorldTransform.mulP(localCamPos); 823 F32 height; 824 terrain->getHeight( Point2F( localCamPos.x, localCamPos.y ), &height ); 825 bool aboveTerrain = ( height <= localCamPos.z ); 826 827 // Don't occlude if we're below the terrain. This prevents problems when 828 // looking out from underground bases... 829 if( !aboveTerrain ) 830 continue; 831 832 const Box3F& oBox = object->getObjBox(); 833 F32 minSide = getMin(oBox.len_x(), oBox.len_y()); 834 if (minSide > 85.0f) 835 continue; 836 837 const Box3F& rBox = object->getWorldBox(); 838 Point3F ul(rBox.minExtents.x, rBox.minExtents.y, rBox.maxExtents.z); 839 Point3F ur(rBox.minExtents.x, rBox.maxExtents.y, rBox.maxExtents.z); 840 Point3F ll(rBox.maxExtents.x, rBox.minExtents.y, rBox.maxExtents.z); 841 Point3F lr(rBox.maxExtents.x, rBox.maxExtents.y, rBox.maxExtents.z); 842 843 terrWorldTransform.mulP(ul); 844 terrWorldTransform.mulP(ur); 845 terrWorldTransform.mulP(ll); 846 terrWorldTransform.mulP(lr); 847 848 Point3F xBaseL0_s = ul - localCamPos; 849 Point3F xBaseL0_e = lr - localCamPos; 850 Point3F xBaseL1_s = ur - localCamPos; 851 Point3F xBaseL1_e = ll - localCamPos; 852 853 static F32 checkPoints[3] = {0.75, 0.5, 0.25}; 854 RayInfo rinfo; 855 for( U32 i = 0; i < 3; i ++ ) 856 { 857 Point3F start = (xBaseL0_s * checkPoints[i]) + localCamPos; 858 Point3F end = (xBaseL0_e * checkPoints[i]) + localCamPos; 859 860 if (terrain->castRay(start, end, &rinfo)) 861 continue; 862 863 terrain->getHeight(Point2F(start.x, start.y), &height); 864 if ((height <= start.z) == aboveTerrain) 865 continue; 866 867 start = (xBaseL1_s * checkPoints[i]) + localCamPos; 868 end = (xBaseL1_e * checkPoints[i]) + localCamPos; 869 870 if (terrain->castRay(start, end, &rinfo)) 871 continue; 872 873 Point3F test = (start + end) * 0.5; 874 if (terrain->castRay(localCamPos, test, &rinfo) == false) 875 continue; 876 877 return true; 878 } 879 } 880 881 return false; 882} 883 884//----------------------------------------------------------------------------- 885 886void SceneCullingState::debugRenderCullingVolumes() const 887{ 888 const ColorI occluderColor( 255, 0, 0, 255 ); 889 const ColorI includerColor( 0, 255, 0, 255 ); 890 891 const PlaneF& nearPlane = getCullingFrustum().getPlanes()[ Frustum::PlaneNear ]; 892 const PlaneF& farPlane = getCullingFrustum().getPlanes()[ Frustum::PlaneFar ]; 893 894 DebugDrawer* drawer = DebugDrawer::get(); 895 const SceneZoneSpaceManager* zoneManager = mSceneManager->getZoneManager(); 896 897 bool haveDebugZone = false; 898 const U32 numZones = mZoneStates.size(); 899 for( S32 zoneId = numZones - 1; zoneId >= 0; -- zoneId ) 900 { 901 if( !zoneManager->isValidZoneId( zoneId ) ) 902 continue; 903 904 const SceneZoneCullingState& zoneState = mZoneStates[ zoneId ]; 905 if( !zoneManager->getZoneOwner( zoneId )->isSelected() && ( zoneId != SceneZoneSpaceManager::RootZoneId || haveDebugZone ) ) 906 continue; 907 908 haveDebugZone = true; 909 910 for( SceneZoneCullingState::CullingVolumeIterator iter( zoneState ); 911 iter.isValid(); ++ iter ) 912 { 913 // Temporarily add near and far plane to culling volume so that 914 // no matter how it is defined, it has a chance of being properly 915 // capped. 916 917 const U32 numPlanes = iter->getPlanes().getNumPlanes(); 918 const PlaneF* planes = iter->getPlanes().getPlanes(); 919 920 TempAlloc< PlaneF> tempPlanes( numPlanes + 2 ); 921 922 tempPlanes[ 0 ] = nearPlane; 923 tempPlanes[ 1 ] = farPlane; 924 925 dMemcpy( &tempPlanes[ 2 ], planes, numPlanes * sizeof( PlaneF ) ); 926 927 // Build a polyhedron from the plane set. 928 929 Polyhedron polyhedron; 930 polyhedron.buildFromPlanes( 931 PlaneSetF( tempPlanes, numPlanes + 2 ) 932 ); 933 934 // If the polyhedron has any renderable data, 935 // hand it over to the debug drawer. 936 937 if( polyhedron.getNumEdges() ) 938 drawer->drawPolyhedron( polyhedron, iter->isOccluder() ? occluderColor : includerColor ); 939 } 940 } 941} 942