mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-04-29 00:05:40 +00:00
Engine directory for ticket #1
This commit is contained in:
parent
352279af7a
commit
7dbfe6994d
3795 changed files with 1363358 additions and 0 deletions
934
Engine/source/scene/culling/sceneCullingState.cpp
Normal file
934
Engine/source/scene/culling/sceneCullingState.cpp
Normal file
|
|
@ -0,0 +1,934 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "scene/culling/sceneCullingState.h"
|
||||
|
||||
#include "scene/sceneManager.h"
|
||||
#include "scene/sceneObject.h"
|
||||
#include "scene/zones/sceneZoneSpace.h"
|
||||
#include "math/mathUtils.h"
|
||||
#include "platform/profiler.h"
|
||||
#include "terrain/terrData.h"
|
||||
#include "util/tempAlloc.h"
|
||||
#include "gfx/sim/debugDraw.h"
|
||||
|
||||
|
||||
extern bool gEditingMission;
|
||||
|
||||
|
||||
bool SceneCullingState::smDisableTerrainOcclusion = false;
|
||||
bool SceneCullingState::smDisableZoneCulling = false;
|
||||
U32 SceneCullingState::smMaxOccludersPerZone = 4;
|
||||
F32 SceneCullingState::smOccluderMinWidthPercentage = 0.1f;
|
||||
F32 SceneCullingState::smOccluderMinHeightPercentage = 0.1f;
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
SceneCullingState::SceneCullingState( SceneManager* sceneManager, const SceneCameraState& viewState )
|
||||
: mSceneManager( sceneManager ),
|
||||
mCameraState( viewState ),
|
||||
mDisableZoneCulling( smDisableZoneCulling ),
|
||||
mDisableTerrainOcclusion( smDisableTerrainOcclusion )
|
||||
{
|
||||
AssertFatal( sceneManager->getZoneManager(), "SceneCullingState::SceneCullingState - SceneManager must have a zone manager!" );
|
||||
|
||||
VECTOR_SET_ASSOCIATION( mZoneStates );
|
||||
VECTOR_SET_ASSOCIATION( mAddedOccluderObjects );
|
||||
|
||||
// Allocate zone states.
|
||||
|
||||
const U32 numZones = sceneManager->getZoneManager()->getNumZones();
|
||||
mZoneStates.setSize( numZones );
|
||||
dMemset( mZoneStates.address(), 0, sizeof( SceneZoneCullingState ) * numZones );
|
||||
|
||||
// Allocate the zone visibility flags.
|
||||
|
||||
mZoneVisibilityFlags.setSize( numZones );
|
||||
mZoneVisibilityFlags.clear();
|
||||
|
||||
// Construct the root culling volume from
|
||||
// the camera's view frustum. Omit the frustum's
|
||||
// near and far plane so we don't test it repeatedly.
|
||||
|
||||
const Frustum& frustum = mCameraState.getFrustum();
|
||||
PlaneF* planes = allocateData< PlaneF >( 4 );
|
||||
|
||||
planes[ 0 ] = frustum.getPlanes()[ Frustum::PlaneLeft ];
|
||||
planes[ 1 ] = frustum.getPlanes()[ Frustum::PlaneRight ];
|
||||
planes[ 2 ] = frustum.getPlanes()[ Frustum::PlaneTop];
|
||||
planes[ 3 ] = frustum.getPlanes()[ Frustum::PlaneBottom ];
|
||||
|
||||
mRootVolume = SceneCullingVolume(
|
||||
SceneCullingVolume::Includer,
|
||||
PlaneSetF( planes, 4 )
|
||||
);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool SceneCullingState::isWithinVisibleZone( SceneObject* object ) const
|
||||
{
|
||||
for( SceneObject::ZoneRef* ref = object->_getZoneRefHead();
|
||||
ref != NULL; ref = ref->nextInObj )
|
||||
if( mZoneVisibilityFlags.test( ref->zone ) )
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void SceneCullingState::addOccluder( SceneObject* object )
|
||||
{
|
||||
PROFILE_SCOPE( SceneCullingState_addOccluder );
|
||||
|
||||
// If the occluder is itself occluded, don't add it.
|
||||
//
|
||||
// NOTE: We do allow near plane intersections here. Silhouette extraction
|
||||
// should take that into account.
|
||||
|
||||
if( cullObjects( &object, 1, DontCullRenderDisabled ) != 1 )
|
||||
return;
|
||||
|
||||
// If the occluder has already been added, do nothing. Check this
|
||||
// after the culling check since the same occluder can be added by
|
||||
// two separate zones and not be visible in one yet be visible in the
|
||||
// other.
|
||||
|
||||
if( mAddedOccluderObjects.contains( object ) )
|
||||
return;
|
||||
mAddedOccluderObjects.push_back( object );
|
||||
|
||||
// Let the object build a silhouette. If it doesn't
|
||||
// return one, abort.
|
||||
|
||||
Vector< Point3F > silhouette;
|
||||
object->buildSilhouette( getCameraState(), silhouette );
|
||||
|
||||
if( silhouette.empty() || silhouette.size() < 3 )
|
||||
return;
|
||||
|
||||
// Generate the culling volume.
|
||||
|
||||
SceneCullingVolume volume;
|
||||
if( !createCullingVolume(
|
||||
silhouette.address(),
|
||||
silhouette.size(),
|
||||
SceneCullingVolume::Occluder,
|
||||
volume ) )
|
||||
return;
|
||||
|
||||
// Add the frustum to all zones that the object is assigned to.
|
||||
|
||||
for( SceneObject::ZoneRef* ref = object->_getZoneRefHead(); ref != NULL; ref = ref->nextInObj )
|
||||
addCullingVolumeToZone( ref->zone, volume );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool SceneCullingState::addCullingVolumeToZone( U32 zoneId, const SceneCullingVolume& volume )
|
||||
{
|
||||
PROFILE_SCOPE( SceneCullingState_addCullingVolumeToZone );
|
||||
|
||||
AssertFatal( zoneId < mZoneStates.size(), "SceneCullingState::addCullingVolumeToZone - Zone ID out of range" );
|
||||
SceneZoneCullingState& zoneState = mZoneStates[ zoneId ];
|
||||
|
||||
// [rene, 07-Apr-10] I previously used to attempt to merge things here and detect whether
|
||||
// the visibility state of the zone has changed at all. Since we allow polyhedra to be
|
||||
// degenerate here and since polyhedra cannot be merged easily like frustums, I have opted
|
||||
// to remove this for now. I'm also convinced that with the current traversal system it
|
||||
// adds little benefit.
|
||||
|
||||
// Link the volume to the zone state.
|
||||
|
||||
typedef SceneZoneCullingState::CullingVolumeLink LinkType;
|
||||
|
||||
LinkType* link = reinterpret_cast< LinkType* >( allocateData( sizeof( LinkType ) ) );
|
||||
|
||||
link->mVolume = volume;
|
||||
link->mNext = zoneState.mCullingVolumes;
|
||||
zoneState.mCullingVolumes = link;
|
||||
|
||||
if( volume.isOccluder() )
|
||||
zoneState.mHaveOccluders = true;
|
||||
else
|
||||
zoneState.mHaveIncluders = true;
|
||||
|
||||
// Mark sorting state as dirty.
|
||||
|
||||
zoneState.mHaveSortedVolumes = false;
|
||||
|
||||
// Set the visibility flag for the zone.
|
||||
|
||||
if( volume.isIncluder() )
|
||||
mZoneVisibilityFlags.set( zoneId );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool SceneCullingState::addCullingVolumeToZone( U32 zoneId, SceneCullingVolume::Type type, const AnyPolyhedron& polyhedron )
|
||||
{
|
||||
// Allocate space on our chunker.
|
||||
|
||||
const U32 numPlanes = polyhedron.getNumPlanes();
|
||||
PlaneF* planes = allocateData< PlaneF >( numPlanes );
|
||||
|
||||
// Copy the planes over.
|
||||
|
||||
dMemcpy( planes, polyhedron.getPlanes(), numPlanes * sizeof( planes[ 0 ] ) );
|
||||
|
||||
// Create a culling volume.
|
||||
|
||||
SceneCullingVolume volume(
|
||||
type,
|
||||
PlaneSetF( planes, numPlanes )
|
||||
);
|
||||
|
||||
// And add it.
|
||||
|
||||
return addCullingVolumeToZone( zoneId, volume );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool SceneCullingState::createCullingVolume( const Point3F* vertices, U32 numVertices, SceneCullingVolume::Type type, SceneCullingVolume& outVolume )
|
||||
{
|
||||
const Point3F& viewPos = getCameraState().getViewPosition();
|
||||
const Point3F& viewDir = getCameraState().getViewDirection();
|
||||
const bool isOrtho = getFrustum().isOrtho();
|
||||
|
||||
//TODO: check if we need to handle penetration of the near plane for occluders specially
|
||||
|
||||
// Allocate space for the clipping planes we generate. Assume the worst case
|
||||
// of every edge generating a plane and, for includers, all edges meeting at
|
||||
// steep angles so we need to insert extra planes (the latter is not possible,
|
||||
// of course, but it makes things less complicated here). For occluders, add
|
||||
// an extra plane for the near cap.
|
||||
|
||||
const U32 maxPlanes = ( type == SceneCullingVolume::Occluder ? numVertices + 1 : numVertices * 2 );
|
||||
PlaneF* planes = allocateData< PlaneF >( maxPlanes );
|
||||
|
||||
// Keep track of the world-space bounds of the polygon. We use this later
|
||||
// to derive some metrics.
|
||||
|
||||
Box3F wsPolyBounds;
|
||||
|
||||
wsPolyBounds.minExtents = Point3F( TypeTraits< F32 >::MAX, TypeTraits< F32 >::MAX, TypeTraits< F32 >::MAX );
|
||||
wsPolyBounds.maxExtents = Point3F( TypeTraits< F32 >::MIN, TypeTraits< F32 >::MIN, TypeTraits< F32 >::MIN );
|
||||
|
||||
// For occluders, also keep track of the nearest, and two farthest silhouette points. We use
|
||||
// this later to construct a near capping plane.
|
||||
F32 minVertexDistanceSquared = TypeTraits< F32 >::MAX;
|
||||
U32 leastDistantVert = 0;
|
||||
|
||||
F32 maxVertexDistancesSquared[ 2 ] = { TypeTraits< F32 >::MIN, TypeTraits< F32 >::MIN };
|
||||
U32 mostDistantVertices[ 2 ] = { 0, 0 };
|
||||
|
||||
// Generate the extrusion volume. For orthographic projections, extrude
|
||||
// parallel to the view direction whereas for parallel projections, extrude
|
||||
// from the viewpoint.
|
||||
|
||||
U32 numPlanes = 0;
|
||||
U32 lastVertex = numVertices - 1;
|
||||
bool invert = false;
|
||||
|
||||
for( U32 i = 0; i < numVertices; lastVertex = i, ++ i )
|
||||
{
|
||||
AssertFatal( numPlanes < maxPlanes, "SceneCullingState::createCullingVolume - Did not allocate enough planes!" );
|
||||
|
||||
const Point3F& v1 = vertices[ i ];
|
||||
const Point3F& v2 = vertices[ lastVertex ];
|
||||
|
||||
// Keep track of bounds.
|
||||
|
||||
wsPolyBounds.minExtents.setMin( v1 );
|
||||
wsPolyBounds.maxExtents.setMax( v1 );
|
||||
|
||||
// Skip the edge if it's length is really short.
|
||||
|
||||
const Point3F edgeVector = v2 - v1;
|
||||
const F32 edgeVectorLenSquared = edgeVector.lenSquared();
|
||||
if( edgeVectorLenSquared < 0.025f )
|
||||
continue;
|
||||
|
||||
//TODO: might need to do additional checks here for non-planar polygons used by occluders
|
||||
//TODO: test for colinearity of edge vector with view vector (occluders only)
|
||||
|
||||
// Create a plane for the edge.
|
||||
|
||||
if( isOrtho )
|
||||
{
|
||||
// Compute a plane through the two edge vertices and one
|
||||
// of the vertices extended along the view direction.
|
||||
|
||||
if( !invert )
|
||||
planes[ numPlanes ] = PlaneF( v1, v1 + viewDir, v2 );
|
||||
else
|
||||
planes[ numPlanes ] = PlaneF( v2, v1 + viewDir, v1 );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compute a plane going through the viewpoint and the two
|
||||
// edge vertices.
|
||||
|
||||
if( !invert )
|
||||
planes[ numPlanes ] = PlaneF( v1, viewPos, v2 );
|
||||
else
|
||||
planes[ numPlanes ] = PlaneF( v2, viewPos, v1 );
|
||||
}
|
||||
|
||||
numPlanes ++;
|
||||
|
||||
// If this is the first plane that we have created, find out whether
|
||||
// the vertex ordering is giving us the plane orientations that we want
|
||||
// (facing inside). If not, invert vertex order from now on.
|
||||
|
||||
if( numPlanes == 1 )
|
||||
{
|
||||
Point3F center( 0, 0, 0 );
|
||||
for( U32 n = 0; n < numVertices; ++ n )
|
||||
center += vertices[n];
|
||||
center /= numVertices;
|
||||
|
||||
if( planes[numPlanes - 1].whichSide( center ) == PlaneF::Back )
|
||||
{
|
||||
invert = true;
|
||||
planes[ numPlanes - 1 ].invert();
|
||||
}
|
||||
}
|
||||
|
||||
// For occluders, keep tabs of the nearest, and two farthest vertices.
|
||||
|
||||
if( type == SceneCullingVolume::Occluder )
|
||||
{
|
||||
const F32 distSquared = ( v1 - viewPos ).lenSquared();
|
||||
if( distSquared < minVertexDistanceSquared )
|
||||
{
|
||||
minVertexDistanceSquared = distSquared;
|
||||
leastDistantVert = i;
|
||||
}
|
||||
if( distSquared > maxVertexDistancesSquared[ 0 ] )
|
||||
{
|
||||
// Move 0 to 1.
|
||||
maxVertexDistancesSquared[ 1 ] = maxVertexDistancesSquared[ 0 ];
|
||||
mostDistantVertices[ 1 ] = mostDistantVertices[ 0 ];
|
||||
|
||||
// Replace 0.
|
||||
maxVertexDistancesSquared[ 0 ] = distSquared;
|
||||
mostDistantVertices[ 0 ] = i;
|
||||
}
|
||||
else if( distSquared > maxVertexDistancesSquared[ 1 ] )
|
||||
{
|
||||
// Replace 1.
|
||||
maxVertexDistancesSquared[ 1 ] = distSquared;
|
||||
mostDistantVertices[ 1 ] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the extrusion produced no useful result, abort.
|
||||
|
||||
if( numPlanes < 3 )
|
||||
return false;
|
||||
|
||||
// For includers, test the angle of the edges at the current vertex.
|
||||
// If too steep, add an extra plane to improve culling efficiency.
|
||||
|
||||
if( false )//type == SceneCullingVolume::Includer )
|
||||
{
|
||||
const U32 numOriginalPlanes = numPlanes;
|
||||
U32 lastPlaneIndex = numPlanes - 1;
|
||||
|
||||
for( U32 i = 0; i < numOriginalPlanes; lastPlaneIndex = i, ++ i )
|
||||
{
|
||||
const PlaneF& currentPlane = planes[ i ];
|
||||
const PlaneF& lastPlane = planes[ lastPlaneIndex ];
|
||||
|
||||
// Compute the cosine of the angle between the two plane normals.
|
||||
|
||||
const F32 cosAngle = mFabs( mDot( currentPlane, lastPlane ) );
|
||||
|
||||
// The planes meet at increasingly steep angles the more they point
|
||||
// in opposite directions, i.e the closer the angle of their normals
|
||||
// is to 180 degrees. Skip any two planes that don't get near that.
|
||||
|
||||
if( cosAngle > 0.1f )
|
||||
continue;
|
||||
|
||||
//TODO
|
||||
|
||||
const Point3F addNormals = currentPlane + lastPlane;
|
||||
const Point3F crossNormals = mCross( currentPlane, lastPlane );
|
||||
|
||||
Point3F newNormal = currentPlane + lastPlane;//addNormals - mDot( addNormals, crossNormals ) * crossNormals;
|
||||
|
||||
//
|
||||
|
||||
planes[ numPlanes ] = PlaneF( currentPlane.getPosition(), newNormal );
|
||||
numPlanes ++;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the metrics of the culling volume in relation to the view frustum.
|
||||
//
|
||||
// For this, we are short-circuiting things slightly. The correct way (other than doing
|
||||
// full screen projections) would be to transform all the polygon points into camera
|
||||
// space, lay an AABB around those points, and then find the X and Z extents on the near plane.
|
||||
//
|
||||
// However, while not as accurate, a faster way is to just project the axial vectors
|
||||
// of the bounding box onto both the camera right and up vector. This gives us a rough
|
||||
// estimate of the camera-space size of the polygon we're looking at.
|
||||
|
||||
const MatrixF& cameraTransform = getCameraState().getViewWorldMatrix();
|
||||
const Point3F cameraRight = cameraTransform.getRightVector();
|
||||
const Point3F cameraUp = cameraTransform.getUpVector();
|
||||
|
||||
const Point3F wsPolyBoundsExtents = wsPolyBounds.getExtents();
|
||||
|
||||
F32 widthEstimate =
|
||||
getMax( mFabs( wsPolyBoundsExtents.x * cameraRight.x ),
|
||||
getMax( mFabs( wsPolyBoundsExtents.y * cameraRight.y ),
|
||||
mFabs( wsPolyBoundsExtents.z * cameraRight.z ) ) );
|
||||
|
||||
F32 heightEstimate =
|
||||
getMax( mFabs( wsPolyBoundsExtents.x * cameraUp.x ),
|
||||
getMax( mFabs( wsPolyBoundsExtents.y * cameraUp.y ),
|
||||
mFabs( wsPolyBoundsExtents.z * cameraUp.z ) ) );
|
||||
|
||||
// If the current camera is a perspective one, divide the two estimates
|
||||
// by the distance of the nearest bounding box vertex to the camera
|
||||
// to account for perspective distortion.
|
||||
|
||||
if( !isOrtho )
|
||||
{
|
||||
const Point3F nearestVertex = wsPolyBounds.computeVertex(
|
||||
Box3F::getPointIndexFromOctant( - viewDir )
|
||||
);
|
||||
|
||||
const F32 distance = ( nearestVertex - viewPos ).len();
|
||||
|
||||
widthEstimate /= distance;
|
||||
heightEstimate /= distance;
|
||||
}
|
||||
|
||||
// If we are creating an occluder, check to see if the estimates fit
|
||||
// our minimum requirements.
|
||||
|
||||
if( type == SceneCullingVolume::Occluder )
|
||||
{
|
||||
const F32 widthEstimatePercentage = widthEstimate / getFrustum().getWidth();
|
||||
const F32 heightEstimatePercentage = heightEstimate / getFrustum().getHeight();
|
||||
|
||||
if( widthEstimatePercentage < smOccluderMinWidthPercentage ||
|
||||
heightEstimatePercentage < smOccluderMinHeightPercentage )
|
||||
return false; // Reject.
|
||||
}
|
||||
|
||||
// Use the area estimate as the volume's sort point.
|
||||
|
||||
const F32 sortPoint = widthEstimate * heightEstimate;
|
||||
|
||||
// Finally, if it's an occluder, compute a near cap. The near cap prevents objects
|
||||
// in front of the occluder from testing positive. The same could be achieved by
|
||||
// manually comparing distances before testing objects but since that would amount
|
||||
// to the same checks the plane/AABB tests do, it's easier to just add another plane.
|
||||
// Additionally, it gives the benefit of being able to create more precise culling
|
||||
// results by angling the plane.
|
||||
|
||||
//NOTE: Could consider adding a near cap for includers too when generating a volume
|
||||
// for the outdoor zone as that may prevent quite a bit of space from being included.
|
||||
// However, given that this space will most likely just be filled with interior
|
||||
// stuff anyway, it's probably not worth it.
|
||||
|
||||
if( type == SceneCullingVolume::Occluder )
|
||||
{
|
||||
const U32 nearCapIndex = numPlanes;
|
||||
planes[ nearCapIndex ] = PlaneF(
|
||||
vertices[ mostDistantVertices[ 0 ] ],
|
||||
vertices[ mostDistantVertices[ 1 ] ],
|
||||
vertices[ leastDistantVert ] );
|
||||
|
||||
// Invert the plane, if necessary.
|
||||
if( planes[ nearCapIndex ].whichSide( viewPos ) == PlaneF::Front )
|
||||
planes[ nearCapIndex ].invert();
|
||||
|
||||
numPlanes ++;
|
||||
}
|
||||
|
||||
// Create the volume from the planes.
|
||||
|
||||
outVolume = SceneCullingVolume(
|
||||
type,
|
||||
PlaneSetF( planes, numPlanes )
|
||||
);
|
||||
outVolume.setSortPoint( sortPoint );
|
||||
|
||||
// Done.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace {
|
||||
struct ZoneArrayIterator
|
||||
{
|
||||
U32 mCurrent;
|
||||
U32 mNumZones;
|
||||
const U32* mZones;
|
||||
|
||||
ZoneArrayIterator( const U32* zones, U32 numZones )
|
||||
: mCurrent( 0 ),
|
||||
mNumZones( numZones ),
|
||||
mZones( zones ) {}
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return ( mCurrent < mNumZones );
|
||||
}
|
||||
ZoneArrayIterator& operator ++()
|
||||
{
|
||||
mCurrent ++;
|
||||
return *this;
|
||||
}
|
||||
U32 operator *() const
|
||||
{
|
||||
return mZones[ mCurrent ];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template< typename T, typename Iter >
|
||||
inline SceneZoneCullingState::CullingTestResult SceneCullingState::_testOccludersOnly( const T& bounds, Iter zoneIter ) const
|
||||
{
|
||||
// Test the culling states of all zones that the object
|
||||
// is assigned to.
|
||||
|
||||
for( ; zoneIter.isValid(); ++ zoneIter )
|
||||
{
|
||||
const SceneZoneCullingState& zoneState = getZoneState( *zoneIter );
|
||||
|
||||
// Skip zone if there are no occluders.
|
||||
|
||||
if( !zoneState.hasOccluders() )
|
||||
continue;
|
||||
|
||||
// If the object's world bounds overlaps any of the volumes
|
||||
// for this zone, it's rendered.
|
||||
|
||||
if( zoneState.testVolumes( bounds, true ) == SceneZoneCullingState::CullingTestPositiveByOcclusion )
|
||||
return SceneZoneCullingState::CullingTestPositiveByOcclusion;
|
||||
}
|
||||
|
||||
return SceneZoneCullingState::CullingTestNegative;
|
||||
}
|
||||
|
||||
template< typename T, typename Iter >
|
||||
inline SceneZoneCullingState::CullingTestResult SceneCullingState::_test( const T& bounds, Iter zoneIter,
|
||||
const PlaneF& nearPlane, const PlaneF& farPlane ) const
|
||||
{
|
||||
// Defer test of near and far plane until we've hit a zone
|
||||
// which actually has visible space. This prevents us from
|
||||
// doing near/far tests on objects that were included in the
|
||||
// potential render list but aren't actually in any visible
|
||||
// zone.
|
||||
bool haveTestedNearAndFar = false;
|
||||
|
||||
// Test the culling states of all zones that the object
|
||||
// is assigned to.
|
||||
|
||||
for( ; zoneIter.isValid(); ++ zoneIter )
|
||||
{
|
||||
const SceneZoneCullingState& zoneState = getZoneState( *zoneIter );
|
||||
|
||||
// Skip zone if there are no positive culling volumes.
|
||||
|
||||
if( !zoneState.hasIncluders() )
|
||||
continue;
|
||||
|
||||
// If we haven't tested the near and far plane yet, do so
|
||||
// now.
|
||||
|
||||
if( !haveTestedNearAndFar )
|
||||
{
|
||||
// Test near plane.
|
||||
|
||||
PlaneF::Side nearSide = nearPlane.whichSide( bounds );
|
||||
if( nearSide == PlaneF::Back )
|
||||
return SceneZoneCullingState::CullingTestNegative;
|
||||
|
||||
// Test far plane.
|
||||
|
||||
PlaneF::Side farSide = farPlane.whichSide( bounds );
|
||||
if( farSide == PlaneF::Back )
|
||||
return SceneZoneCullingState::CullingTestNegative;
|
||||
|
||||
haveTestedNearAndFar = true;
|
||||
}
|
||||
|
||||
// If the object's world bounds overlaps any of the volumes
|
||||
// for this zone, it's rendered.
|
||||
|
||||
SceneZoneCullingState::CullingTestResult result = zoneState.testVolumes( bounds );
|
||||
|
||||
if( result == SceneZoneCullingState::CullingTestPositiveByInclusion )
|
||||
return result;
|
||||
else if( result == SceneZoneCullingState::CullingTestPositiveByOcclusion )
|
||||
return result;
|
||||
}
|
||||
|
||||
return SceneZoneCullingState::CullingTestNegative;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
template< bool OCCLUDERS_ONLY, typename T >
|
||||
inline SceneZoneCullingState::CullingTestResult SceneCullingState::_test( const T& bounds, const U32* zones, U32 numZones ) const
|
||||
{
|
||||
// If zone culling is disabled, only test against
|
||||
// the root frustum.
|
||||
|
||||
if( disableZoneCulling() )
|
||||
{
|
||||
if( !OCCLUDERS_ONLY && !getFrustum().isCulled( bounds ) )
|
||||
return SceneZoneCullingState::CullingTestPositiveByInclusion;
|
||||
|
||||
return SceneZoneCullingState::CullingTestNegative;
|
||||
}
|
||||
|
||||
// Otherwise test each of the zones.
|
||||
|
||||
if( OCCLUDERS_ONLY )
|
||||
{
|
||||
return _testOccludersOnly(
|
||||
bounds,
|
||||
ZoneArrayIterator( zones, numZones )
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
const PlaneF* frustumPlanes = getFrustum().getPlanes();
|
||||
|
||||
return _test(
|
||||
bounds,
|
||||
ZoneArrayIterator( zones, numZones ),
|
||||
frustumPlanes[ Frustum::PlaneNear ],
|
||||
frustumPlanes[ Frustum::PlaneFar ]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool SceneCullingState::isCulled( const Box3F& aabb, const U32* zones, U32 numZones ) const
|
||||
{
|
||||
SceneZoneCullingState::CullingTestResult result = _test< false >( aabb, zones, numZones );
|
||||
return ( result == SceneZoneCullingState::CullingTestNegative ||
|
||||
result == SceneZoneCullingState::CullingTestPositiveByOcclusion );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool SceneCullingState::isCulled( const OrientedBox3F& obb, const U32* zones, U32 numZones ) const
|
||||
{
|
||||
SceneZoneCullingState::CullingTestResult result = _test< false >( obb, zones, numZones );
|
||||
return ( result == SceneZoneCullingState::CullingTestNegative ||
|
||||
result == SceneZoneCullingState::CullingTestPositiveByOcclusion );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool SceneCullingState::isCulled( const SphereF& sphere, const U32* zones, U32 numZones ) const
|
||||
{
|
||||
SceneZoneCullingState::CullingTestResult result = _test< false >( sphere, zones, numZones );
|
||||
return ( result == SceneZoneCullingState::CullingTestNegative ||
|
||||
result == SceneZoneCullingState::CullingTestPositiveByOcclusion );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool SceneCullingState::isOccluded( SceneObject* object ) const
|
||||
{
|
||||
if( disableZoneCulling() )
|
||||
return false;
|
||||
|
||||
CullingTestResult result = _testOccludersOnly(
|
||||
object->getWorldBox(),
|
||||
SceneObject::ObjectZonesIterator( object )
|
||||
);
|
||||
|
||||
return ( result == SceneZoneCullingState::CullingTestPositiveByOcclusion );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool SceneCullingState::isOccluded( const Box3F& aabb, const U32* zones, U32 numZones ) const
|
||||
{
|
||||
return ( _test< true >( aabb, zones, numZones ) == SceneZoneCullingState::CullingTestPositiveByOcclusion );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool SceneCullingState::isOccluded( const OrientedBox3F& obb, const U32* zones, U32 numZones ) const
|
||||
{
|
||||
return ( _test< true >( obb, zones, numZones ) == SceneZoneCullingState::CullingTestPositiveByOcclusion );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool SceneCullingState::isOccluded( const SphereF& sphere, const U32* zones, U32 numZones ) const
|
||||
{
|
||||
return ( _test< true >( sphere, zones, numZones ) == SceneZoneCullingState::CullingTestPositiveByOcclusion );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
U32 SceneCullingState::cullObjects( SceneObject** objects, U32 numObjects, U32 cullOptions ) const
|
||||
{
|
||||
PROFILE_SCOPE( SceneCullingState_cullObjects );
|
||||
|
||||
U32 numRemainingObjects = 0;
|
||||
|
||||
// We test near and far planes separately in order to not do the tests
|
||||
// repeatedly, so fetch the planes now.
|
||||
const PlaneF& nearPlane = getFrustum().getPlanes()[ Frustum::PlaneNear ];
|
||||
const PlaneF& farPlane = getFrustum().getPlanes()[ Frustum::PlaneFar ];
|
||||
|
||||
for( U32 i = 0; i < numObjects; ++ i )
|
||||
{
|
||||
SceneObject* object = objects[ i ];
|
||||
bool isCulled = true;
|
||||
|
||||
// If we should respect editor overrides, test that now.
|
||||
|
||||
if( !( cullOptions & CullEditorOverrides ) &&
|
||||
gEditingMission &&
|
||||
( ( object->isCullingDisabledInEditor() && object->isRenderEnabled() ) || object->isSelected() ) )
|
||||
{
|
||||
isCulled = false;
|
||||
}
|
||||
|
||||
// If the object is render-disabled, it gets culled. The only
|
||||
// way around this is the editor override above.
|
||||
|
||||
else if( !( cullOptions & DontCullRenderDisabled ) &&
|
||||
!object->isRenderEnabled() )
|
||||
{
|
||||
isCulled = true;
|
||||
}
|
||||
|
||||
// Global bounds objects are never culled. Note that this means
|
||||
// that if these objects are to respect zoning, they need to manually
|
||||
// trigger the respective culling checks for whatever they want to
|
||||
// batch.
|
||||
|
||||
else if( object->isGlobalBounds() )
|
||||
isCulled = false;
|
||||
|
||||
// If terrain occlusion checks are enabled, run them now.
|
||||
|
||||
else if( !mDisableTerrainOcclusion &&
|
||||
object->getWorldBox().minExtents.x > -1e5 &&
|
||||
isOccludedByTerrain( object ) )
|
||||
{
|
||||
// Occluded by terrain.
|
||||
isCulled = true;
|
||||
}
|
||||
|
||||
// If the object shouldn't be subjected to more fine-grained culling
|
||||
// or if zone culling is disabled, just test against the root frustum.
|
||||
|
||||
else if( !( object->getTypeMask() & CULLING_INCLUDE_TYPEMASK ) ||
|
||||
( object->getTypeMask() & CULLING_EXCLUDE_TYPEMASK ) ||
|
||||
disableZoneCulling() )
|
||||
{
|
||||
isCulled = getFrustum().isCulled( object->getWorldBox() );
|
||||
}
|
||||
|
||||
// Go through the zones that the object is assigned to and
|
||||
// test the object against the frustums of each of the zones.
|
||||
|
||||
else
|
||||
{
|
||||
CullingTestResult result = _test(
|
||||
object->getWorldBox(),
|
||||
SceneObject::ObjectZonesIterator( object ),
|
||||
nearPlane,
|
||||
farPlane
|
||||
);
|
||||
|
||||
isCulled = ( result == SceneZoneCullingState::CullingTestNegative ||
|
||||
result == SceneZoneCullingState::CullingTestPositiveByOcclusion );
|
||||
}
|
||||
|
||||
if( !isCulled )
|
||||
objects[ numRemainingObjects ++ ] = object;
|
||||
}
|
||||
|
||||
return numRemainingObjects;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool SceneCullingState::isOccludedByTerrain( SceneObject* object ) const
|
||||
{
|
||||
PROFILE_SCOPE( SceneCullingState_isOccludedByTerrain );
|
||||
|
||||
// Don't try to occlude globally bounded objects.
|
||||
if( object->isGlobalBounds() )
|
||||
return false;
|
||||
|
||||
const Vector< SceneObject* >& terrains = getSceneManager()->getContainer()->getTerrains();
|
||||
const U32 numTerrains = terrains.size();
|
||||
|
||||
for( U32 terrainIdx = 0; terrainIdx < numTerrains; ++ terrainIdx )
|
||||
{
|
||||
TerrainBlock* terrain = dynamic_cast< TerrainBlock* >( terrains[ terrainIdx ] );
|
||||
if( !terrain )
|
||||
continue;
|
||||
|
||||
Point3F localCamPos = getCameraState().getViewPosition();
|
||||
terrain->getWorldTransform().mulP( localCamPos );
|
||||
F32 height;
|
||||
terrain->getHeight( Point2F( localCamPos.x, localCamPos.y ), &height );
|
||||
bool aboveTerrain = ( height <= localCamPos.z );
|
||||
|
||||
// Don't occlude if we're below the terrain. This prevents problems when
|
||||
// looking out from underground bases...
|
||||
if( !aboveTerrain )
|
||||
continue;
|
||||
|
||||
const Box3F& oBox = object->getObjBox();
|
||||
F32 minSide = getMin(oBox.len_x(), oBox.len_y());
|
||||
if (minSide > 85.0f)
|
||||
continue;
|
||||
|
||||
const Box3F& rBox = object->getWorldBox();
|
||||
Point3F ul(rBox.minExtents.x, rBox.minExtents.y, rBox.maxExtents.z);
|
||||
Point3F ur(rBox.minExtents.x, rBox.maxExtents.y, rBox.maxExtents.z);
|
||||
Point3F ll(rBox.maxExtents.x, rBox.minExtents.y, rBox.maxExtents.z);
|
||||
Point3F lr(rBox.maxExtents.x, rBox.maxExtents.y, rBox.maxExtents.z);
|
||||
|
||||
terrain->getWorldTransform().mulP(ul);
|
||||
terrain->getWorldTransform().mulP(ur);
|
||||
terrain->getWorldTransform().mulP(ll);
|
||||
terrain->getWorldTransform().mulP(lr);
|
||||
|
||||
Point3F xBaseL0_s = ul - localCamPos;
|
||||
Point3F xBaseL0_e = lr - localCamPos;
|
||||
Point3F xBaseL1_s = ur - localCamPos;
|
||||
Point3F xBaseL1_e = ll - localCamPos;
|
||||
|
||||
static F32 checkPoints[3] = {0.75, 0.5, 0.25};
|
||||
RayInfo rinfo;
|
||||
for( U32 i = 0; i < 3; i ++ )
|
||||
{
|
||||
Point3F start = (xBaseL0_s * checkPoints[i]) + localCamPos;
|
||||
Point3F end = (xBaseL0_e * checkPoints[i]) + localCamPos;
|
||||
|
||||
if (terrain->castRay(start, end, &rinfo))
|
||||
continue;
|
||||
|
||||
terrain->getHeight(Point2F(start.x, start.y), &height);
|
||||
if ((height <= start.z) == aboveTerrain)
|
||||
continue;
|
||||
|
||||
start = (xBaseL1_s * checkPoints[i]) + localCamPos;
|
||||
end = (xBaseL1_e * checkPoints[i]) + localCamPos;
|
||||
|
||||
if (terrain->castRay(start, end, &rinfo))
|
||||
continue;
|
||||
|
||||
Point3F test = (start + end) * 0.5;
|
||||
if (terrain->castRay(localCamPos, test, &rinfo) == false)
|
||||
continue;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void SceneCullingState::debugRenderCullingVolumes() const
|
||||
{
|
||||
const ColorI occluderColor( 255, 0, 0, 255 );
|
||||
const ColorI includerColor( 0, 255, 0, 255 );
|
||||
|
||||
const PlaneF& nearPlane = getFrustum().getPlanes()[ Frustum::PlaneNear ];
|
||||
const PlaneF& farPlane = getFrustum().getPlanes()[ Frustum::PlaneFar ];
|
||||
|
||||
DebugDrawer* drawer = DebugDrawer::get();
|
||||
const SceneZoneSpaceManager* zoneManager = mSceneManager->getZoneManager();
|
||||
|
||||
bool haveDebugZone = false;
|
||||
const U32 numZones = mZoneStates.size();
|
||||
for( S32 zoneId = numZones - 1; zoneId >= 0; -- zoneId )
|
||||
{
|
||||
if( !zoneManager->isValidZoneId( zoneId ) )
|
||||
continue;
|
||||
|
||||
const SceneZoneCullingState& zoneState = mZoneStates[ zoneId ];
|
||||
if( !zoneManager->getZoneOwner( zoneId )->isSelected() && ( zoneId != SceneZoneSpaceManager::RootZoneId || haveDebugZone ) )
|
||||
continue;
|
||||
|
||||
haveDebugZone = true;
|
||||
|
||||
for( SceneZoneCullingState::CullingVolumeIterator iter( zoneState );
|
||||
iter.isValid(); ++ iter )
|
||||
{
|
||||
// Temporarily add near and far plane to culling volume so that
|
||||
// no matter how it is defined, it has a chance of being properly
|
||||
// capped.
|
||||
|
||||
const U32 numPlanes = iter->getPlanes().getNumPlanes();
|
||||
const PlaneF* planes = iter->getPlanes().getPlanes();
|
||||
|
||||
TempAlloc< PlaneF > tempPlanes( numPlanes + 2 );
|
||||
|
||||
tempPlanes[ 0 ] = nearPlane;
|
||||
tempPlanes[ 1 ] = farPlane;
|
||||
|
||||
dMemcpy( &tempPlanes[ 2 ], planes, numPlanes * sizeof( PlaneF ) );
|
||||
|
||||
// Build a polyhedron from the plane set.
|
||||
|
||||
Polyhedron polyhedron;
|
||||
polyhedron.buildFromPlanes(
|
||||
PlaneSetF( tempPlanes, numPlanes + 2 )
|
||||
);
|
||||
|
||||
// If the polyhedron has any renderable data,
|
||||
// hand it over to the debug drawer.
|
||||
|
||||
if( polyhedron.getNumEdges() )
|
||||
drawer->drawPolyhedron( polyhedron, iter->isOccluder() ? occluderColor : includerColor );
|
||||
}
|
||||
}
|
||||
}
|
||||
310
Engine/source/scene/culling/sceneCullingState.h
Normal file
310
Engine/source/scene/culling/sceneCullingState.h
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _SCENECULLINGSTATE_H_
|
||||
#define _SCENECULLINGSTATE_H_
|
||||
|
||||
#ifndef _SCENEZONECULLINGSTATE_H_
|
||||
#include "scene/culling/sceneZoneCullingState.h"
|
||||
#endif
|
||||
|
||||
#ifndef _MATHUTIL_FRUSTUM_H_
|
||||
#include "math/util/frustum.h"
|
||||
#endif
|
||||
|
||||
#ifndef _TVECTOR_H_
|
||||
#include "core/util/tVector.h"
|
||||
#endif
|
||||
|
||||
#ifndef _SCENECAMERASTATE_H_
|
||||
#include "scene/sceneCameraState.h"
|
||||
#endif
|
||||
|
||||
#ifndef _DATACHUNKER_H_
|
||||
#include "core/dataChunker.h"
|
||||
#endif
|
||||
|
||||
#ifndef _BITVECTOR_H_
|
||||
#include "core/bitVector.h"
|
||||
#endif
|
||||
|
||||
|
||||
class SceneObject;
|
||||
class SceneManager;
|
||||
|
||||
|
||||
/// An object that gathers the culling state for a scene.
|
||||
class SceneCullingState
|
||||
{
|
||||
public:
|
||||
|
||||
/// Used to disable the somewhat expensive terrain occlusion testing
|
||||
/// done in during scene culling.
|
||||
static bool smDisableTerrainOcclusion;
|
||||
|
||||
/// Whether to force zone culling to off by default.
|
||||
static bool smDisableZoneCulling;
|
||||
|
||||
/// @name Occluder Restrictions
|
||||
/// Size restrictions on occlusion culling volumes. Any occlusion volume
|
||||
/// that does not meet these minimum requirements is not accepted into the
|
||||
/// rendering state.
|
||||
///
|
||||
/// Having independent restrictions on both width and height allows filtering
|
||||
/// out occluders that might have a lot of area but only by covering very thin
|
||||
/// stretches of the screen.
|
||||
/// @{
|
||||
|
||||
/// If more than this number of occlusion volumes are added to a ZoneState,
|
||||
/// then the occlusions volumes corresponding to the smallest amount of screen
|
||||
/// real estate get dropped such as to never exceed this total number of occlusion
|
||||
/// volumes.
|
||||
static U32 smMaxOccludersPerZone;
|
||||
|
||||
/// Percentage of camera-space frustum near plane height that an occlusion culler must
|
||||
/// at least fill in order to not be rejected.
|
||||
/// @note The height computed for occluders is only an estimate.
|
||||
static F32 smOccluderMinHeightPercentage;
|
||||
|
||||
/// Percentage of camera-space frustum near plane width that an occlusion culler must
|
||||
/// at least fill in order to not be rejected.
|
||||
/// @note The width computed for occluders is only an estimate.
|
||||
static F32 smOccluderMinWidthPercentage;
|
||||
|
||||
/// @}
|
||||
|
||||
protected:
|
||||
|
||||
/// Scene which is being culled.
|
||||
SceneManager* mSceneManager;
|
||||
|
||||
/// The viewing state that defines how the scene is being viewed.
|
||||
SceneCameraState mCameraState;
|
||||
|
||||
/// The root culling volume corresponding to the camera frustum.
|
||||
SceneCullingVolume mRootVolume;
|
||||
|
||||
/// Occluders that have been added to this render state. Adding an occluder does not
|
||||
/// necessarily result in an occluder volume being added. To not repeatedly try to
|
||||
/// process the same occluder object, all objects that are added are recorded here.
|
||||
Vector< SceneObject* > mAddedOccluderObjects;
|
||||
|
||||
///
|
||||
BitVector mZoneVisibilityFlags;
|
||||
|
||||
/// ZoneState entries for all zones in the scene.
|
||||
Vector< SceneZoneCullingState > mZoneStates;
|
||||
|
||||
/// Allocator for culling data that can be freed in one go when
|
||||
/// the culling state is freed.
|
||||
DataChunker mDataChunker;
|
||||
|
||||
/// If true, occlusion checks will not be done against the terrains
|
||||
/// in the scene.
|
||||
bool mDisableTerrainOcclusion;
|
||||
|
||||
/// If true, all objects will only be tested against the root
|
||||
/// frustum.
|
||||
bool mDisableZoneCulling;
|
||||
|
||||
public:
|
||||
|
||||
///
|
||||
SceneCullingState( SceneManager* sceneManager,
|
||||
const SceneCameraState& cameraState );
|
||||
|
||||
/// Return the scene which is being culled in this state.
|
||||
SceneManager* getSceneManager() const { return mSceneManager; }
|
||||
|
||||
/// Return the root frustum which is used to set up scene visibility.
|
||||
const Frustum& getFrustum() const { return getCameraState().getFrustum(); }
|
||||
|
||||
/// Return the viewing state that defines how the scene is being viewed.
|
||||
const SceneCameraState& getCameraState() const { return mCameraState; }
|
||||
|
||||
/// Return the root culling volume that corresponds to the camera frustum.
|
||||
/// @note This volume omits the near and far plane of the frustum's polyhedron
|
||||
/// as these will be tested separately during culling. Testing them repeatedly
|
||||
/// just wastes time.
|
||||
const SceneCullingVolume& getRootVolume() const { return mRootVolume; }
|
||||
|
||||
/// @name Visibility and Occlusion
|
||||
/// @{
|
||||
|
||||
enum CullOptions
|
||||
{
|
||||
/// Cull objects that have their SceneObject::DisableCullingInEditorFlag set.
|
||||
/// By default, these objects will not get culled if the editor is active.
|
||||
CullEditorOverrides = BIT( 0 ),
|
||||
|
||||
/// Do not cull objects that are render-disabled.
|
||||
/// @see SceneObject::isRenderEnabled()
|
||||
DontCullRenderDisabled = BIT( 1 )
|
||||
};
|
||||
|
||||
/// Cull the given list of objects according to the current culling state.
|
||||
///
|
||||
/// @param object Array of objects. This array will be modified in place.
|
||||
/// @param numObjects Number of objects in @a objects.
|
||||
/// @param cullOptions Combination of CullOptions.
|
||||
///
|
||||
/// @return Number of objects remaining in the list.
|
||||
U32 cullObjects( SceneObject** objects, U32 numObjects, U32 cullOptions = 0 ) const;
|
||||
|
||||
/// Return true if the given object is culled according to the current culling state.
|
||||
bool isCulled( SceneObject* object ) const { return ( cullObjects( &object, 1 ) == 0 ); }
|
||||
|
||||
/// Return true if the given AABB is culled in any of the given zones.
|
||||
bool isCulled( const Box3F& aabb, const U32* zones, U32 numZones ) const;
|
||||
|
||||
/// Return true if the given OBB is culled in any of the given zones.
|
||||
bool isCulled( const OrientedBox3F& obb, const U32* zones, U32 numZones ) const;
|
||||
|
||||
/// Return true if the given sphere is culled in any of the given zones.
|
||||
bool isCulled( const SphereF& sphere, const U32* zones, U32 numZones ) const;
|
||||
|
||||
/// Return true if the given object is occluded according to the current culling state.
|
||||
bool isOccluded( SceneObject* object ) const;
|
||||
|
||||
/// Return true if the given AABB is occluded according to the current culling state.
|
||||
bool isOccluded( const Box3F& aabb, const U32* zones, U32 numZones ) const;
|
||||
|
||||
/// Return true if the given OBB is occluded according to the current culling state.
|
||||
bool isOccluded( const OrientedBox3F& obb, const U32* zones, U32 numZones ) const;
|
||||
|
||||
/// Return true if the given sphere is occluded according to the current culling state.
|
||||
bool isOccluded( const SphereF& sphere, const U32* zones, U32 numZones ) const;
|
||||
|
||||
/// Add the occlusion information contained in the given object.
|
||||
///
|
||||
/// @note This should only be called after all positive frustums have been added
|
||||
/// to the zone state.
|
||||
void addOccluder( SceneObject* object );
|
||||
|
||||
/// Test whether the given object is occluded by any of the terrains
|
||||
/// in the scene.
|
||||
bool isOccludedByTerrain( SceneObject* object ) const;
|
||||
|
||||
/// Set whether isCulled() should do terrain occlusion checks or not.
|
||||
void setDisableTerrainOcclusion( bool value ) { mDisableTerrainOcclusion = value; }
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name Zones
|
||||
/// @{
|
||||
|
||||
/// If true, culling will only be performed against the root frustum
|
||||
/// and not against frustums of individual zones.
|
||||
///
|
||||
/// @note This also disables occluders as these are added to the zone frustums.
|
||||
bool disableZoneCulling() const { return mDisableZoneCulling; }
|
||||
void disableZoneCulling( bool value ) { mDisableZoneCulling = value; }
|
||||
|
||||
/// Return true if any of the zones that the object is currently are
|
||||
/// visible.
|
||||
bool isWithinVisibleZone( SceneObject* object ) const;
|
||||
|
||||
/// Return a bit vector with one bit for each zone in the scene. If the bit is set,
|
||||
/// the zone has includer culling volumes attached to it and thus is visible.
|
||||
const BitVector& getZoneVisibilityFlags() const { return mZoneVisibilityFlags; }
|
||||
|
||||
/// Return the culling state for a particular zone.
|
||||
/// @param zoneId Numeric ID of zone.
|
||||
const SceneZoneCullingState& getZoneState( U32 zoneId ) const
|
||||
{
|
||||
AssertFatal( zoneId < ( U32 ) mZoneStates.size(), "SceneCullingState::getZoneState - Index out of bounds" );
|
||||
return mZoneStates[ zoneId ];
|
||||
}
|
||||
|
||||
/// Returns the culling state for a particular zone.
|
||||
/// @param zoneId Numeric ID of zone.
|
||||
SceneZoneCullingState& getZoneState( U32 zoneId )
|
||||
{
|
||||
return const_cast< SceneZoneCullingState& >( static_cast< const SceneCullingState* >( this )->getZoneState( zoneId ) );
|
||||
}
|
||||
|
||||
/// Add a culling volume to the visibility state of the given zone.
|
||||
///
|
||||
/// @param zoneId ID of zone to which to add the given frustum's visibility information.
|
||||
/// @param volume A culling volume. Note that the data in the volume must have
|
||||
/// a lifetime at least as long as the culling state.
|
||||
///
|
||||
/// @return True if the visibility state of the zone has changed, i.e. if the volume
|
||||
/// was either added in whole or merged with an existing set of planes. If the visibility
|
||||
/// state of the zone has not changed, returns false.
|
||||
bool addCullingVolumeToZone( U32 zoneId, const SceneCullingVolume& volume );
|
||||
|
||||
/// Copy the data from the given polyhedron to the culling state, create
|
||||
/// a new culling volume it and add it to the current culling state of the given zone.
|
||||
///
|
||||
/// @param zoneId ID of zone to which to add the given frustum's visibility information.
|
||||
/// @param type Which type of culling volume to add.
|
||||
/// @param polyhedron Polyhedron describing the space of the culling volume.
|
||||
bool addCullingVolumeToZone( U32 zoneId, SceneCullingVolume::Type type, const AnyPolyhedron& polyhedron );
|
||||
|
||||
/// Create a new culling volume by extruding the given polygon away from the viewpoint.
|
||||
///
|
||||
/// @param vertices Array of polygon vertices.
|
||||
/// @param numVertices Number of vertices in @a vertices.
|
||||
/// @param type Type of culling volume to create.
|
||||
/// @param outVolume (out) Receives the generated volume, if successful.
|
||||
///
|
||||
/// @return True if a volume could be generated from the given polygon or false if not.
|
||||
bool createCullingVolume( const Point3F* vertices, U32 numVertices, SceneCullingVolume::Type type, SceneCullingVolume& outVolume );
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name Memory Management
|
||||
///
|
||||
/// Rather than allocating a lot of individual point and plane data for the culling volumes,
|
||||
/// it is more efficient to batch allocate chunks of memory and then release all the memory
|
||||
/// for all culling volumes in one go. This is facilitated by this interface.
|
||||
///
|
||||
/// @{
|
||||
|
||||
/// Allocate memory from this culling state. The memory is freed when the
|
||||
/// culling state is destroyed.
|
||||
void* allocateData( U32 size ) { return mDataChunker.alloc( size ); }
|
||||
|
||||
/// Allocate memory for @a num instances of T from this culling state.
|
||||
template< typename T >
|
||||
T* allocateData( U32 num ) { return reinterpret_cast< T* >( allocateData( sizeof( T ) * num ) ); }
|
||||
|
||||
/// @}
|
||||
|
||||
/// Queue debug visualizations of the culling volumes of all currently selected zones
|
||||
/// (or, if no zone is selected, all volumes in the outdoor zone) to the debug drawer.
|
||||
void debugRenderCullingVolumes() const;
|
||||
|
||||
private:
|
||||
|
||||
typedef SceneZoneCullingState::CullingTestResult CullingTestResult;
|
||||
|
||||
// Helper methods to avoid code duplication.
|
||||
|
||||
template< bool OCCLUDERS_ONLY, typename T > CullingTestResult _test( const T& bounds, const U32* zones, U32 numZones ) const;
|
||||
template< typename T, typename Iter > CullingTestResult _test
|
||||
( const T& bounds, Iter iter, const PlaneF& nearPlane, const PlaneF& farPlane ) const;
|
||||
template< typename T, typename Iter > CullingTestResult _testOccludersOnly( const T& bounds, Iter iter ) const;
|
||||
};
|
||||
|
||||
#endif // !_SCENECULLINGSTATE_H_
|
||||
24
Engine/source/scene/culling/sceneCullingVolume.cpp
Normal file
24
Engine/source/scene/culling/sceneCullingVolume.cpp
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "scene/culling/sceneCullingVolume.h"
|
||||
130
Engine/source/scene/culling/sceneCullingVolume.h
Normal file
130
Engine/source/scene/culling/sceneCullingVolume.h
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _SCENECULLINGVOLUME_H_
|
||||
#define _SCENECULLINGVOLUME_H_
|
||||
|
||||
#ifndef _MPLANESET_H_
|
||||
#include "math/mPlaneSet.h"
|
||||
#endif
|
||||
|
||||
|
||||
/// A volume used to include or exclude space in a scene.
|
||||
///
|
||||
/// Culling volumes are represented as sets of clipping planes.
|
||||
///
|
||||
/// @note Culling is performed in world space so the plane data for culling volumes
|
||||
/// must be in world space too.
|
||||
class SceneCullingVolume
|
||||
{
|
||||
public:
|
||||
|
||||
/// Type of culling.
|
||||
enum Type
|
||||
{
|
||||
Includer,
|
||||
Occluder,
|
||||
};
|
||||
|
||||
protected:
|
||||
|
||||
/// What type of culling volume this is.
|
||||
Type mType;
|
||||
|
||||
///
|
||||
F32 mSortPoint;
|
||||
|
||||
/// The set of clipping planes that defines the clipping volume for this culler.
|
||||
PlaneSetF mClippingPlanes;
|
||||
|
||||
/// Test the given bounds against this culling volume.
|
||||
///
|
||||
/// Note that we allow false positives here for includers. This will only cause an
|
||||
/// occasional object to be classified as intersecting when in fact it is outside.
|
||||
/// This is still better though than requiring the expensive intersection tests for
|
||||
/// all intersecting objects.
|
||||
///
|
||||
/// @return True if the culling volume accepts the given bounds.
|
||||
template< typename B > bool _testBounds( const B& bounds ) const
|
||||
{
|
||||
if( isOccluder() )
|
||||
return getPlanes().isContained( bounds );
|
||||
else
|
||||
return ( getPlanes().testPotentialIntersection( bounds ) != GeometryOutside );
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// Create an *uninitialized* culling volume.
|
||||
SceneCullingVolume() {}
|
||||
|
||||
///
|
||||
SceneCullingVolume( Type type, const PlaneSetF& planes )
|
||||
: mType( type ), mClippingPlanes( planes ), mSortPoint( 1.f ) {}
|
||||
|
||||
/// Return the type of volume defined by this culling volume, i.e. whether it includes
|
||||
/// or excludes space.
|
||||
Type getType() const { return mType; }
|
||||
|
||||
/// Return true if this is an inclusion volume.
|
||||
bool isIncluder() const { return ( getType() == Includer ); }
|
||||
|
||||
/// Return true if this is an occlusion volume.
|
||||
bool isOccluder() const { return ( getType() == Occluder ); }
|
||||
|
||||
/// Return the set of clipping planes that defines the culling volume.
|
||||
const PlaneSetF& getPlanes() const { return mClippingPlanes; }
|
||||
|
||||
/// @name Sorting
|
||||
///
|
||||
/// Before testing, culling volumes will be sorted by decreasing probability of causing
|
||||
/// test positives. Thus, the sort point of a volume should be a rough metric of the amount
|
||||
/// of scene/screen space it covers.
|
||||
///
|
||||
/// Note that sort points for occluders are independent of sort points for includers.
|
||||
/// @{
|
||||
|
||||
/// Return the sort point value of the volume. The larger the value, the more likely the
|
||||
/// volume is to cause positive test results with bounding volumes.
|
||||
F32 getSortPoint() const { return mSortPoint; }
|
||||
|
||||
///
|
||||
void setSortPoint( F32 value ) { mSortPoint = value; }
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name Testing
|
||||
/// @{
|
||||
|
||||
/// Return true if the volume accepts the given AABB.
|
||||
bool test( const Box3F& aabb ) const { return _testBounds( aabb ); }
|
||||
|
||||
/// Return true if the volume accepts the given OBB.
|
||||
bool test( const OrientedBox3F& obb ) const { return _testBounds( obb ); }
|
||||
|
||||
/// Return true if the volume accepts the given sphere.
|
||||
bool test( const SphereF& sphere ) const { return _testBounds( sphere ); }
|
||||
|
||||
/// @}
|
||||
};
|
||||
|
||||
#endif // !_SCENECULLINGVOLUME_H_
|
||||
235
Engine/source/scene/culling/sceneZoneCullingState.cpp
Normal file
235
Engine/source/scene/culling/sceneZoneCullingState.cpp
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "scene/culling/sceneZoneCullingState.h"
|
||||
|
||||
#include "scene/culling/sceneCullingState.h"
|
||||
#include "platform/profiler.h"
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
template< typename T >
|
||||
inline SceneZoneCullingState::CullingTestResult SceneZoneCullingState::_testVolumes( T bounds, bool occludersOnly ) const
|
||||
{
|
||||
// If we are testing for occlusion only and we don't have any
|
||||
// occlusion volumes, we can early out here.
|
||||
|
||||
if( occludersOnly && !mHaveOccluders )
|
||||
return CullingTestNegative;
|
||||
|
||||
// If we haven't sorted the volumes on this zone state yet,
|
||||
// do so now.
|
||||
|
||||
if( !mHaveSortedVolumes )
|
||||
_sortVolumes();
|
||||
|
||||
// Now go through the volumes in this zone and test them
|
||||
// against the bounds.
|
||||
|
||||
for( CullingVolumeLink* link = mCullingVolumes; link != NULL; link = link->mNext )
|
||||
{
|
||||
const SceneCullingVolume& volume = link->mVolume;
|
||||
|
||||
if( volume.isOccluder() )
|
||||
{
|
||||
if( volume.test( bounds ) )
|
||||
return CullingTestPositiveByOcclusion;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we are testing for occlusion only, we can early out as soon
|
||||
// as we have reached the first non-inverted volume.
|
||||
if( occludersOnly )
|
||||
return CullingTestNegative;
|
||||
|
||||
if( volume.test( bounds ) )
|
||||
return CullingTestPositiveByInclusion;
|
||||
}
|
||||
}
|
||||
|
||||
return CullingTestNegative;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
SceneZoneCullingState::CullingTestResult SceneZoneCullingState::testVolumes( const Box3F& aabb, bool invertedOnly ) const
|
||||
{
|
||||
PROFILE_SCOPE( SceneZoneCullingState_testVolumes );
|
||||
return _testVolumes( aabb, invertedOnly );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
SceneZoneCullingState::CullingTestResult SceneZoneCullingState::testVolumes( const OrientedBox3F& obb, bool invertedOnly ) const
|
||||
{
|
||||
PROFILE_SCOPE( SceneZoneCullingState_testVolumes_OBB );
|
||||
return _testVolumes( obb, invertedOnly );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
SceneZoneCullingState::CullingTestResult SceneZoneCullingState::testVolumes( const SphereF& sphere, bool invertedOnly ) const
|
||||
{
|
||||
PROFILE_SCOPE( SceneZoneCullingState_testVolumes_Sphere );
|
||||
return _testVolumes( sphere, invertedOnly );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void SceneZoneCullingState::_sortVolumes() const
|
||||
{
|
||||
// First do a pass to gather all occlusion volumes. These must be put on the
|
||||
// list in front of all inclusion volumes. Otherwise, an inclusion volume
|
||||
// may test positive when in fact an occlusion volume would reject the object.
|
||||
|
||||
CullingVolumeLink* occluderHead = NULL;
|
||||
CullingVolumeLink* occluderTail = NULL;
|
||||
|
||||
if( mHaveOccluders )
|
||||
{
|
||||
U32 numOccluders = 0;
|
||||
|
||||
for( CullingVolumeLink* current = mCullingVolumes, *prev = NULL; current != NULL; )
|
||||
{
|
||||
CullingVolumeLink* next = current->mNext;
|
||||
|
||||
if( !current->mVolume.isOccluder() )
|
||||
prev = current;
|
||||
else
|
||||
{
|
||||
// Unlink from list.
|
||||
|
||||
if( prev )
|
||||
prev->mNext = next;
|
||||
else
|
||||
mCullingVolumes = next;
|
||||
|
||||
// Sort into list.
|
||||
|
||||
_insertSorted( occluderHead, occluderTail, current );
|
||||
++ numOccluders;
|
||||
}
|
||||
|
||||
current = next;
|
||||
}
|
||||
|
||||
// If we ended up with more inverted (occlusion) volumes than we want,
|
||||
// chop off any but the first N volumes. Since we have sorted the volumes
|
||||
// by screen coverage, this will get rid of smallest occlusion volumes.
|
||||
|
||||
if( numOccluders > SceneCullingState::smMaxOccludersPerZone )
|
||||
{
|
||||
CullingVolumeLink* last = occluderHead;
|
||||
for( U32 i = 0; i < ( SceneCullingState::smMaxOccludersPerZone - 1 ); ++ i )
|
||||
last = last->mNext;
|
||||
|
||||
// Chop off rest. The links are allocated on the chunker
|
||||
// and thus will simply disappear when the state gets deleted.
|
||||
|
||||
last->mNext = NULL;
|
||||
occluderTail = last;
|
||||
}
|
||||
}
|
||||
|
||||
// Now, do a second pass to sort all includer volumes by decreasing screen
|
||||
// real estate so that when testing against them, we test the larger volumes first
|
||||
// and the smaller ones later.
|
||||
|
||||
CullingVolumeLink* includerHead = NULL;
|
||||
CullingVolumeLink* includerTail = NULL;
|
||||
|
||||
while( mCullingVolumes )
|
||||
{
|
||||
CullingVolumeLink* current = mCullingVolumes;
|
||||
|
||||
AssertFatal( !current->mVolume.isOccluder(),
|
||||
"SceneCullingState::ZoneState::_sortFrustums - Occluders must have been filtered out at this point" );
|
||||
|
||||
// Unlink from list.
|
||||
|
||||
mCullingVolumes = current->mNext;
|
||||
|
||||
// Sort into list.
|
||||
|
||||
_insertSorted( includerHead, includerTail, current );
|
||||
}
|
||||
|
||||
// Merge the two lists. Put inverted volumes first and
|
||||
// non-inverted volumes second.
|
||||
|
||||
if( occluderHead != NULL )
|
||||
{
|
||||
mCullingVolumes = occluderHead;
|
||||
occluderTail->mNext = includerHead;
|
||||
}
|
||||
else
|
||||
mCullingVolumes = includerHead;
|
||||
|
||||
// Done.
|
||||
|
||||
mHaveSortedVolumes = true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void SceneZoneCullingState::_insertSorted( CullingVolumeLink*& head, CullingVolumeLink*& tail, CullingVolumeLink* link )
|
||||
{
|
||||
// If first element, just put it in the list
|
||||
// and return.
|
||||
|
||||
if( !head )
|
||||
{
|
||||
head = link;
|
||||
tail = link;
|
||||
link->mNext = NULL;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, search for where to put it.
|
||||
|
||||
F32 sortPoint = link->mVolume.getSortPoint();
|
||||
|
||||
for( CullingVolumeLink* current = head, *prev = NULL; current != NULL; prev = current, current = current->mNext )
|
||||
{
|
||||
F32 currentSortPoint = current->mVolume.getSortPoint();
|
||||
if( currentSortPoint > sortPoint )
|
||||
continue;
|
||||
|
||||
if( !prev )
|
||||
head = link;
|
||||
else
|
||||
prev->mNext = link;
|
||||
|
||||
link->mNext = current;
|
||||
return;
|
||||
}
|
||||
|
||||
// Smallest frustum in list. Append to end.
|
||||
|
||||
tail->mNext = link;
|
||||
link->mNext = NULL;
|
||||
|
||||
tail = link;
|
||||
}
|
||||
171
Engine/source/scene/culling/sceneZoneCullingState.h
Normal file
171
Engine/source/scene/culling/sceneZoneCullingState.h
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _SCENEZONECULLINGSTATE_H_
|
||||
#define _SCENEZONECULLINGSTATE_H_
|
||||
|
||||
#ifndef _SCENECULLINGVOLUME_H_
|
||||
#include "scene/culling/sceneCullingVolume.h"
|
||||
#endif
|
||||
|
||||
|
||||
/// Culling state for a zone.
|
||||
///
|
||||
/// Zone states keep track of the culling volumes that are generated during traversal
|
||||
/// for a particular zone in a scene.
|
||||
///
|
||||
/// @note This class has no meaningful constructor; the memory for all zone states is
|
||||
/// cleared en bloc.
|
||||
class SceneZoneCullingState
|
||||
{
|
||||
public:
|
||||
|
||||
friend class SceneCullingState; // mCullingVolumes
|
||||
|
||||
/// Result of a culling test in a zone.
|
||||
enum CullingTestResult
|
||||
{
|
||||
/// An includer tested positive on the bounding volume.
|
||||
CullingTestPositiveByInclusion,
|
||||
|
||||
/// An occluder tested positive on the bounding volume.
|
||||
CullingTestPositiveByOcclusion,
|
||||
|
||||
/// None of the culling volumes included or excluded the bounding volume.
|
||||
CullingTestNegative
|
||||
};
|
||||
|
||||
/// A culling volume linked to a zone.
|
||||
///
|
||||
/// @note Memory for CullingVolumeLink instances is maintained by SceneCullingState.
|
||||
struct CullingVolumeLink
|
||||
{
|
||||
/// Culling volume.
|
||||
SceneCullingVolume mVolume;
|
||||
|
||||
/// Next culling volume linked to the zone.
|
||||
CullingVolumeLink* mNext;
|
||||
|
||||
CullingVolumeLink( const SceneCullingVolume& volume )
|
||||
: mVolume( volume ) {}
|
||||
};
|
||||
|
||||
/// Iterator over the culling volumes assigned to a zone.
|
||||
struct CullingVolumeIterator
|
||||
{
|
||||
CullingVolumeIterator( const SceneZoneCullingState& state )
|
||||
: mCurrent( state.getCullingVolumes() ) {}
|
||||
|
||||
bool isValid() const { return mCurrent != NULL; }
|
||||
const SceneCullingVolume& operator *() const
|
||||
{
|
||||
AssertFatal( isValid(), "SceneCullingState::ZoneState::CullingVolumeIterator::operator* - Invalid iterator" );
|
||||
return mCurrent->mVolume;
|
||||
}
|
||||
const SceneCullingVolume* operator ->() const
|
||||
{
|
||||
AssertFatal( isValid(), "SceneCullingState::ZoneState::CullingVolumeIterator::operator-> - Invalid iterator" );
|
||||
return &mCurrent->mVolume;
|
||||
}
|
||||
CullingVolumeIterator& operator ++()
|
||||
{
|
||||
AssertFatal( isValid(), "SceneCullingState::ZoneState::CullingVolumeIterator::operator++ - Invalid iterator" );
|
||||
mCurrent = mCurrent->mNext;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
CullingVolumeLink* mCurrent;
|
||||
};
|
||||
|
||||
protected:
|
||||
|
||||
/// Whether tests can be short-circuited, i.e. the first culler that rejects or accepts
|
||||
/// will cause the test to terminate. This is the case if there are no includers inside
|
||||
/// occluders, i.e. if occluders can be trusted to fully exclude any space they cover.
|
||||
bool mCanShortcuit;//RDTODO: implement this
|
||||
|
||||
/// Link of culling volumes defining the visibility state of the zone. Since there may be
|
||||
/// multiple portals leading into a zone or multiple occluders inside a zone, we may have multiple
|
||||
/// culling volumes.
|
||||
mutable CullingVolumeLink* mCullingVolumes;
|
||||
|
||||
/// Whether culling volumes for this zone state have already been sorted.
|
||||
mutable bool mHaveSortedVolumes;
|
||||
|
||||
/// Whether there are inclusion volumes on this state.
|
||||
bool mHaveIncluders;
|
||||
|
||||
/// Whether there are occlusion volumes on this state.
|
||||
bool mHaveOccluders;
|
||||
|
||||
/// Culling volume test abstracted over bounding volume type.
|
||||
template< typename T > CullingTestResult _testVolumes( T bounds, bool occludersOnly ) const;
|
||||
|
||||
/// Sort the culling volumes such that the volumes with the highest probability
|
||||
/// of rejecting objects come first in the list. Also, make sure that all
|
||||
/// occluders come before all includers so that occlusion is handled correctly.
|
||||
void _sortVolumes() const;
|
||||
|
||||
/// Insert the volume in @a link at the proper position in the list represented
|
||||
/// by @a head and @a tail.
|
||||
static void _insertSorted( CullingVolumeLink*& head, CullingVolumeLink*& tail, CullingVolumeLink* link );
|
||||
|
||||
public:
|
||||
|
||||
/// Zone states are constructed by SceneCullingState. This constructor should not
|
||||
/// be used otherwise. It is public due to the use through Vector in SceneCullingState.
|
||||
SceneZoneCullingState() {}
|
||||
|
||||
/// Return true if the zone is visible. This is the case if any
|
||||
/// includers have been added to the zone's rendering state.
|
||||
bool isZoneVisible() const { return mHaveIncluders; }
|
||||
|
||||
/// Return the list of culling volumes attached to the zone.
|
||||
CullingVolumeLink* getCullingVolumes() const { _sortVolumes(); return mCullingVolumes; }
|
||||
|
||||
/// Test whether the culling volumes added to the zone test positive on the
|
||||
/// given AABB, i.e. whether they include or exclude the given AABB.
|
||||
CullingTestResult testVolumes( const Box3F& aabb, bool occludersOnly = false ) const;
|
||||
|
||||
/// Test whether the culling volumes added to the zone test positive on the
|
||||
/// given OBB, i.e. whether they include or exclude the given OBB.
|
||||
///
|
||||
/// @param obb An OBB described by 8 points.
|
||||
/// @param invertedOnly If true, only inverted cullers are tested.
|
||||
CullingTestResult testVolumes( const OrientedBox3F& obb, bool occludersOnly = false ) const;
|
||||
|
||||
/// Test whether the culling volumes added to the zone test positive on the
|
||||
/// given sphere, i.e. whether they include or exclude the given sphere.
|
||||
CullingTestResult testVolumes( const SphereF& sphere, bool occludersOnly = false ) const;
|
||||
|
||||
/// Return true if the zone has more than one culling volume assigned to it.
|
||||
bool hasMultipleVolumes() const { return ( mCullingVolumes && mCullingVolumes->mNext ); }
|
||||
|
||||
/// Return true if the zone has inclusion volumes assigned to it.
|
||||
bool hasIncluders() const { return mHaveIncluders; }
|
||||
|
||||
/// Return true if the zone has occlusion volumes assigned to it.
|
||||
bool hasOccluders() const { return mHaveOccluders; }
|
||||
};
|
||||
|
||||
#endif // !_SCENEZONECULLINGSTATE_H_
|
||||
Loading…
Add table
Add a link
Reference in a new issue