Engine directory for ticket #1

This commit is contained in:
DavidWyand-GG 2012-09-19 11:15:01 -04:00
parent 352279af7a
commit 7dbfe6994d
3795 changed files with 1363358 additions and 0 deletions

View 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 );
}
}
}

View 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_

View 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"

View 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_

View 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;
}

View 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_

View file

@ -0,0 +1,67 @@
//-----------------------------------------------------------------------------
// 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 _FOGSTRUCTS_H_
#define _FOGSTRUCTS_H_
/// The aerial fog settings.
struct FogData
{
F32 density;
F32 densityOffset;
F32 atmosphereHeight;
ColorF color;
FogData()
{
density = 0.0f;
densityOffset = 0.0f;
atmosphereHeight = 0.0f;
color.set( 0.5f, 0.5f, 0.5f, 1.0f );
}
};
/// The water fog settings.
struct WaterFogData
{
F32 density;
F32 densityOffset;
F32 wetDepth;
F32 wetDarkening;
ColorI color;
PlaneF plane;
F32 depthGradMax;
WaterFogData()
{
density = 0.0f;
densityOffset = 0.0f;
wetDepth = 0.0f;
wetDarkening = 0.0f;
color.set( 0.5f, 0.5f, 0.5f, 1.0f );
plane.set( 0.0f, 0.0f, 1.0f );
depthGradMax = 0.0f;
}
};
#endif // _FOGSTRUCTS_H_

View file

@ -0,0 +1,73 @@
//-----------------------------------------------------------------------------
// 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 _SCENEAMBIENTSOUNDOBJECT_H_
#define _SCENEAMBIENTSOUNDOBJECT_H_
class SFXAmbience;
/// Template mixin to add ability to hold a custom SFXAmbience to
/// a SceneObject.
template< typename Base >
class SceneAmbientSoundObject : public Base
{
public:
typedef Base Parent;
protected:
enum
{
SoundMask = Parent::NextFreeMask << 0, ///< Ambient sound properties have changed.
NextFreeMask = Parent::NextFreeMask << 1,
};
/// Ambient sound properties for this space.
SFXAmbience* mSoundAmbience;
public:
SceneAmbientSoundObject();
/// Set the ambient sound properties for the space.
void setSoundAmbience( SFXAmbience* ambience );
// SimObject.
static void initPersistFields();
// NetObject.
virtual U32 packUpdate( NetConnection* connection, U32 mask, BitStream* stream );
virtual void unpackUpdate( NetConnection* connection, BitStream* stream );
// SceneObject.
virtual SFXAmbience* getSoundAmbience() const { return mSoundAmbience; }
private:
// Console field getters/setters.
static bool _setSoundAmbience( void* object, const char* index, const char* data );
};
#endif // !_SCENEAMBIENTSOUNDOBJECT_H_

View file

@ -0,0 +1,114 @@
//-----------------------------------------------------------------------------
// 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/mixin/sceneAmbientSoundObject.h"
#include "T3D/sfx/sfx3DWorld.h"
#include "sfx/sfxTypes.h"
#include "sfx/sfxAmbience.h"
#include "core/stream/bitStream.h"
#include "console/engineAPI.h"
//-----------------------------------------------------------------------------
template< typename Base >
SceneAmbientSoundObject< Base >::SceneAmbientSoundObject()
: mSoundAmbience( NULL )
{
}
//-----------------------------------------------------------------------------
template< typename Base >
void SceneAmbientSoundObject< Base >::initPersistFields()
{
Parent::addGroup( "Sound" );
Parent::addProtectedField( "soundAmbience", TypeSFXAmbienceName, Offset( mSoundAmbience, SceneAmbientSoundObject ),
&_setSoundAmbience, &defaultProtectedGetFn,
"Ambient sound environment for the space." );
Parent::endGroup( "Sound" );
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
template< typename Base >
U32 SceneAmbientSoundObject< Base >::packUpdate( NetConnection* connection, U32 mask, BitStream* stream )
{
U32 retMask = Parent::packUpdate( connection, mask, stream );
if( stream->writeFlag( mask & SoundMask ) )
sfxWrite( stream, mSoundAmbience );
return retMask;
}
//-----------------------------------------------------------------------------
template< typename Base >
void SceneAmbientSoundObject< Base >::unpackUpdate( NetConnection* connection, BitStream* stream )
{
Parent::unpackUpdate( connection, stream );
if( stream->readFlag() ) // SoundMask
{
SFXAmbience* ambience;
String errorStr;
if( !sfxReadAndResolve( stream, &ambience, errorStr ) )
Con::errorf( "SceneAmbientSoundObject::unpackUpdate - bad packet: %s", errorStr.c_str() );
else
setSoundAmbience( ambience );
}
}
//-----------------------------------------------------------------------------
template< typename Base >
void SceneAmbientSoundObject< Base >::setSoundAmbience( SFXAmbience* ambience )
{
if( mSoundAmbience == ambience )
return;
mSoundAmbience = ambience;
if( this->isServerObject() )
this->setMaskBits( SoundMask );
else if( this->isProperlyAdded() && gSFX3DWorld )
gSFX3DWorld->notifyChanged( this );
}
//-----------------------------------------------------------------------------
template< typename Base >
bool SceneAmbientSoundObject< Base >::_setSoundAmbience( void* object, const char* index, const char* data )
{
SceneAmbientSoundObject* p = reinterpret_cast< SceneAmbientSoundObject* >( object );
SFXAmbience* ambience = EngineUnmarshallData< SFXAmbience* >()( data );
p->setSoundAmbience( ambience );
return false;
}

View file

@ -0,0 +1,116 @@
//-----------------------------------------------------------------------------
// 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 _SCENEPOLYHEDRALOBJECT_H_
#define _SCENEPOLYHEDRALOBJECT_H_
#ifndef _MPOLYHEDRON_H_
#include "math/mPolyhedron.h"
#endif
/// Shared interface for polyhedral objects.
struct IScenePolyhedralObject
{
/// Convert the polyhedral object to a raw polyhedron.
virtual AnyPolyhedron ToAnyPolyhedron() const = 0;
};
/// Helper template for mixing a polyhedral volume definition into
/// the superclass hierarchy of a SceneSpace-derived class.
template< typename Base, typename P = Polyhedron >
class ScenePolyhedralObject : public Base, public IScenePolyhedralObject
{
public:
typedef Base Parent;
typedef P PolyhedronType;
enum
{
MAX_PLANES = 256,
MAX_POINTS = 256,
MAX_EDGES = 256
};
protected:
enum
{
PolyMask = Parent::NextFreeMask << 0,
NextFreeMask = Parent::NextFreeMask << 1
};
/// Whether the polyhedron corresponds to the object box. If so,
/// several things can be fast-tracked. For example, serializing the
/// polyhedron is pointless as it can be easily reconstructed from the
/// object box on load time. Also, certain operations like containment
/// tests have significantly faster formulations for AABBs (given that the
/// input data is transformed into object space) than for general
/// polyhedrons.
bool mIsBox;
/// The polyhedron that defines the volume of the object.
/// @note Defined in object space by default.
PolyhedronType mPolyhedron;
///
virtual void _renderObject( ObjectRenderInst* ri, SceneRenderState* state, BaseMatInstance* overrideMat );
public:
ScenePolyhedralObject()
: mIsBox( true ) {}
ScenePolyhedralObject( const PolyhedronType& polyhedron )
: mIsBox( false ),
mPolyhedron( polyhedron ) {}
/// Return the polyhedron that describes the space.
const PolyhedronType& getPolyhedron() const { return mPolyhedron; }
// SimObject.
virtual bool onAdd();
virtual void writeFields( Stream& stream, U32 tabStop );
virtual bool writeField( StringTableEntry name, const char* value );
static void initPersistFields();
// NetObject.
virtual U32 packUpdate( NetConnection* connection, U32 mask, BitStream* stream );
virtual void unpackUpdate( NetConnection* connection, BitStream* stream );
// SceneObject.
virtual bool containsPoint( const Point3F& point );
// IScenePolyhedralObject.
virtual AnyPolyhedron ToAnyPolyhedron() const { return getPolyhedron(); }
private:
static bool _setPlane( void* object, const char* index, const char* data );
static bool _setPoint( void* object, const char* index, const char* data );
static bool _setEdge( void* object, const char* index, const char* data );
};
#endif // !_SCENEPOLYHEDRALOBJECT_H_

View file

@ -0,0 +1,395 @@
//-----------------------------------------------------------------------------
// 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/mixin/scenePolyhedralObject.h"
#include "console/consoleTypes.h"
#include "gfx/gfxDrawUtil.h"
#include "gfx/gfxTransformSaver.h"
#include "core/stream/bitStream.h"
#include "math/mathIO.h"
#if 0 // Enable when enabling debug rendering below.
#include "scene/sceneRenderState.h"
#include "gfx/sim/debugDraw.h"
#endif
//-----------------------------------------------------------------------------
template< typename Base, typename P >
void ScenePolyhedralObject< Base, P >::initPersistFields()
{
Parent::addGroup( "Internal" );
Parent::addProtectedField( "plane", TypeRealString, NULL,
&_setPlane, &defaultProtectedGetFn,
"For internal use only.",
AbstractClassRep::FIELD_HideInInspectors );
Parent::addProtectedField( "point", TypeRealString, NULL,
&_setPoint, &defaultProtectedGetFn,
"For internal use only.",
AbstractClassRep::FIELD_HideInInspectors );
Parent::addProtectedField( "edge", TypeRealString, NULL,
&_setEdge, &defaultProtectedGetFn,
"For internal use only.",
AbstractClassRep::FIELD_HideInInspectors );
Parent::endGroup( "Internal" );
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
template< typename Base, typename P >
bool ScenePolyhedralObject< Base, P >::onAdd()
{
// If no polyhedron has been initialized for the zone, default
// to object box. Do this before calling the parent's onAdd()
// so that we set the object box correctly.
if( mPolyhedron.getNumPlanes() == 0 )
{
mPolyhedron.buildBox( MatrixF::Identity, this->getObjBox() );
mIsBox = true;
}
else
{
mIsBox = false;
// Compute object-space bounds from polyhedron.
this->mObjBox = mPolyhedron.getBounds();
}
if( !Parent::onAdd() )
return false;
return true;
}
//-----------------------------------------------------------------------------
template< typename Base, typename P >
bool ScenePolyhedralObject< Base, P >::containsPoint( const Point3F& point )
{
// If our shape is the OBB, use the default implementation
// inherited from SceneObject.
if( this->mIsBox )
return Parent::containsPoint( point );
// Take the point into our local object space.
Point3F p = point;
this->getWorldTransform().mulP( p );
p.convolveInverse( this->getScale() );
// See if the polyhedron contains the point.
return mPolyhedron.isContained( p );
}
//-----------------------------------------------------------------------------
template< typename Base, typename P >
void ScenePolyhedralObject< Base, P >::_renderObject( ObjectRenderInst* ri, SceneRenderState* state, BaseMatInstance* overrideMat )
{
if( overrideMat )
return;
if( this->mIsBox )
Parent::_renderObject( ri, state, overrideMat );
else if( !this->mEditorRenderMaterial )
{
GFXTransformSaver saver;
MatrixF mat = this->getRenderTransform();
mat.scale( this->getScale() );
GFX->multWorld( mat );
GFXStateBlockDesc desc;
desc.setZReadWrite( true, false );
desc.setBlend( true );
desc.setCullMode( GFXCullNone );
GFX->getDrawUtil()->drawPolyhedron( desc, mPolyhedron, this->_getDefaultEditorSolidColor() );
// Render black wireframe.
desc.setFillModeWireframe();
GFX->getDrawUtil()->drawPolyhedron( desc, mPolyhedron, this->_getDefaultEditorWireframeColor() );
}
else
{
//TODO: render polyhedron with material
}
// Debug rendering.
#if 0
if( state->isDiffusePass() )
DebugDrawer::get()->drawPolyhedronDebugInfo( mPolyhedron, this->getTransform(), this->getScale() );
#endif
}
//-----------------------------------------------------------------------------
template< typename Base, typename P >
U32 ScenePolyhedralObject< Base, P >::packUpdate( NetConnection* connection, U32 mask, BitStream* stream )
{
U32 retMask = Parent::packUpdate( connection, mask, stream );
if( stream->writeFlag( !mIsBox && ( mask & PolyMask ) ) )
{
// Write planes.
const U32 numPlanes = mPolyhedron.getNumPlanes();
const typename PolyhedronType::PlaneType* planes = mPolyhedron.getPlanes();
stream->writeInt( numPlanes, 8 );
for( U32 i = 0; i < numPlanes; ++ i )
mathWrite( *stream, planes[ i ] );
// Write points.
const U32 numPoints = mPolyhedron.getNumPoints();
const typename PolyhedronType::PointType* points = mPolyhedron.getPoints();
stream->writeInt( numPoints, 8 );
for( U32 i = 0; i < numPoints; ++ i )
mathWrite( *stream, points[ i ] );
// Write edges.
const U32 numEdges = mPolyhedron.getNumEdges();
const typename PolyhedronType::EdgeType* edges = mPolyhedron.getEdges();
stream->writeInt( numEdges, 8 );
for( U32 i = 0; i < numEdges; ++ i )
{
const typename PolyhedronType::EdgeType& edge = edges[ i ];
stream->writeInt( edge.face[ 0 ], 8 );
stream->writeInt( edge.face[ 1 ], 8 );
stream->writeInt( edge.vertex[ 0 ], 8 );
stream->writeInt( edge.vertex[ 1 ], 8 );
}
}
return retMask;
}
//-----------------------------------------------------------------------------
template< typename Base, typename P >
void ScenePolyhedralObject< Base, P >::unpackUpdate( NetConnection* connection, BitStream* stream )
{
Parent::unpackUpdate( connection, stream );
if( stream->readFlag() ) // PolyMask
{
// Read planes.
const U32 numPlanes = stream->readInt( 8 );
mPolyhedron.planeList.setSize( numPlanes );
for( U32 i = 0; i < numPlanes; ++ i )
mathRead( *stream, &mPolyhedron.planeList[ i ] );
// Read points.
const U32 numPoints = stream->readInt( 8 );
mPolyhedron.pointList.setSize( numPoints );
for( U32 i = 0; i < numPoints; ++ i )
mathRead( *stream, &mPolyhedron.pointList[ i ] );
// Read edges.
const U32 numEdges = stream->readInt( 8 );
mPolyhedron.edgeList.setSize( numEdges );
for( U32 i = 0; i < numEdges; ++ i )
{
typename PolyhedronType::EdgeType& edge = mPolyhedron.edgeList[ i ];
edge.face[ 0 ] = stream->readInt( 8 );
edge.face[ 1 ] = stream->readInt( 8 );
edge.vertex[ 0 ] = stream->readInt( 8 );
edge.vertex[ 1 ] = stream->readInt( 8 );
}
}
}
//-----------------------------------------------------------------------------
template< typename Base, typename P >
bool ScenePolyhedralObject< Base, P >::writeField( StringTableEntry name, const char* value )
{
StringTableEntry sPlane = StringTable->insert( "plane" );
StringTableEntry sPoint = StringTable->insert( "point" );
StringTableEntry sEdge = StringTable->insert( "edge" );
if( name == sPlane || name == sPoint || name == sEdge )
return false;
return Parent::writeField( name, value );
}
//-----------------------------------------------------------------------------
template< typename Base, typename P >
void ScenePolyhedralObject< Base, P >::writeFields( Stream& stream, U32 tabStop )
{
Parent::writeFields( stream, tabStop );
// If the polyhedron is the same as our object box,
// don't bother writing out the planes and points.
if( mIsBox )
return;
stream.write( 2, "\r\n" );
// Write all planes.
const U32 numPlanes = mPolyhedron.getNumPlanes();
for( U32 i = 0; i < numPlanes; ++ i )
{
const PlaneF& plane = mPolyhedron.getPlanes()[ i ];
stream.writeTabs( tabStop );
char buffer[ 1024 ];
dSprintf( buffer, sizeof( buffer ), "plane = \"%g %g %g %g\";",
plane.x, plane.y, plane.z, plane.d
);
stream.writeLine( reinterpret_cast< const U8* >( buffer ) );
}
// Write all points.
const U32 numPoints = mPolyhedron.getNumPoints();
for( U32 i = 0; i < numPoints; ++ i )
{
const Point3F& point = mPolyhedron.getPoints()[ i ];
stream.writeTabs( tabStop );
char buffer[ 1024 ];
dSprintf( buffer, sizeof( buffer ), "point = \"%g %g %g\";",
point.x, point.y, point.z
);
stream.writeLine( reinterpret_cast< const U8* >( buffer ) );
}
// Write all edges.
const U32 numEdges = mPolyhedron.getNumEdges();
for( U32 i = 0; i < numEdges; ++ i )
{
const PolyhedronData::Edge& edge = mPolyhedron.getEdges()[ i ];
stream.writeTabs( tabStop );
char buffer[ 1024 ];
dSprintf( buffer, sizeof( buffer ), "edge = \"%i %i %i %i\";",
edge.face[ 0 ], edge.face[ 1 ],
edge.vertex[ 0 ], edge.vertex[ 1 ]
);
stream.writeLine( reinterpret_cast< const U8* >( buffer ) );
}
}
//-----------------------------------------------------------------------------
template< typename Base, typename P >
bool ScenePolyhedralObject< Base, P >::_setPlane( void* object, const char* index, const char* data )
{
ScenePolyhedralObject* obj = reinterpret_cast< ScenePolyhedralObject* >( object );
PlaneF plane;
dSscanf( data, "%g %g %g %g",
&plane.x,
&plane.y,
&plane.z,
&plane.d
);
obj->mPolyhedron.planeList.push_back( plane );
obj->setMaskBits( PolyMask );
obj->mIsBox = false;
return false;
}
//-----------------------------------------------------------------------------
template< typename Base, typename P >
bool ScenePolyhedralObject< Base, P >::_setPoint( void* object, const char* index, const char* data )
{
ScenePolyhedralObject* obj = reinterpret_cast< ScenePolyhedralObject* >( object );
Point3F point;
dSscanf( data, "%g %g %g %g",
&point[ 0 ],
&point[ 1 ],
&point[ 2 ]
);
obj->mPolyhedron.pointList.push_back( point );
obj->setMaskBits( PolyMask );
obj->mIsBox = false;
return false;
}
//-----------------------------------------------------------------------------
template< typename Base, typename P >
bool ScenePolyhedralObject< Base, P >::_setEdge( void* object, const char* index, const char* data )
{
ScenePolyhedralObject* obj = reinterpret_cast< ScenePolyhedralObject* >( object );
PolyhedronData::Edge edge;
dSscanf( data, "%i %i %i %i",
&edge.face[ 0 ],
&edge.face[ 1 ],
&edge.vertex[ 0 ],
&edge.vertex[ 1 ]
);
obj->mPolyhedron.edgeList.push_back( edge );
obj->setMaskBits( PolyMask );
obj->mIsBox = false;
return false;
}

View file

@ -0,0 +1,434 @@
//-----------------------------------------------------------------------------
// 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 "gfx/gfxDevice.h"
#include "scene/pathManager.h"
#include "sim/netConnection.h"
#include "core/stream/bitStream.h"
#include "scene/simPath.h"
#include "interior/interiorInstance.h"
#include "math/mathIO.h"
#include "scene/sceneRenderState.h"
#include "scene/sceneManager.h"
#include "platform/profiler.h"
#include "core/module.h"
extern bool gEditingMission;
namespace {
U32 countNumBits(U32 n)
{
U32 count = 0;
while (n != 0) {
n >>= 1;
count++;
}
return count ? count : 1;
}
} // namespace {}
MODULE_BEGIN( PathManager )
MODULE_INIT
{
AssertFatal(gClientPathManager == NULL && gServerPathManager == NULL, "Error, already initialized the path manager!");
gClientPathManager = new PathManager(false);
gServerPathManager = new PathManager(true);
}
MODULE_SHUTDOWN
{
AssertFatal(gClientPathManager != NULL && gServerPathManager != NULL, "Error, path manager not initialized!");
delete gClientPathManager;
gClientPathManager = NULL;
delete gServerPathManager;
gServerPathManager = NULL;
}
MODULE_END;
//--------------------------------------------------------------------------
//-------------------------------------- PathManagerEvent
//
class PathManagerEvent : public NetEvent
{
public:
U32 modifiedPath;
bool clearPaths;
PathManager::PathEntry path;
public:
typedef NetEvent Parent;
PathManagerEvent() { }
void pack(NetConnection*, BitStream*);
void write(NetConnection*, BitStream*);
void unpack(NetConnection*, BitStream*);
void process(NetConnection*);
DECLARE_CONOBJECT(PathManagerEvent);
};
void PathManagerEvent::pack(NetConnection*, BitStream* stream)
{
// Write out the modified path...
stream->write(modifiedPath);
stream->writeFlag(clearPaths);
stream->write(path.totalTime);
stream->write(path.positions.size());
// This is here for safety. You can remove it if you want to try your luck at bigger sizes. -- BJG
AssertWarn(path.positions.size() < 1500/40, "Warning! Path size is pretty big - may cause packet overrun!");
// Each one of these is about 8 floats and 2 ints
// so we'll say it's about 40 bytes in size, which is where the 40 in the above calc comes from.
for (U32 j = 0; j < path.positions.size(); j++)
{
mathWrite(*stream, path.positions[j]);
mathWrite(*stream, path.rotations[j]);
stream->write(path.msToNext[j]);
stream->write(path.smoothingType[j]);
}
}
void PathManagerEvent::write(NetConnection*nc, BitStream *stream)
{
pack(nc, stream);
}
void PathManagerEvent::unpack(NetConnection*, BitStream* stream)
{
// Read in the modified path...
stream->read(&modifiedPath);
clearPaths = stream->readFlag();
stream->read(&path.totalTime);
U32 numPoints;
stream->read(&numPoints);
path.positions.setSize(numPoints);
path.rotations.setSize(numPoints);
path.msToNext.setSize(numPoints);
path.smoothingType.setSize(numPoints);
for (U32 j = 0; j < path.positions.size(); j++)
{
mathRead(*stream, &path.positions[j]);
mathRead(*stream, &path.rotations[j]);
stream->read(&path.msToNext[j]);
stream->read(&path.smoothingType[j]);
}
}
void PathManagerEvent::process(NetConnection*)
{
if (clearPaths)
{
// Clear out all the client's paths...
gClientPathManager->clearPaths();
}
AssertFatal(modifiedPath <= gClientPathManager->mPaths.size(), "Error out of bounds path!");
if (modifiedPath == gClientPathManager->mPaths.size()) {
PathManager::PathEntry *pe = new PathManager::PathEntry;
*pe = path;
gClientPathManager->mPaths.push_back(pe);
}
else
*(gClientPathManager->mPaths[modifiedPath]) = path;
}
IMPLEMENT_CO_NETEVENT_V1(PathManagerEvent);
// Will be internalized once the @internal tag is working
ConsoleDocClass( PathManagerEvent,
"@brief Class responsible for the registration, transmission, and management "
"of paths on client and server.\n\n"
"For internal use only, not intended for use in TorqueScript or game development\n\n"
"@internal\n"
);
//--------------------------------------------------------------------------
//-------------------------------------- PathManager Implementation
//
PathManager* gClientPathManager = NULL;
PathManager* gServerPathManager = NULL;
//--------------------------------------------------------------------------
PathManager::PathManager(const bool isServer)
{
VECTOR_SET_ASSOCIATION(mPaths);
mIsServer = isServer;
}
PathManager::~PathManager()
{
clearPaths();
}
void PathManager::clearPaths()
{
for (U32 i = 0; i < mPaths.size(); i++)
delete mPaths[i];
mPaths.setSize(0);
#ifdef TORQUE_DEBUG
// This gets rid of the memory used by the vector.
// Prevents it from showing up in memory leak logs.
mPaths.compact();
#endif
}
ConsoleFunction(clearServerPaths, void, 1, 1, "")
{
gServerPathManager->clearPaths();
}
ConsoleFunction(clearClientPaths, void, 1, 1, "")
{
gClientPathManager->clearPaths();
}
//--------------------------------------------------------------------------
U32 PathManager::allocatePathId()
{
mPaths.increment();
mPaths.last() = new PathEntry;
return (mPaths.size() - 1);
}
void PathManager::updatePath(const U32 id,
const Vector<Point3F>& positions,
const Vector<QuatF>& rotations,
const Vector<U32>& times,
const Vector<U32>& smoothingTypes)
{
AssertFatal(mIsServer == true, "PathManager::updatePath: Error, must be called on the server side");
AssertFatal(id < mPaths.size(), "PathManager::updatePath: error, id out of range");
AssertFatal(positions.size() == times.size() && positions.size() == smoothingTypes.size(), "Error, times and positions must match!");
PathEntry& rEntry = *mPaths[id];
rEntry.positions = positions;
rEntry.rotations = rotations;
rEntry.msToNext = times;
rEntry.smoothingType = smoothingTypes;
rEntry.totalTime = 0;
for (S32 i = 0; i < S32(rEntry.msToNext.size()); i++)
rEntry.totalTime += rEntry.msToNext[i];
transmitPath(id);
}
//--------------------------------------------------------------------------
void PathManager::transmitPaths(NetConnection* nc)
{
AssertFatal(mIsServer, "Error, cannot call transmitPaths on client path manager!");
// Send over paths
for(S32 i = 0; i < mPaths.size(); i++)
{
PathManagerEvent* event = new PathManagerEvent;
event->clearPaths = (i == 0);
event->modifiedPath = i;
event->path = *(mPaths[i]);
nc->postNetEvent(event);
}
}
void PathManager::transmitPath(const U32 id)
{
AssertFatal(mIsServer, "Error, cannot call transmitNewPath on client path manager!");
// Post to all active clients that have already received their paths...
//
SimGroup* pClientGroup = Sim::getClientGroup();
for (SimGroup::iterator itr = pClientGroup->begin(); itr != pClientGroup->end(); itr++) {
NetConnection* nc = dynamic_cast<NetConnection*>(*itr);
if (nc && nc->missionPathsSent())
{
// Transmit the updated path...
PathManagerEvent* event = new PathManagerEvent;
event->modifiedPath = id;
event->clearPaths = false;
event->path = *(mPaths[id]);
nc->postNetEvent(event);
}
}
}
void PathManager::getPathPosition(const U32 id,
const F64 msPosition,
Point3F& rPosition,
QuatF &rotation)
{
AssertFatal(isValidPath(id), "Error, this is not a valid path!");
PROFILE_START(PathManGetPos);
// Ok, query holds our path information...
F64 ms = msPosition;
if (ms > mPaths[id]->totalTime)
ms = mPaths[id]->totalTime;
S32 startNode = 0;
while (ms > mPaths[id]->msToNext[startNode]) {
ms -= mPaths[id]->msToNext[startNode];
startNode++;
}
S32 endNode = (startNode + 1) % mPaths[id]->positions.size();
Point3F& rStart = mPaths[id]->positions[startNode];
Point3F& rEnd = mPaths[id]->positions[endNode];
F64 interp = ms / F32(mPaths[id]->msToNext[startNode]);
if(mPaths[id]->smoothingType[startNode] == Marker::SmoothingTypeLinear)
{
rPosition = (rStart * (1.0 - interp)) + (rEnd * interp);
}
else if(mPaths[id]->smoothingType[startNode] == Marker::SmoothingTypeAccelerate)
{
interp = mSin(interp * M_PI - (M_PI / 2)) * 0.5 + 0.5;
rPosition = (rStart * (1.0 - interp)) + (rEnd * interp);
}
else if(mPaths[id]->smoothingType[startNode] == Marker::SmoothingTypeSpline)
{
S32 preStart = startNode - 1;
S32 postEnd = endNode + 1;
if(postEnd >= mPaths[id]->positions.size())
postEnd = 0;
if(preStart < 0)
preStart = mPaths[id]->positions.size() - 1;
Point3F p0 = mPaths[id]->positions[preStart];
Point3F p1 = rStart;
Point3F p2 = rEnd;
Point3F p3 = mPaths[id]->positions[postEnd];
rPosition.x = mCatmullrom(interp, p0.x, p1.x, p2.x, p3.x);
rPosition.y = mCatmullrom(interp, p0.y, p1.y, p2.y, p3.y);
rPosition.z = mCatmullrom(interp, p0.z, p1.z, p2.z, p3.z);
}
rotation.interpolate( mPaths[id]->rotations[startNode], mPaths[id]->rotations[endNode], interp );
PROFILE_END();
}
U32 PathManager::getPathTotalTime(const U32 id) const
{
AssertFatal(isValidPath(id), "Error, this is not a valid path!");
return mPaths[id]->totalTime;
}
U32 PathManager::getPathNumWaypoints(const U32 id) const
{
AssertFatal(isValidPath(id), "Error, this is not a valid path!");
return mPaths[id]->positions.size();
}
U32 PathManager::getWaypointTime(const U32 id, const U32 wayPoint) const
{
AssertFatal(isValidPath(id), "Error, this is not a valid path!");
AssertFatal(wayPoint < getPathNumWaypoints(id), "Invalid waypoint!");
U32 time = 0;
for (U32 i = 0; i < wayPoint; i++)
time += mPaths[id]->msToNext[i];
return time;
}
U32 PathManager::getPathTimeBits(const U32 id)
{
AssertFatal(isValidPath(id), "Error, this is not a valid path!");
return countNumBits(mPaths[id]->totalTime);
}
U32 PathManager::getPathWaypointBits(const U32 id)
{
AssertFatal(isValidPath(id), "Error, this is not a valid path!");
return countNumBits(mPaths[id]->positions.size());
}
bool PathManager::dumpState(BitStream* stream) const
{
stream->write(mPaths.size());
for (U32 i = 0; i < mPaths.size(); i++) {
const PathEntry& rEntry = *mPaths[i];
stream->write(rEntry.totalTime);
stream->write(rEntry.positions.size());
for (U32 j = 0; j < rEntry.positions.size(); j++) {
mathWrite(*stream, rEntry.positions[j]);
stream->write(rEntry.msToNext[j]);
}
}
return stream->getStatus() == Stream::Ok;
}
bool PathManager::readState(BitStream* stream)
{
U32 i;
for (i = 0; i < mPaths.size(); i++)
delete mPaths[i];
U32 numPaths;
stream->read(&numPaths);
mPaths.setSize(numPaths);
for (i = 0; i < mPaths.size(); i++) {
mPaths[i] = new PathEntry;
PathEntry& rEntry = *mPaths[i];
stream->read(&rEntry.totalTime);
U32 numPositions;
stream->read(&numPositions);
rEntry.positions.setSize(numPositions);
rEntry.msToNext.setSize(numPositions);
for (U32 j = 0; j < rEntry.positions.size(); j++) {
mathRead(*stream, &rEntry.positions[j]);
stream->read(&rEntry.msToNext[j]);
}
}
return stream->getStatus() == Stream::Ok;
}

View file

@ -0,0 +1,129 @@
//-----------------------------------------------------------------------------
// 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 _PATHMANAGER_H_
#define _PATHMANAGER_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _MPOINT3_H_
#include "math/mPoint3.h"
#endif
#ifndef _MQUAT_H_
#include "math/mQuat.h"
#endif
#ifndef _SCENEOBJECT_H_
#include "scene/sceneObject.h"
#endif
class NetConnection;
class BitStream;
class PathManager
{
friend class PathManagerEvent;
private:
struct PathEntry {
U32 totalTime;
Vector<Point3F> positions;
Vector<QuatF> rotations;
Vector<U32> smoothingType;
Vector<U32> msToNext;
PathEntry() {
VECTOR_SET_ASSOCIATION(positions);
VECTOR_SET_ASSOCIATION(rotations);
VECTOR_SET_ASSOCIATION(smoothingType);
VECTOR_SET_ASSOCIATION(msToNext);
}
};
Vector<PathEntry*> mPaths;
public:
enum PathType {
BackAndForth,
Looping
};
public:
PathManager(const bool isServer);
~PathManager();
void clearPaths();
//-------------------------------------- Path querying
public:
bool isValidPath(const U32 id) const;
void getPathPosition(const U32 id, const F64 msPosition, Point3F& rPosition, QuatF &rotation);
U32 getPathTotalTime(const U32 id) const;
U32 getPathNumWaypoints(const U32 id) const;
U32 getWaypointTime(const U32 id, const U32 wayPoint) const;
U32 getPathTimeBits(const U32 id);
U32 getPathWaypointBits(const U32 id);
//-------------------------------------- Path Registration/Transmission/Management
public:
// Called after mission load to clear out the paths on the client, and to transmit
// the information for the current mission's paths.
void transmitPaths(NetConnection*);
void transmitPath(U32);
U32 allocatePathId();
void updatePath(const U32 id, const Vector<Point3F>&, const Vector<QuatF>&, const Vector<U32> &, const Vector<U32>&);
//-------------------------------------- State dumping/reading
public:
bool dumpState(BitStream*) const;
bool readState(BitStream*);
private:
bool mIsServer;
bool mPathsSent;
};
struct PathNode {
Point3F position;
QuatF rotation;
U32 smoothingType;
U32 msToNext;
};
extern PathManager* gClientPathManager;
extern PathManager* gServerPathManager;
//--------------------------------------------------------------------------
inline bool PathManager::isValidPath(const U32 id) const
{
return (id < U32(mPaths.size())) && mPaths[id]->positions.size() > 0;
}
#endif // _H_PATHMANAGER

View file

@ -0,0 +1,315 @@
//-----------------------------------------------------------------------------
// 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/reflectionManager.h"
#include "platform/profiler.h"
#include "platform/platformTimer.h"
#include "console/consoleTypes.h"
#include "core/tAlgorithm.h"
#include "math/mMathFn.h"
#include "T3D/gameBase/gameConnection.h"
#include "ts/tsShapeInstance.h"
#include "gui/3d/guiTSControl.h"
#include "scene/sceneManager.h"
#include "gfx/gfxDebugEvent.h"
#include "gfx/gfxStringEnumTranslate.h"
#include "gfx/screenshot.h"
#include "core/module.h"
#include "scene/reflectionMatHook.h"
#include "console/engineAPI.h"
MODULE_BEGIN( ReflectionManager )
MODULE_INIT
{
ManagedSingleton< ReflectionManager >::createSingleton();
ReflectionManager::initConsole();
}
MODULE_SHUTDOWN
{
ManagedSingleton< ReflectionManager >::deleteSingleton();
}
MODULE_END;
GFX_ImplementTextureProfile( ReflectRenderTargetProfile,
GFXTextureProfile::DiffuseMap,
GFXTextureProfile::PreserveSize | GFXTextureProfile::NoMipmap | GFXTextureProfile::RenderTarget | GFXTextureProfile::Pooled,
GFXTextureProfile::None );
GFX_ImplementTextureProfile( RefractTextureProfile,
GFXTextureProfile::DiffuseMap,
GFXTextureProfile::PreserveSize |
GFXTextureProfile::RenderTarget |
GFXTextureProfile::Pooled,
GFXTextureProfile::None );
static S32 QSORT_CALLBACK compareReflectors( const void *a, const void *b )
{
const ReflectorBase *A = *((ReflectorBase**)a);
const ReflectorBase *B = *((ReflectorBase**)b);
F32 dif = B->score - A->score;
return (S32)mFloor( dif );
}
U32 ReflectionManager::smFrameReflectionMS = 10;
F32 ReflectionManager::smRefractTexScale = 0.5f;
ReflectionManager::ReflectionManager()
: mUpdateRefract( true ),
mReflectFormat( GFXFormatR8G8B8A8 )
{
mTimer = PlatformTimer::create();
GFXDevice::getDeviceEventSignal().notify( this, &ReflectionManager::_handleDeviceEvent );
}
void ReflectionManager::initConsole()
{
Con::addVariable( "$pref::Reflect::refractTexScale", TypeF32, &ReflectionManager::smRefractTexScale, "RefractTex has dimensions equal to the active render target scaled in both x and y by this float.\n"
"@ingroup Rendering");
Con::addVariable( "$pref::Reflect::frameLimitMS", TypeS32, &ReflectionManager::smFrameReflectionMS, "ReflectionManager tries not to spend more than this amount of time updating reflections per frame.\n"
"@ingroup Rendering");
}
ReflectionManager::~ReflectionManager()
{
SAFE_DELETE( mTimer );
AssertFatal( mReflectors.size() == 0, "ReflectionManager, some reflectors were left nregistered!" );
GFXDevice::getDeviceEventSignal().remove( this, &ReflectionManager::_handleDeviceEvent );
}
void ReflectionManager::registerReflector( ReflectorBase *reflector )
{
mReflectors.push_back_unique( reflector );
}
void ReflectionManager::unregisterReflector( ReflectorBase *reflector )
{
mReflectors.remove( reflector );
}
void ReflectionManager::update( F32 timeSlice,
const Point2I &resolution,
const CameraQuery &query )
{
GFXDEBUGEVENT_SCOPE( UpdateReflections, ColorI::WHITE );
if ( mReflectors.empty() )
return;
PROFILE_SCOPE( ReflectionManager_Update );
// Calculate our target time from the slice.
U32 targetMs = timeSlice * smFrameReflectionMS;
// Setup a culler for testing the
// visibility of reflectors.
Frustum culler;
culler.set( false,
query.fov,
(F32)resolution.x / (F32)resolution.y,
query.nearPlane,
query.farPlane,
query.cameraMatrix );
// Manipulate the frustum for tiled screenshots
const bool screenShotMode = gScreenShot && gScreenShot->isPending();
if ( screenShotMode )
gScreenShot->tileFrustum( culler );
// We use the frame time and not real time
// here as this may be called multiple times
// within a frame.
U32 startOfUpdateMs = Platform::getVirtualMilliseconds();
// Save this for interested parties.
mLastUpdateMs = startOfUpdateMs;
ReflectParams refparams;
refparams.query = &query;
refparams.viewportExtent = resolution;
refparams.culler = culler;
refparams.startOfUpdateMs = startOfUpdateMs;
// Update the reflection score.
ReflectorList::iterator reflectorIter = mReflectors.begin();
for ( ; reflectorIter != mReflectors.end(); reflectorIter++ )
(*reflectorIter)->calcScore( refparams );
// Sort them by the score.
dQsort( mReflectors.address(), mReflectors.size(), sizeof(ReflectorBase*), compareReflectors );
// Update as many reflections as we can
// within the target time limit.
mTimer->getElapsedMs();
mTimer->reset();
U32 numUpdated = 0;
reflectorIter = mReflectors.begin();
for ( ; reflectorIter != mReflectors.end(); reflectorIter++ )
{
// We're sorted by score... so once we reach
// a zero score we have nothing more to update.
if ( (*reflectorIter)->score <= 0.0f && !screenShotMode )
break;
(*reflectorIter)->updateReflection( refparams );
(*reflectorIter)->lastUpdateMs = startOfUpdateMs;
numUpdated++;
// If we run out of update time then stop.
if ( mTimer->getElapsedMs() > targetMs && !screenShotMode && (*reflectorIter)->score < 1000.0f )
break;
}
U32 totalElapsed = mTimer->getElapsedMs();
// Set metric/debug related script variables...
U32 numEnabled = mReflectors.size();
U32 numVisible = 0;
U32 numOccluded = 0;
reflectorIter = mReflectors.begin();
for ( ; reflectorIter != mReflectors.end(); reflectorIter++ )
{
ReflectorBase *pReflector = (*reflectorIter);
if ( pReflector->isOccluded() )
numOccluded++;
else
numVisible++;
}
#ifdef TORQUE_GATHER_METRICS
const GFXTextureProfileStats &stats = ReflectRenderTargetProfile.getStats();
F32 mb = ( stats.activeBytes / 1024.0f ) / 1024.0f;
char temp[256];
dSprintf( temp, 256, "%s %d %0.2f\n",
ReflectRenderTargetProfile.getName().c_str(),
stats.activeCount,
mb );
Con::setVariable( "$Reflect::textureStats", temp );
Con::setIntVariable( "$Reflect::renderTargetsAllocated", stats.allocatedTextures );
Con::setIntVariable( "$Reflect::poolSize", stats.activeCount );
Con::setIntVariable( "$Reflect::numObjects", numEnabled );
Con::setIntVariable( "$Reflect::numVisible", numVisible );
Con::setIntVariable( "$Reflect::numOccluded", numOccluded );
Con::setIntVariable( "$Reflect::numUpdated", numUpdated );
Con::setIntVariable( "$Reflect::elapsed", totalElapsed );
#endif
}
GFXTexHandle ReflectionManager::allocRenderTarget( const Point2I &size )
{
return GFXTexHandle( size.x, size.y, mReflectFormat,
&ReflectRenderTargetProfile,
avar("%s() - mReflectTex (line %d)", __FUNCTION__, __LINE__) );
}
GFXTextureObject* ReflectionManager::getRefractTex()
{
GFXTarget *target = GFX->getActiveRenderTarget();
GFXFormat targetFormat = target->getFormat();
const Point2I &targetSize = target->getSize();
#if defined(TORQUE_OS_XENON)
// On the Xbox360, it needs to do a resolveTo from the active target, so this
// may as well be the full size of the active target
const U32 desWidth = targetSize.x;
const U32 desHeight = targetSize.y;
#else
const U32 desWidth = mFloor( (F32)targetSize.x * smRefractTexScale );
const U32 desHeight = mFloor( ( F32)targetSize.y * smRefractTexScale );
#endif
if ( mRefractTex.isNull() ||
mRefractTex->getWidth() != desWidth ||
mRefractTex->getHeight() != desHeight ||
mRefractTex->getFormat() != targetFormat )
{
mRefractTex.set( desWidth, desHeight, targetFormat, &RefractTextureProfile, "mRefractTex" );
mUpdateRefract = true;
}
if ( mUpdateRefract )
{
target->resolveTo( mRefractTex );
mUpdateRefract = false;
}
return mRefractTex;
}
BaseMatInstance* ReflectionManager::getReflectionMaterial( BaseMatInstance *inMat ) const
{
// See if we have an existing material hook.
ReflectionMaterialHook *hook = static_cast<ReflectionMaterialHook*>( inMat->getHook( ReflectionMaterialHook::Type ) );
if ( !hook )
{
// Create a hook and initialize it using the incoming material.
hook = new ReflectionMaterialHook;
hook->init( inMat );
inMat->addHook( hook );
}
return hook->getReflectMat();
}
bool ReflectionManager::_handleDeviceEvent( GFXDevice::GFXDeviceEventType evt )
{
switch( evt )
{
case GFXDevice::deStartOfFrame:
mUpdateRefract = true;
break;
case GFXDevice::deDestroy:
mRefractTex = NULL;
break;
default:
break;
}
return true;
}
DefineEngineFunction( setReflectFormat, void, ( GFXFormat format ),,
"Set the reflection texture format.\n"
"@ingroup GFX\n" )
{
REFLECTMGR->setReflectFormat( format );
}

View file

@ -0,0 +1,154 @@
//-----------------------------------------------------------------------------
// 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 _REFLECTIONMANAGER_H_
#define _REFLECTIONMANAGER_H_
#ifndef _GFXDEVICE_H_
#include "gfx/gfxDevice.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _UTIL_DELEGATE_H_
#include "core/util/delegate.h"
#endif
#ifndef _GFXTEXTUREHANDLE_H_
#include "gfx/gfxTextureHandle.h"
#endif
#ifndef _TSINGLETON_H_
#include "core/util/tSingleton.h"
#endif
#ifndef _REFLECTOR_H_
#include "scene/reflector.h"
#endif
class PlatformTimer;
class BaseMatInstance;
enum ReflectMode
{
ReflectNever = 0,
ReflectDynamic,
ReflectAlways
};
typedef Delegate<bool(bool)> ReflectDelegate;
class SceneObject;
struct Reflector
{
SceneObject *object;
ReflectDelegate updateFn;
F32 priority;
U32 maxRateMs;
F32 maxDist;
U32 lastUpdateMs;
F32 score;
bool updated;
bool tried;
bool hasTexture;
};
typedef Vector<Reflector> ReflectorVec;
GFX_DeclareTextureProfile( ReflectRenderTargetProfile );
GFX_DeclareTextureProfile( RefractTextureProfile );
class ReflectionManager
{
public:
ReflectionManager();
virtual ~ReflectionManager();
static void initConsole();
/// Called to change the reflection texture format.
void setReflectFormat( GFXFormat format ) { mReflectFormat = format; }
/// Returns the current reflection format.
GFXFormat getReflectFormat() const { return mReflectFormat; }
/// Doll out callbacks to registered objects based on
/// scoring and elapsed time. This should be called
/// once for each viewport that renders.
void update( F32 timeSlice,
const Point2I &resolution,
const CameraQuery &query );
void registerReflector( ReflectorBase *reflector );
void unregisterReflector( ReflectorBase *reflector );
GFXTexHandle allocRenderTarget( const Point2I &size );
GFXTextureObject* getRefractTex();
BaseMatInstance* getReflectionMaterial( BaseMatInstance *inMat ) const;
const U32& getLastUpdateMs() const { return mLastUpdateMs; }
protected:
bool _handleDeviceEvent( GFXDevice::GFXDeviceEventType evt );
protected:
/// ReflectionManager tries not to spend more than this amount of time
/// updating reflections per frame.
static U32 smFrameReflectionMS;
/// RefractTex has dimensions equal to the active render target scaled in
/// both x and y by this float.
static F32 smRefractTexScale;
/// A timer used for tracking update time.
PlatformTimer *mTimer;
/// All registered reflections which we handle updating.
ReflectorList mReflectors;
/// Refraction texture copied from the backbuffer once per frame that
/// gets used by all WaterObjects.
GFXTexHandle mRefractTex;
/// The texture format to use for reflection and
/// refraction texture sources.
GFXFormat mReflectFormat;
/// Set when the refraction texture is dirty
/// and requires an update.
bool mUpdateRefract;
/// Platform time in milliseconds of the last update.
U32 mLastUpdateMs;
public:
// For ManagedSingleton.
static const char* getSingletonName() { return "ReflectionManager"; }
};
/// Returns the ReflectionManager singleton.
#define REFLECTMGR ManagedSingleton<ReflectionManager>::instance()
#endif // _REFLECTIONMANAGER_H_

View file

@ -0,0 +1,108 @@
//-----------------------------------------------------------------------------
// 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/reflectionMatHook.h"
#include "materials/materialManager.h"
#include "materials/customMaterialDefinition.h"
#include "materials/materialFeatureTypes.h"
#include "materials/materialFeatureData.h"
#include "shaderGen/featureType.h"
#include "shaderGen/featureMgr.h"
#include "scene/sceneRenderState.h"
const MatInstanceHookType ReflectionMaterialHook::Type( "Reflection" );
ReflectionMaterialHook::ReflectionMaterialHook() :
mReflectMat(NULL)
{
}
ReflectionMaterialHook::~ReflectionMaterialHook()
{
SAFE_DELETE(mReflectMat);
}
void ReflectionMaterialHook::init( BaseMatInstance *inMat )
{
if( !inMat->isValid() )
return;
Material *reflectMat = (Material*)inMat->getMaterial();
if ( inMat->isCustomMaterial() )
{
// This is a custom material... who knows what it really does...do something
// smart here later.
}
// We may want to disable some states that the material might enable for us.
GFXStateBlockDesc refractState = inMat->getUserStateBlock();
// Always z-read, and z-write if the material isn't translucent
refractState.setZReadWrite( true, reflectMat->isTranslucent() ? false : true );
// Create reflection material instance.
BaseMatInstance *newMat = new ReflectionMatInstance( reflectMat );
newMat->setUserObject( inMat->getUserObject() );
newMat->getFeaturesDelegate().bind( &ReflectionMaterialHook::_overrideFeatures );
newMat->addStateBlockDesc( refractState );
if( !newMat->init( inMat->getFeatures(), inMat->getVertexFormat() ) )
{
SAFE_DELETE( newMat );
newMat = MATMGR->createWarningMatInstance();
}
mReflectMat = newMat;
}
void ReflectionMaterialHook::_overrideFeatures( ProcessedMaterial *mat,
U32 stageNum,
MaterialFeatureData &fd,
const FeatureSet &features )
{
// First stage only in reflections
if( stageNum != 0 )
{
fd.features.clear();
return;
}
// Forward shading on materials in reflections
fd.features.addFeature( MFT_ForwardShading );
fd.features.addFeature( MFT_Fog );
}
//------------------------------------------------------------------------------
ReflectionMatInstance::ReflectionMatInstance( Material *mat )
: MatInstance( *mat )
{
}
bool ReflectionMatInstance::setupPass( SceneRenderState *state, const SceneData &sgData )
{
return Parent::setupPass(state, sgData);
}

View file

@ -0,0 +1,74 @@
//-----------------------------------------------------------------------------
// 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 _REFLECTIONMATHOOK_H_
#define _REFLECTIONMATHOOK_H_
#ifndef _MATINSTANCEHOOK_H_
#include "materials/matInstanceHook.h"
#endif
#ifndef _MATINSTANCE_H_
#include "materials/matInstance.h"
#endif
class ReflectionMatInstance : public MatInstance
{
typedef MatInstance Parent;
public:
ReflectionMatInstance( Material *mat );
virtual ~ReflectionMatInstance() {}
virtual bool setupPass( SceneRenderState *state, const SceneData &sgData );
};
class ReflectionMaterialHook : public MatInstanceHook
{
public:
ReflectionMaterialHook();
// MatInstanceHook
virtual ~ReflectionMaterialHook();
virtual const MatInstanceHookType& getType() const { return Type; }
/// The material hook type.
static const MatInstanceHookType Type;
BaseMatInstance* getReflectMat() const { return mReflectMat; }
void init( BaseMatInstance *mat );
protected:
static void _overrideFeatures( ProcessedMaterial *mat,
U32 stageNum,
MaterialFeatureData &fd,
const FeatureSet &features );
///
BaseMatInstance* mReflectMat;
};
#endif // _SHADOWMATHOOK_H_

View file

@ -0,0 +1,743 @@
//-----------------------------------------------------------------------------
// 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/reflector.h"
#include "console/consoleTypes.h"
#include "gfx/gfxCubemap.h"
#include "gfx/gfxDebugEvent.h"
#include "gfx/gfxTransformSaver.h"
#include "scene/sceneManager.h"
#include "scene/sceneRenderState.h"
#include "core/stream/bitStream.h"
#include "scene/reflectionManager.h"
#include "gui/3d/guiTSControl.h"
#include "ts/tsShapeInstance.h"
#include "gfx/gfxOcclusionQuery.h"
#include "lighting/lightManager.h"
#include "lighting/shadowMap/lightShadowMap.h"
#include "math/mathUtils.h"
#include "math/util/frustum.h"
#include "gfx/screenshot.h"
extern ColorI gCanvasClearColor;
//-------------------------------------------------------------------------
// ReflectorDesc
//-------------------------------------------------------------------------
IMPLEMENT_CO_DATABLOCK_V1( ReflectorDesc );
ConsoleDocClass( ReflectorDesc,
"@brief A datablock which defines performance and quality properties for "
"dynamic reflections.\n\n"
"ReflectorDesc is not itself a reflection and does not render reflections. "
"It is a dummy class for holding and exposing to the user a set of "
"reflection related properties. Objects which support dynamic reflections "
"may then reference a ReflectorDesc.\n\n"
"@tsexample\n"
"datablock ReflectorDesc( ExampleReflectorDesc )\n"
"{\n"
" texSize = 256;\n"
" nearDist = 0.1;\n"
" farDist = 500;\n"
" objectTypeMask = 0xFFFFFFFF;\n"
" detailAdjust = 1.0;\n"
" priority = 1.0;\n"
" maxRateMs = 0;\n"
" useOcclusionQuery = true;\n"
"};\n"
"@endtsexample\n"
"@see ShapeBaseData::cubeReflectorDesc\n"
"@ingroup enviroMisc"
);
ReflectorDesc::ReflectorDesc()
{
texSize = 256;
nearDist = 0.1f;
farDist = 1000.0f;
objectTypeMask = 0xFFFFFFFF;
detailAdjust = 1.0f;
priority = 1.0f;
maxRateMs = 15;
useOcclusionQuery = true;
}
ReflectorDesc::~ReflectorDesc()
{
}
void ReflectorDesc::initPersistFields()
{
addGroup( "ReflectorDesc" );
addField( "texSize", TypeS32, Offset( texSize, ReflectorDesc ),
"Size in pixels of the (square) reflection texture. For a cubemap "
"this value is interpreted as size of each face." );
addField( "nearDist", TypeF32, Offset( nearDist, ReflectorDesc ),
"Near plane distance to use when rendering this reflection. Adjust "
"this to limit self-occlusion artifacts." );
addField( "farDist", TypeF32, Offset( farDist, ReflectorDesc ),
"Far plane distance to use when rendering reflections." );
addField( "objectTypeMask", TypeS32, Offset( objectTypeMask, ReflectorDesc ),
"Object types which render into this reflection." );
addField( "detailAdjust", TypeF32, Offset( detailAdjust, ReflectorDesc ),
"Scale applied to lod calculation of objects rendering into "
"this reflection ( modulates $pref::TS::detailAdjust )." );
addField( "priority", TypeF32, Offset( priority, ReflectorDesc ),
"Priority for updating this reflection, relative to others." );
addField( "maxRateMs", TypeS32, Offset( maxRateMs, ReflectorDesc ),
"If less than maxRateMs has elapsed since this relfection was last "
"updated, then do not update it again. This 'skip' can be disabled by "
"setting maxRateMs to zero." );
addField( "useOcclusionQuery", TypeBool, Offset( useOcclusionQuery, ReflectorDesc ),
"If available on the device use HOQs to determine if the reflective object "
"is visible before updating its reflection." );
endGroup( "ReflectorDesc" );
Parent::initPersistFields();
}
void ReflectorDesc::packData( BitStream *stream )
{
Parent::packData( stream );
stream->write( texSize );
stream->write( nearDist );
stream->write( farDist );
stream->write( objectTypeMask );
stream->write( detailAdjust );
stream->write( priority );
stream->write( maxRateMs );
stream->writeFlag( useOcclusionQuery );
}
void ReflectorDesc::unpackData( BitStream *stream )
{
Parent::unpackData( stream );
stream->read( &texSize );
stream->read( &nearDist );
stream->read( &farDist );
stream->read( &objectTypeMask );
stream->read( &detailAdjust );
stream->read( &priority );
stream->read( &maxRateMs );
useOcclusionQuery = stream->readFlag();
}
bool ReflectorDesc::preload( bool server, String &errorStr )
{
if ( !Parent::preload( server, errorStr ) )
return false;
return true;
}
//-------------------------------------------------------------------------
// ReflectorBase
//-------------------------------------------------------------------------
ReflectorBase::ReflectorBase()
{
mEnabled = false;
mOccluded = false;
mIsRendering = false;
mDesc = NULL;
mObject = NULL;
mOcclusionQuery = GFX->createOcclusionQuery();
mQueryPending = false;
}
ReflectorBase::~ReflectorBase()
{
delete mOcclusionQuery;
}
void ReflectorBase::unregisterReflector()
{
if ( mEnabled )
{
REFLECTMGR->unregisterReflector( this );
mEnabled = false;
}
}
F32 ReflectorBase::calcScore( const ReflectParams &params )
{
PROFILE_SCOPE( ReflectorBase_calcScore );
// First check the occlusion query to see if we're hidden.
if ( mDesc->useOcclusionQuery &&
mOcclusionQuery )
{
GFXOcclusionQuery::OcclusionQueryStatus status = mOcclusionQuery->getStatus( false );
if ( status == GFXOcclusionQuery::Waiting )
{
mQueryPending = true;
// Don't change mOccluded since we don't know yet, use the value
// from last frame.
}
else
{
mQueryPending = false;
if ( status == GFXOcclusionQuery::Occluded )
mOccluded = true;
else if ( status == GFXOcclusionQuery::NotOccluded )
mOccluded = false;
}
}
// If we're disabled for any reason then there
// is nothing more left to do.
if ( !mEnabled ||
mOccluded ||
params.culler.isCulled( mObject->getWorldBox() ) )
{
score = 0;
return score;
}
// This mess is calculating a score based on LOD.
/*
F32 sizeWS = getMax( object->getWorldBox().len_z(), 0.001f );
Point3F cameraOffset = params.culler.getPosition() - object->getPosition();
F32 dist = getMax( cameraOffset.len(), 0.01f );
F32 worldToScreenScaleY = ( params.culler.getNearDist() * params.viewportExtent.y ) /
( params.culler.getNearTop() - params.culler.getNearBottom() );
F32 sizeSS = sizeWS / dist * worldToScreenScaleY;
*/
if ( mDesc->priority == -1.0f )
{
score = 1000.0f;
return score;
}
F32 lodFactor = 1.0f; //sizeSS;
F32 maxRate = getMax( (F32)mDesc->maxRateMs, 1.0f );
U32 delta = params.startOfUpdateMs - lastUpdateMs;
F32 timeFactor = getMax( (F32)delta / maxRate - 1.0f, 0.0f );
score = mDesc->priority * timeFactor * lodFactor;
return score;
}
//-------------------------------------------------------------------------
// CubeReflector
//-------------------------------------------------------------------------
CubeReflector::CubeReflector()
: mLastTexSize( 0 )
{
}
void CubeReflector::registerReflector( SceneObject *object,
ReflectorDesc *desc )
{
if ( mEnabled )
return;
mEnabled = true;
mObject = object;
mDesc = desc;
REFLECTMGR->registerReflector( this );
}
void CubeReflector::unregisterReflector()
{
if ( !mEnabled )
return;
REFLECTMGR->unregisterReflector( this );
mEnabled = false;
}
void CubeReflector::updateReflection( const ReflectParams &params )
{
GFXDEBUGEVENT_SCOPE( CubeReflector_UpdateReflection, ColorI::WHITE );
mIsRendering = true;
// Setup textures and targets...
S32 texDim = mDesc->texSize;
texDim = getMax( texDim, 32 );
// Protect against the reflection texture being bigger
// than the current game back buffer.
texDim = getMin( texDim, params.viewportExtent.x );
texDim = getMin( texDim, params.viewportExtent.y );
bool texResize = ( texDim != mLastTexSize );
const GFXFormat reflectFormat = REFLECTMGR->getReflectFormat();
if ( texResize ||
cubemap.isNull() ||
cubemap->getFormat() != reflectFormat )
{
cubemap = GFX->createCubemap();
cubemap->initDynamic( texDim, reflectFormat );
}
GFXTexHandle depthBuff = LightShadowMap::_getDepthTarget( texDim, texDim );
if ( renderTarget.isNull() )
renderTarget = GFX->allocRenderToTextureTarget();
GFX->pushActiveRenderTarget();
renderTarget->attachTexture( GFXTextureTarget::DepthStencil, depthBuff );
F32 oldVisibleDist = gClientSceneGraph->getVisibleDistance();
gClientSceneGraph->setVisibleDistance( mDesc->farDist );
for ( U32 i = 0; i < 6; i++ )
updateFace( params, i );
GFX->popActiveRenderTarget();
gClientSceneGraph->setVisibleDistance(oldVisibleDist);
mIsRendering = false;
mLastTexSize = texDim;
}
void CubeReflector::updateFace( const ReflectParams &params, U32 faceidx )
{
GFXDEBUGEVENT_SCOPE( CubeReflector_UpdateFace, ColorI::WHITE );
// store current matrices
GFXTransformSaver saver;
// set projection to 90 degrees vertical and horizontal
F32 left, right, top, bottom;
MathUtils::makeFrustum( &left, &right, &top, &bottom, M_HALFPI_F, 1.0f, mDesc->nearDist );
GFX->setFrustum( left, right, bottom, top, mDesc->nearDist, mDesc->farDist );
// We don't use a special clipping projection, but still need to initialize
// this for objects like SkyBox which will use it during a reflect pass.
gClientSceneGraph->setNonClipProjection( GFX->getProjectionMatrix() );
// Standard view that will be overridden below.
VectorF vLookatPt(0.0f, 0.0f, 0.0f), vUpVec(0.0f, 0.0f, 0.0f), vRight(0.0f, 0.0f, 0.0f);
switch( faceidx )
{
case 0 : // D3DCUBEMAP_FACE_POSITIVE_X:
vLookatPt = VectorF( 1.0f, 0.0f, 0.0f );
vUpVec = VectorF( 0.0f, 1.0f, 0.0f );
break;
case 1 : // D3DCUBEMAP_FACE_NEGATIVE_X:
vLookatPt = VectorF( -1.0f, 0.0f, 0.0f );
vUpVec = VectorF( 0.0f, 1.0f, 0.0f );
break;
case 2 : // D3DCUBEMAP_FACE_POSITIVE_Y:
vLookatPt = VectorF( 0.0f, 1.0f, 0.0f );
vUpVec = VectorF( 0.0f, 0.0f,-1.0f );
break;
case 3 : // D3DCUBEMAP_FACE_NEGATIVE_Y:
vLookatPt = VectorF( 0.0f, -1.0f, 0.0f );
vUpVec = VectorF( 0.0f, 0.0f, 1.0f );
break;
case 4 : // D3DCUBEMAP_FACE_POSITIVE_Z:
vLookatPt = VectorF( 0.0f, 0.0f, 1.0f );
vUpVec = VectorF( 0.0f, 1.0f, 0.0f );
break;
case 5: // D3DCUBEMAP_FACE_NEGATIVE_Z:
vLookatPt = VectorF( 0.0f, 0.0f, -1.0f );
vUpVec = VectorF( 0.0f, 1.0f, 0.0f );
break;
}
// create camera matrix
VectorF cross = mCross( vUpVec, vLookatPt );
cross.normalizeSafe();
MatrixF matView(true);
matView.setColumn( 0, cross );
matView.setColumn( 1, vLookatPt );
matView.setColumn( 2, vUpVec );
matView.setPosition( mObject->getPosition() );
matView.inverse();
GFX->setWorldMatrix(matView);
renderTarget->attachTexture( GFXTextureTarget::Color0, cubemap, faceidx );
GFX->setActiveRenderTarget( renderTarget );
GFX->clear( GFXClearStencil | GFXClearTarget | GFXClearZBuffer, gCanvasClearColor, 1.0f, 0 );
SceneRenderState reflectRenderState
(
gClientSceneGraph,
SPT_Reflect,
SceneCameraState::fromGFX()
);
reflectRenderState.getMaterialDelegate().bind( REFLECTMGR, &ReflectionManager::getReflectionMaterial );
reflectRenderState.setDiffuseCameraTransform( params.query->cameraMatrix );
reflectRenderState.disableAdvancedLightingBins(true);
// render scene
LIGHTMGR->registerGlobalLights( &reflectRenderState.getFrustum(), false );
gClientSceneGraph->renderSceneNoLights( &reflectRenderState, mDesc->objectTypeMask );
LIGHTMGR->unregisterAllLights();
// Clean up.
renderTarget->resolve();
}
F32 CubeReflector::calcFaceScore( const ReflectParams &params, U32 faceidx )
{
if ( Parent::calcScore( params ) <= 0.0f )
return score;
VectorF vLookatPt(0.0f, 0.0f, 0.0f);
switch( faceidx )
{
case 0 : // D3DCUBEMAP_FACE_POSITIVE_X:
vLookatPt = VectorF( 1.0f, 0.0f, 0.0f );
break;
case 1 : // D3DCUBEMAP_FACE_NEGATIVE_X:
vLookatPt = VectorF( -1.0f, 0.0f, 0.0f );
break;
case 2 : // D3DCUBEMAP_FACE_POSITIVE_Y:
vLookatPt = VectorF( 0.0f, 1.0f, 0.0f );
break;
case 3 : // D3DCUBEMAP_FACE_NEGATIVE_Y:
vLookatPt = VectorF( 0.0f, -1.0f, 0.0f );
break;
case 4 : // D3DCUBEMAP_FACE_POSITIVE_Z:
vLookatPt = VectorF( 0.0f, 0.0f, 1.0f );
break;
case 5: // D3DCUBEMAP_FACE_NEGATIVE_Z:
vLookatPt = VectorF( 0.0f, 0.0f, -1.0f );
break;
}
VectorF cameraDir;
params.query->cameraMatrix.getColumn( 1, &cameraDir );
F32 dot = mDot( cameraDir, -vLookatPt );
dot = getMax( ( dot + 1.0f ) / 2.0f, 0.1f );
score *= dot;
return score;
}
F32 CubeReflector::CubeFaceReflector::calcScore( const ReflectParams &params )
{
score = cube->calcFaceScore( params, faceIdx );
mOccluded = cube->isOccluded();
return score;
}
//-------------------------------------------------------------------------
// PlaneReflector
//-------------------------------------------------------------------------
void PlaneReflector::registerReflector( SceneObject *object,
ReflectorDesc *desc )
{
mEnabled = true;
mObject = object;
mDesc = desc;
mLastDir = Point3F::One;
mLastPos = Point3F::Max;
REFLECTMGR->registerReflector( this );
}
F32 PlaneReflector::calcScore( const ReflectParams &params )
{
if ( Parent::calcScore( params ) <= 0.0f || score >= 1000.0f )
return score;
// The planar reflection is view dependent to score it
// higher if the view direction and/or position has changed.
// Get the current camera info.
VectorF camDir = params.query->cameraMatrix.getForwardVector();
Point3F camPos = params.query->cameraMatrix.getPosition();
// Scale up the score based on the view direction change.
F32 dot = mDot( camDir, mLastDir );
dot = ( 1.0f - dot ) * 1000.0f;
score += dot * mDesc->priority;
// Also account for the camera movement.
score += ( camPos - mLastPos ).lenSquared() * mDesc->priority;
return score;
}
void PlaneReflector::updateReflection( const ReflectParams &params )
{
PROFILE_SCOPE(PlaneReflector_updateReflection);
GFXDEBUGEVENT_SCOPE( PlaneReflector_updateReflection, ColorI::WHITE );
mIsRendering = true;
S32 texDim = mDesc->texSize;
texDim = getMax( texDim, 32 );
// Protect against the reflection texture being bigger
// than the current game back buffer.
texDim = getMin( texDim, params.viewportExtent.x );
texDim = getMin( texDim, params.viewportExtent.y );
bool texResize = ( texDim != mLastTexSize );
mLastTexSize = texDim;
const Point2I texSize( texDim, texDim );
if ( texResize ||
reflectTex.isNull() ||
reflectTex->getFormat() != REFLECTMGR->getReflectFormat() )
reflectTex = REFLECTMGR->allocRenderTarget( texSize );
GFXTexHandle depthBuff = LightShadowMap::_getDepthTarget( texSize.x, texSize.y );
// store current matrices
GFXTransformSaver saver;
F32 aspectRatio = F32( params.viewportExtent.x ) / F32( params.viewportExtent.y );
Frustum frustum;
frustum.set(false, params.query->fov, aspectRatio, params.query->nearPlane, params.query->farPlane);
// Manipulate the frustum for tiled screenshots
const bool screenShotMode = gScreenShot && gScreenShot->isPending();
if ( screenShotMode )
gScreenShot->tileFrustum( frustum );
GFX->setFrustum( frustum );
// Store the last view info for scoring.
mLastDir = params.query->cameraMatrix.getForwardVector();
mLastPos = params.query->cameraMatrix.getPosition();
if ( objectSpace )
{
// set up camera transform relative to object
MatrixF invObjTrans = mObject->getRenderTransform();
invObjTrans.inverse();
MatrixF relCamTrans = invObjTrans * params.query->cameraMatrix;
MatrixF camReflectTrans = getCameraReflection( relCamTrans );
MatrixF camTrans = mObject->getRenderTransform() * camReflectTrans;
camTrans.inverse();
GFX->setWorldMatrix( camTrans );
// use relative reflect transform for modelview since clip plane is in object space
camTrans = camReflectTrans;
camTrans.inverse();
// set new projection matrix
gClientSceneGraph->setNonClipProjection( (MatrixF&) GFX->getProjectionMatrix() );
MatrixF clipProj = getFrustumClipProj( camTrans );
GFX->setProjectionMatrix( clipProj );
}
else
{
MatrixF camTrans = params.query->cameraMatrix;
// set world mat from new camera view
MatrixF camReflectTrans = getCameraReflection( camTrans );
camReflectTrans.inverse();
GFX->setWorldMatrix( camReflectTrans );
// set new projection matrix
gClientSceneGraph->setNonClipProjection( (MatrixF&) GFX->getProjectionMatrix() );
MatrixF clipProj = getFrustumClipProj( camReflectTrans );
GFX->setProjectionMatrix( clipProj );
}
// Adjust the detail amount
F32 detailAdjustBackup = TSShapeInstance::smDetailAdjust;
TSShapeInstance::smDetailAdjust *= mDesc->detailAdjust;
if(reflectTarget.isNull())
reflectTarget = GFX->allocRenderToTextureTarget();
reflectTarget->attachTexture( GFXTextureTarget::Color0, reflectTex );
reflectTarget->attachTexture( GFXTextureTarget::DepthStencil, depthBuff );
GFX->pushActiveRenderTarget();
GFX->setActiveRenderTarget( reflectTarget );
SceneRenderState reflectRenderState
(
gClientSceneGraph,
SPT_Reflect,
SceneCameraState::fromGFX()
);
reflectRenderState.getMaterialDelegate().bind( REFLECTMGR, &ReflectionManager::getReflectionMaterial );
reflectRenderState.setDiffuseCameraTransform( params.query->cameraMatrix );
reflectRenderState.disableAdvancedLightingBins(true);
U32 objTypeFlag = -1;
LIGHTMGR->registerGlobalLights( &reflectRenderState.getFrustum(), false );
// Since we can sometime be rendering a reflection for 1 or 2 frames before
// it gets updated do to the lag associated with getting the results from
// a HOQ we can sometimes see into parts of the reflection texture that
// have nothing but clear color ( eg. under the water ).
// To make this look less crappy use the ambient color of the sun.
//
// In the future we may want to fix this instead by having the scatterSky
// render a skirt or something in its lower half.
//
ColorF clearColor = reflectRenderState.getAmbientLightColor();
GFX->clear( GFXClearZBuffer | GFXClearStencil | GFXClearTarget, clearColor, 1.0f, 0 );
gClientSceneGraph->renderSceneNoLights( &reflectRenderState, objTypeFlag );
LIGHTMGR->unregisterAllLights();
// Clean up.
reflectTarget->resolve();
GFX->popActiveRenderTarget();
// Restore detail adjust amount.
TSShapeInstance::smDetailAdjust = detailAdjustBackup;
mIsRendering = false;
}
MatrixF PlaneReflector::getCameraReflection( MatrixF &camTrans )
{
Point3F normal = refplane;
// Figure out new cam position
Point3F camPos = camTrans.getPosition();
F32 dist = refplane.distToPlane( camPos );
Point3F newCamPos = camPos - normal * dist * 2.0;
// Figure out new look direction
Point3F i, j, k;
camTrans.getColumn( 0, &i );
camTrans.getColumn( 1, &j );
camTrans.getColumn( 2, &k );
i = MathUtils::reflect( i, normal );
j = MathUtils::reflect( j, normal );
k = MathUtils::reflect( k, normal );
//mCross( i, j, &k );
MatrixF newTrans(true);
newTrans.setColumn( 0, i );
newTrans.setColumn( 1, j );
newTrans.setColumn( 2, k );
newTrans.setPosition( newCamPos );
return newTrans;
}
inline float sgn(float a)
{
if (a > 0.0F) return (1.0F);
if (a < 0.0F) return (-1.0F);
return (0.0F);
}
MatrixF PlaneReflector::getFrustumClipProj( MatrixF &modelview )
{
static MatrixF rotMat(EulerF( static_cast<F32>(M_PI / 2.f), 0.0, 0.0));
static MatrixF invRotMat(EulerF( -static_cast<F32>(M_PI / 2.f), 0.0, 0.0));
MatrixF revModelview = modelview;
revModelview = rotMat * revModelview; // add rotation to modelview because it needs to be removed from projection
// rotate clip plane into modelview space
Point4F clipPlane;
Point3F pnt = refplane * -(refplane.d + 0.0 );
Point3F norm = refplane;
revModelview.mulP( pnt );
revModelview.mulV( norm );
norm.normalize();
clipPlane.set( norm.x, norm.y, norm.z, -mDot( pnt, norm ) );
// Manipulate projection matrix
//------------------------------------------------------------------------
MatrixF proj = GFX->getProjectionMatrix();
proj.mul( invRotMat ); // reverse rotation imposed by Torque
proj.transpose(); // switch to row-major order
// Calculate the clip-space corner point opposite the clipping plane
// as (sgn(clipPlane.x), sgn(clipPlane.y), 1, 1) and
// transform it into camera space by multiplying it
// by the inverse of the projection matrix
Vector4F q;
q.x = sgn(clipPlane.x) / proj(0,0);
q.y = sgn(clipPlane.y) / proj(1,1);
q.z = -1.0F;
q.w = ( 1.0F - proj(2,2) ) / proj(3,2);
F32 a = 1.0 / (clipPlane.x * q.x + clipPlane.y * q.y + clipPlane.z * q.z + clipPlane.w * q.w);
Vector4F c = clipPlane * a;
// CodeReview [ags 1/23/08] Come up with a better way to deal with this.
if(GFX->getAdapterType() == OpenGL)
c.z += 1.0f;
// Replace the third column of the projection matrix
proj.setColumn( 2, c );
proj.transpose(); // convert back to column major order
proj.mul( rotMat ); // restore Torque rotation
return proj;
}

View file

@ -0,0 +1,229 @@
//-----------------------------------------------------------------------------
// 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 _REFLECTOR_H_
#define _REFLECTOR_H_
#ifndef _GFXCUBEMAP_H_
#include "gfx/gfxCubemap.h"
#endif
#ifndef _GFXTARGET_H_
#include "gfx/gfxTarget.h"
#endif
#ifndef _SIMDATABLOCK_H_
#include "console/simDatablock.h"
#endif
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
#ifndef _MATHUTIL_FRUSTUM_H_
#include "math/util/frustum.h"
#endif
struct CameraQuery;
class Point2I;
class Frustum;
class SceneManager;
class SceneObject;
class GFXOcclusionQuery;
struct ReflectParams
{
const CameraQuery *query;
Point2I viewportExtent;
Frustum culler;
U32 startOfUpdateMs;
};
class ReflectorDesc : public SimDataBlock
{
typedef SimDataBlock Parent;
public:
ReflectorDesc();
virtual ~ReflectorDesc();
DECLARE_CONOBJECT( ReflectorDesc );
static void initPersistFields();
virtual void packData( BitStream *stream );
virtual void unpackData( BitStream* stream );
virtual bool preload( bool server, String &errorStr );
U32 texSize;
F32 nearDist;
F32 farDist;
U32 objectTypeMask;
F32 detailAdjust;
F32 priority;
U32 maxRateMs;
bool useOcclusionQuery;
//U32 lastLodSize;
};
class ReflectorBase
{
public:
ReflectorBase();
virtual ~ReflectorBase();
bool isEnabled() const { return mEnabled; }
virtual void unregisterReflector();
virtual F32 calcScore( const ReflectParams &params );
virtual void updateReflection( const ReflectParams &params ) {}
GFXOcclusionQuery* getOcclusionQuery() const { return mOcclusionQuery; }
bool isOccluded() const { return mOccluded; }
/// Returns true if this reflector is in the process of rendering.
bool isRendering() const { return mIsRendering; }
/// Signifies that the query has not finished yet and a new query
/// does not need to be submitted.
bool mQueryPending;
protected:
bool mEnabled;
bool mIsRendering;
GFXOcclusionQuery *mOcclusionQuery;
bool mOccluded;
SceneObject *mObject;
ReflectorDesc *mDesc;
public:
// These are public because some of them
// are exposed as fields.
F32 score;
U32 lastUpdateMs;
};
typedef Vector<ReflectorBase*> ReflectorList;
class CubeReflector : public ReflectorBase
{
typedef ReflectorBase Parent;
public:
CubeReflector();
virtual ~CubeReflector() {}
void registerReflector( SceneObject *inObject,
ReflectorDesc *inDesc );
virtual void unregisterReflector();
virtual void updateReflection( const ReflectParams &params );
GFXCubemap* getCubemap() const { return cubemap; }
void updateFace( const ReflectParams &params, U32 faceidx );
F32 calcFaceScore( const ReflectParams &params, U32 faceidx );
protected:
GFXTexHandle depthBuff;
GFXTextureTargetRef renderTarget;
GFXCubemapHandle cubemap;
U32 mLastTexSize;
class CubeFaceReflector : public ReflectorBase
{
typedef ReflectorBase Parent;
friend class CubeReflector;
public:
U32 faceIdx;
CubeReflector *cube;
virtual void updateReflection( const ReflectParams &params ) { cube->updateFace( params, faceIdx ); }
virtual F32 calcScore( const ReflectParams &params );
};
CubeFaceReflector mFaces[6];
};
class PlaneReflector : public ReflectorBase
{
typedef ReflectorBase Parent;
public:
PlaneReflector()
{
refplane.set( Point3F(0,0,0), Point3F(0,0,1) );
objectSpace = false;
mLastTexSize = 0;
}
virtual ~PlaneReflector() {}
void registerReflector( SceneObject *inObject,
ReflectorDesc *inDesc );
virtual F32 calcScore( const ReflectParams &params );
virtual void updateReflection( const ReflectParams &params );
/// Set up camera matrix for a reflection on the plane
MatrixF getCameraReflection( MatrixF &camTrans );
/// Oblique frustum clipping - use near plane of zbuffer as a clip plane
MatrixF getFrustumClipProj( MatrixF &modelview );
protected:
U32 mLastTexSize;
// The camera position at the last update.
Point3F mLastPos;
// The camera direction at the last update.
VectorF mLastDir;
public:
GFXTextureTargetRef reflectTarget;
GFXTexHandle reflectTex;
PlaneF refplane;
bool objectSpace;
};
#endif // _REFLECTOR_H_

View file

@ -0,0 +1,65 @@
//-----------------------------------------------------------------------------
// 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/sceneCameraState.h"
#include "gfx/gfxDevice.h"
//-----------------------------------------------------------------------------
SceneCameraState::SceneCameraState( const RectI& viewport, const Frustum& frustum, const MatrixF& worldView, const MatrixF& projection )
: mViewport( viewport ),
mFrustum( frustum ),
mWorldViewMatrix( worldView ),
mProjectionMatrix( projection )
{
mViewDirection = frustum.getTransform().getForwardVector();
}
//-----------------------------------------------------------------------------
SceneCameraState SceneCameraState::fromGFX()
{
return fromGFXWithViewport( GFX->getViewport() );
}
//-----------------------------------------------------------------------------
SceneCameraState SceneCameraState::fromGFXWithViewport( const RectI& viewport )
{
const MatrixF& world = GFX->getWorldMatrix();
MatrixF camera = world;
camera.inverse();
Frustum frustum = GFX->getFrustum();
frustum.setTransform( camera );
return SceneCameraState(
viewport,
frustum,
world,
GFX->getProjectionMatrix()
);
}

View file

@ -0,0 +1,101 @@
//-----------------------------------------------------------------------------
// 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 _SCENECAMERASTATE_H_
#define _SCENECAMERASTATE_H_
#ifndef _MATHUTIL_FRUSTUM_H_
#include "math/util/frustum.h"
#endif
#ifndef _MRECT_H_
#include "math/mRect.h"
#endif
#ifndef _MMATRIX_H_
#include "math/mMatrix.h"
#endif
/// An object that combines all the state that is relevant to looking into the
/// scene from a particular point of view.
class SceneCameraState
{
protected:
/// The screen-space viewport rectangle.
RectI mViewport;
/// The viewing frustum.
Frustum mFrustum;
/// The inverse of the frustum's transform stored here for caching.
MatrixF mWorldViewMatrix;
/// The projection matrix.
MatrixF mProjectionMatrix;
/// World-space vector representing the view direction.
Point3F mViewDirection;
/// Internal constructor.
SceneCameraState() {}
public:
/// Freeze the given viewing state.
///
/// @param viewport Screen-space viewport rectangle.
/// @param frustum Camera frustum.
/// @param worldView World->view matrix.
/// @param projection Projection matrix.
SceneCameraState( const RectI& viewport, const Frustum& frustum, const MatrixF& worldView, const MatrixF& projection );
/// Capture the view state from the current GFX state.
static SceneCameraState fromGFX();
///
static SceneCameraState fromGFXWithViewport( const RectI& viewport );
/// Return the screen-space viewport rectangle.
const RectI& getViewport() const { return mViewport; }
/// Return the camera frustum.
const Frustum& getFrustum() const { return mFrustum; }
/// Return the view position. This is a shortcut for getFrustum().getPosition().
const Point3F& getViewPosition() const { return mFrustum.getPosition(); }
/// Return the world-space view vector.
const Point3F& getViewDirection() const { return mViewDirection; }
/// Return the view->world transform. This is a shortcut for getFrustum().getTransform().
const MatrixF& getViewWorldMatrix() const { return mFrustum.getTransform(); }
/// Return the world->view transform.
const MatrixF& getWorldViewMatrix() const { return mWorldViewMatrix; }
/// Return the projection transform.
const MatrixF& getProjectionMatrix() const { return mProjectionMatrix; }
};
#endif // !_SCENECAMERASTATE_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,335 @@
//-----------------------------------------------------------------------------
// 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 _SCENECONTAINER_H_
#define _SCENECONTAINER_H_
#ifndef _MBOX_H_
#include "math/mBox.h"
#endif
#ifndef _MSPHERE_H_
#include "math/mSphere.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _MPOLYHEDRON_H_
#include "math/mPolyhedron.h"
#endif
#ifndef _SIMOBJECT_H_
#include "console/simObject.h"
#endif
/// @file
/// SceneObject database.
class SceneObject;
class AbstractPolyList;
class OptimizedPolyList;
class Frustum;
class Point3F;
struct RayInfo;
template< typename T >
class SceneObjectRefBase
{
public:
/// Object that is referenced in the link.
SceneObject* object;
/// Next link in chain of container.
T* nextInBin;
/// Previous link in chain of container.
T* prevInBin;
/// Next link in chain that is associated with #object.
T* nextInObj;
};
/// Reference to a scene object.
class SceneObjectRef : public SceneObjectRefBase< SceneObjectRef > {};
/// A contextual hint passed to the polylist methods which
/// allows it to return the appropriate geometry.
enum PolyListContext
{
/// A hint that the polyist is intended
/// for collision testing.
PLC_Collision,
/// A hint that the polyist is for decal
/// geometry generation.
PLC_Decal,
/// A hint that the polyist is used for
/// selection from an editor or other tool.
PLC_Selection,
/// A hint that the polyist will be used
/// to export geometry and would like to have
/// texture coords and materials.
PLC_Export
};
/// For simple queries. Simply creates a vector of the objects
class SimpleQueryList
{
public:
Vector< SceneObject* > mList;
SimpleQueryList()
{
VECTOR_SET_ASSOCIATION( mList );
}
void insertObject( SceneObject* obj ) { mList.push_back(obj); }
static void insertionCallback( SceneObject* obj, void* key )
{
SimpleQueryList* pList = reinterpret_cast< SimpleQueryList* >( key );
pList->insertObject( obj );
}
};
//----------------------------------------------------------------------------
/// Database for SceneObjects.
///
/// ScenceContainer implements a grid-based spatial subdivision for the contents of a scene.
class SceneContainer
{
enum CastRayType
{
CollisionGeometry,
RenderedGeometry,
};
public:
struct Link
{
Link* next;
Link* prev;
Link();
void unlink();
void linkAfter(Link* ptr);
};
struct CallbackInfo
{
PolyListContext context;
AbstractPolyList* polyList;
Box3F boundingBox;
SphereF boundingSphere;
void *key;
};
private:
Link mStart;
Link mEnd;
/// Container queries based on #mCurrSeqKey are are not re-entrant;
/// this is used to detect when it happens.
bool mSearchInProgress;
/// Current sequence key.
U32 mCurrSeqKey;
SceneObjectRef* mFreeRefPool;
Vector< SceneObjectRef* > mRefPoolBlocks;
SceneObjectRef* mBinArray;
SceneObjectRef mOverflowBin;
/// A vector that contains just the water and physical zone
/// object types which is used to optimize searches.
Vector< SceneObject* > mWaterAndZones;
/// Vector that contains just the terrain objects in the container.
Vector< SceneObject* > mTerrains;
static const U32 csmNumBins;
static const F32 csmBinSize;
static const F32 csmTotalBinSize;
static const U32 csmRefPoolBlockSize;
public:
SceneContainer();
~SceneContainer();
/// Return a vector containing all the water and physical zone objects in this container.
const Vector< SceneObject* >& getWaterAndPhysicalZones() const { return mWaterAndZones; }
/// Return a vector containing all terrain objects in this container.
const Vector< SceneObject* >& getTerrains() const { return mTerrains; }
/// @name Basic database operations
/// @{
///
typedef void ( *FindCallback )( SceneObject* object, void* key );
/// Find all objects of the given type(s) and invoke the given callback for each
/// of them.
/// @param mask Object type mask (@see SimObjectTypes).
/// @param callback Pointer to function to invoke for each object.
/// @param key User data to pass to the "key" argument of @a callback.
void findObjects( U32 mask, FindCallback callback, void* key = NULL );
void findObjects( const Box3F& box, U32 mask, FindCallback, void *key = NULL );
void findObjects( const Frustum& frustum, U32 mask, FindCallback, void *key = NULL );
void polyhedronFindObjects( const Polyhedron& polyhedron, U32 mask, FindCallback, void *key = NULL );
/// Find all objects of the given type(s) and add them to the given vector.
/// @param mask Object type mask (@see SimObjectTypes).
/// @param outFound Vector to add found objects to.
void findObjectList( U32 mask, Vector< SceneObject* >* outFound );
///
void findObjectList( const Box3F& box, U32 mask, Vector< SceneObject* >* outFound );
///
void findObjectList( const Frustum& frustum, U32 mask, Vector< SceneObject* >* outFound );
/// @}
/// @name Line intersection
/// @{
typedef bool ( *CastRayCallback )( RayInfo* ri );
/// Test against collision geometry -- fast.
bool castRay( const Point3F &start, const Point3F &end, U32 mask, RayInfo* info, CastRayCallback callback = NULL );
/// Test against rendered geometry -- slow.
bool castRayRendered( const Point3F &start, const Point3F &end, U32 mask, RayInfo* info, CastRayCallback callback = NULL );
bool collideBox(const Point3F &start, const Point3F &end, U32 mask, RayInfo* info);
/// @}
/// @name Poly list
/// @{
///
bool buildPolyList( PolyListContext context,
const Box3F &box,
U32 typeMask,
AbstractPolyList *polylist );
/// @}
/// Add an object to the database.
/// @param object A SceneObject.
bool addObject( SceneObject* object );
/// Remove an object from the database.
/// @param object A SceneObject.
bool removeObject( SceneObject* object );
void addRefPoolBlock();
SceneObjectRef* allocateObjectRef();
void freeObjectRef(SceneObjectRef*);
void insertIntoBins( SceneObject* object );
void removeFromBins( SceneObject* object );
/// Make sure that we're not just sticking the object right back
/// where it came from. The overloaded insertInto is so we don't calculate
/// the ranges twice.
void checkBins( SceneObject* object );
void insertIntoBins(SceneObject*, U32, U32, U32, U32);
void initRadiusSearch(const Point3F& searchPoint,
const F32 searchRadius,
const U32 searchMask);
void initTypeSearch(const U32 searchMask);
SceneObject* containerSearchNextObject();
U32 containerSearchNext();
F32 containerSearchCurrDist();
F32 containerSearchCurrRadiusDist();
private:
Vector<SimObjectPtr<SceneObject>*> mSearchList;///< Object searches to support console querying of the database. ONLY WORKS ON SERVER
S32 mCurrSearchPos;
Point3F mSearchReferencePoint;
void cleanupSearchVectors();
/// Base cast ray code
bool _castRay( U32 type, const Point3F &start, const Point3F &end, U32 mask, RayInfo* info, CastRayCallback callback );
void _findSpecialObjects( const Vector< SceneObject* >& vector, U32 mask, FindCallback, void *key = NULL );
void _findSpecialObjects( const Vector< SceneObject* >& vector, const Box3F &box, U32 mask, FindCallback callback, void *key = NULL );
static void getBinRange( const F32 min, const F32 max, U32& minBin, U32& maxBin );
};
//-----------------------------------------------------------------------------
extern SceneContainer gServerContainer;
extern SceneContainer gClientContainer;
//-----------------------------------------------------------------------------
inline void SceneContainer::freeObjectRef(SceneObjectRef* trash)
{
trash->object = NULL;
trash->nextInBin = NULL;
trash->prevInBin = NULL;
trash->nextInObj = mFreeRefPool;
mFreeRefPool = trash;
}
//-----------------------------------------------------------------------------
inline SceneObjectRef* SceneContainer::allocateObjectRef()
{
if( mFreeRefPool == NULL )
addRefPoolBlock();
AssertFatal( mFreeRefPool!=NULL, "Error, should always have a free reference here!" );
SceneObjectRef* ret = mFreeRefPool;
mFreeRefPool = mFreeRefPool->nextInObj;
ret->nextInObj = NULL;
return ret;
}
#endif // !_SCENECONTAINER_H_

View file

@ -0,0 +1,692 @@
//-----------------------------------------------------------------------------
// 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/sceneManager.h"
#include "scene/sceneObject.h"
#include "scene/zones/sceneTraversalState.h"
#include "scene/sceneRenderState.h"
#include "scene/zones/sceneRootZone.h"
#include "scene/zones/sceneZoneSpace.h"
#include "lighting/lightManager.h"
#include "renderInstance/renderPassManager.h"
#include "gfx/gfxDevice.h"
#include "gfx/gfxDrawUtil.h"
#include "gfx/gfxDebugEvent.h"
#include "console/engineAPI.h"
#include "sim/netConnection.h"
#include "T3D/gameBase/gameConnection.h"
// For player object bounds workaround.
#include "T3D/player.h"
extern bool gEditingMission;
MODULE_BEGIN( Scene )
MODULE_INIT_AFTER( Sim )
MODULE_SHUTDOWN_BEFORE( Sim )
MODULE_INIT
{
// Client scene.
gClientSceneGraph = new SceneManager( true );
// Server scene.
gServerSceneGraph = new SceneManager( false );
Con::addVariable( "$Scene::lockCull", TypeBool, &SceneManager::smLockDiffuseFrustum,
"Debug tool which locks the frustum culling to the current camera location.\n"
"@ingroup Rendering\n" );
Con::addVariable( "$Scene::disableTerrainOcclusion", TypeBool, &SceneCullingState::smDisableTerrainOcclusion,
"Used to disable the somewhat expensive terrain occlusion testing.\n"
"@ingroup Rendering\n" );
Con::addVariable( "$Scene::disableZoneCulling", TypeBool, &SceneCullingState::smDisableZoneCulling,
"If true, zone culling will be disabled and the scene contents will only be culled against the root frustum.\n\n"
"@ingroup Rendering\n" );
Con::addVariable( "$Scene::renderBoundingBoxes", TypeBool, &SceneManager::smRenderBoundingBoxes,
"If true, the bounding boxes of objects will be displayed.\n\n"
"@ingroup Rendering" );
Con::addVariable( "$Scene::maxOccludersPerZone", TypeS32, &SceneCullingState::smMaxOccludersPerZone,
"Maximum number of occluders that will be concurrently allowed into the scene culling state of any given zone.\n\n"
"@ingroup Rendering" );
Con::addVariable( "$Scene::occluderMinWidthPercentage", TypeF32, &SceneCullingState::smOccluderMinWidthPercentage,
"TODO\n\n"
"@ingroup Rendering" );
Con::addVariable( "$Scene::occluderMinHeightPercentage", TypeF32, &SceneCullingState::smOccluderMinHeightPercentage,
"TODO\n\n"
"@ingroup Rendering" );
}
MODULE_SHUTDOWN
{
SAFE_DELETE( gClientSceneGraph );
SAFE_DELETE( gServerSceneGraph );
}
MODULE_END;
bool SceneManager::smRenderBoundingBoxes;
bool SceneManager::smLockDiffuseFrustum = false;
SceneCameraState SceneManager::smLockedDiffuseCamera = SceneCameraState( RectI(), Frustum(), MatrixF(), MatrixF() );
SceneManager* gClientSceneGraph = NULL;
SceneManager* gServerSceneGraph = NULL;
//-----------------------------------------------------------------------------
SceneManager::SceneManager( bool isClient )
: mLightManager( NULL ),
mCurrentRenderState( NULL ),
mIsClient( isClient ),
mUsePostEffectFog( true ),
mDisplayTargetResolution( 0, 0 ),
mDefaultRenderPass( NULL ),
mVisibleDistance( 500.f ),
mNearClip( 0.1f ),
mAmbientLightColor( ColorF( 0.1f, 0.1f, 0.1f, 1.0f ) ),
mZoneManager( NULL )
{
VECTOR_SET_ASSOCIATION( mBatchQueryList );
// For the client, create a zone manager.
if( isClient )
{
mZoneManager = new SceneZoneSpaceManager( getContainer() );
// Add the root zone to the scene.
addObjectToScene( mZoneManager->getRootZone() );
}
}
//-----------------------------------------------------------------------------
SceneManager::~SceneManager()
{
SAFE_DELETE( mZoneManager );
if( mLightManager )
mLightManager->deactivate();
}
//-----------------------------------------------------------------------------
void SceneManager::renderScene( ScenePassType passType, U32 objectMask )
{
SceneCameraState cameraState = SceneCameraState::fromGFX();
// Handle frustum locking.
const bool lockedFrustum = ( smLockDiffuseFrustum && passType == SPT_Diffuse );
if( lockedFrustum )
cameraState = smLockedDiffuseCamera;
else if( passType == SPT_Diffuse )
{
// Store the camera state so if we lock, this will become the
// locked state.
if( passType == SPT_Diffuse )
smLockedDiffuseCamera = cameraState;
}
// Create the render state.
SceneRenderState renderState( this, passType, cameraState );
// If we have locked the frustum, reset the view transform
// on the render pass which the render state has just set
// to the view matrix corresponding to the locked frustum. For
// rendering, however, we need the true view matrix from the
// GFX state.
if( lockedFrustum )
{
RenderPassManager* rpm = renderState.getRenderPass();
rpm->assignSharedXform( RenderPassManager::View, GFX->getWorldMatrix() );
}
// Render.
renderScene( &renderState, objectMask );
}
//-----------------------------------------------------------------------------
void SceneManager::renderScene( SceneRenderState* renderState, U32 objectMask, SceneZoneSpace* baseObject, U32 baseZone )
{
PROFILE_SCOPE( SceneGraph_renderScene );
// Get the lights for rendering the scene.
PROFILE_START( SceneGraph_registerLights );
LIGHTMGR->registerGlobalLights( &renderState->getFrustum(), false );
PROFILE_END();
// If its a diffuse pass, update the current ambient light level.
// To do that find the starting zone and determine whether it has a custom
// ambient light color. If so, pass it on to the ambient light manager.
// If not, use the ambient light color of the sunlight.
//
// Note that we retain the starting zone information here and pass it
// on to renderSceneNoLights so that we don't need to look it up twice.
if( renderState->isDiffusePass() )
{
if( !baseObject && getZoneManager() )
{
getZoneManager()->findZone( renderState->getCameraPosition(), baseObject, baseZone );
AssertFatal( baseObject != NULL, "SceneManager::renderScene - findZone() did not return an object" );
}
ColorF zoneAmbient;
if( baseObject && baseObject->getZoneAmbientLightColor( baseZone, zoneAmbient ) )
mAmbientLightColor.setTargetValue( zoneAmbient );
else
{
const LightInfo* sunlight = LIGHTMGR->getSpecialLight( LightManager::slSunLightType );
if( sunlight )
mAmbientLightColor.setTargetValue( sunlight->getAmbient() );
}
renderState->setAmbientLightColor( mAmbientLightColor.getCurrentValue() );
}
// Trigger the pre-render signal.
PROFILE_START( SceneGraph_preRenderSignal);
mCurrentRenderState = renderState;
getPreRenderSignal().trigger( this, renderState );
mCurrentRenderState = NULL;
PROFILE_END();
// Render the scene.
renderSceneNoLights( renderState, objectMask, baseObject, baseZone );
// Trigger the post-render signal.
PROFILE_START( SceneGraphRender_postRenderSignal );
mCurrentRenderState = renderState;
getPostRenderSignal().trigger( this, renderState );
mCurrentRenderState = NULL;
PROFILE_END();
// Remove the previously registered lights.
PROFILE_START( SceneGraph_unregisterLights);
LIGHTMGR->unregisterAllLights();
PROFILE_END();
}
//-----------------------------------------------------------------------------
void SceneManager::renderSceneNoLights( SceneRenderState* renderState, U32 objectMask, SceneZoneSpace* baseObject, U32 baseZone )
{
// Set the current state.
mCurrentRenderState = renderState;
// Render.
_renderScene( mCurrentRenderState, objectMask, baseObject, baseZone );
#ifdef TORQUE_DEBUG
// If frustum is locked and this is a diffuse pass, render the culling volumes of
// zones that are selected (or the volumes of the outdoor zone if no zone is
// selected).
if( gEditingMission && renderState->isDiffusePass() && smLockDiffuseFrustum )
renderState->getCullingState().debugRenderCullingVolumes();
#endif
mCurrentRenderState = NULL;
}
//-----------------------------------------------------------------------------
void SceneManager::_renderScene( SceneRenderState* state, U32 objectMask, SceneZoneSpace* baseObject, U32 baseZone )
{
AssertFatal( this == gClientSceneGraph, "SceneManager::_buildSceneGraph - Only the client scenegraph can support this call!" );
PROFILE_SCOPE( SceneGraph_batchRenderImages );
// In the editor, override the type mask for diffuse passes.
if( gEditingMission && state->isDiffusePass() )
objectMask = EDITOR_RENDER_TYPEMASK;
// Update the zoning state and traverse zones.
if( getZoneManager() )
{
// Update.
getZoneManager()->updateZoningState();
// If zone culling isn't disabled, traverse the
// zones now.
if( !state->getCullingState().disableZoneCulling() )
{
// Find the start zone if we haven't already.
if( !baseObject )
{
getZoneManager()->findZone( state->getCameraPosition(), baseObject, baseZone );
AssertFatal( baseObject != NULL, "SceneManager::_renderScene - findZone() did not return an object" );
}
// Traverse zones starting in base object.
SceneTraversalState traversalState( &state->getCullingState() );
PROFILE_START( Scene_traverseZones );
baseObject->traverseZones( &traversalState, baseZone );
PROFILE_END();
// Set the scene render box to the area we have traversed.
state->setRenderArea( traversalState.getTraversedArea() );
}
}
// Set the query box for the container query. Never
// make it larger than the frustum's AABB. In the editor,
// always query the full frustum as that gives objects
// the opportunity to render editor visualizations even if
// they are otherwise not in view.
if( !state->getFrustum().getBounds().isOverlapped( state->getRenderArea() ) )
{
// This handles fringe cases like flying backwards into a zone where you
// end up pretty much standing on a zone border and looking directly into
// its "walls". In that case the traversal area will be behind the frustum
// (remember that the camera isn't where visibility starts, it's the near
// distance).
return;
}
Box3F queryBox = state->getFrustum().getBounds();
if( !gEditingMission )
{
queryBox.minExtents.setMax( state->getRenderArea().minExtents );
queryBox.maxExtents.setMin( state->getRenderArea().maxExtents );
}
PROFILE_START( Scene_cullObjects );
//TODO: We should split the codepaths here based on whether the outdoor zone has visible space.
// If it has, we should use the container query-based path.
// If it hasn't, we should fill the object list directly from the zone lists which will usually
// include way fewer objects.
// Gather all objects that intersect the scene render box.
mBatchQueryList.clear();
getContainer()->findObjectList( queryBox, objectMask, &mBatchQueryList );
// Cull the list.
U32 numRenderObjects = state->getCullingState().cullObjects(
mBatchQueryList.address(),
mBatchQueryList.size(),
!state->isDiffusePass() ? SceneCullingState::CullEditorOverrides : 0 // Keep forced editor stuff out of non-diffuse passes.
);
//HACK: If the control object is a Player and it is not in the render list, force
// it into it. This really should be solved by collision bounds being separate from
// object bounds; only because the Player class is using bounds not encompassing
// the actual player object is it that we have this problem in the first place.
// Note that we are forcing the player object into ALL passes here but such
// is the power of proliferation of things done wrong.
GameConnection* connection = GameConnection::getConnectionToServer();
if( connection )
{
Player* player = dynamic_cast< Player* >( connection->getControlObject() );
if( player )
{
mBatchQueryList.setSize( numRenderObjects );
if( !mBatchQueryList.contains( player ) )
{
mBatchQueryList.push_back( player );
numRenderObjects ++;
}
}
}
PROFILE_END();
// Render the remaining objects.
PROFILE_START( Scene_renderObjects );
state->renderObjects( mBatchQueryList.address(), numRenderObjects );
PROFILE_END();
// Render bounding boxes, if enabled.
if( smRenderBoundingBoxes && state->isDiffusePass() )
{
GFXDEBUGEVENT_SCOPE( Scene_renderBoundingBoxes, ColorI::WHITE );
GameBase* cameraObject = 0;
if( connection )
cameraObject = connection->getCameraObject();
GFXStateBlockDesc desc;
desc.setFillModeWireframe();
desc.setZReadWrite( true, false );
for( U32 i = 0; i < numRenderObjects; ++ i )
{
SceneObject* object = mBatchQueryList[ i ];
// Skip global bounds object.
if( object->isGlobalBounds() )
continue;
// Skip camera object as we're viewing the scene from it.
if( object == cameraObject )
continue;
const Box3F& worldBox = object->getWorldBox();
GFX->getDrawUtil()->drawObjectBox(
desc,
Point3F( worldBox.len_x(), worldBox.len_y(), worldBox.len_z() ),
worldBox.getCenter(),
MatrixF::Identity,
ColorI::WHITE
);
}
}
}
//-----------------------------------------------------------------------------
struct ScopingInfo
{
Point3F scopePoint;
F32 scopeDist;
F32 scopeDistSquared;
NetConnection* connection;
};
static void _scopeCallback( SceneObject* object, void* data )
{
if( !object->isScopeable() )
return;
ScopingInfo* info = reinterpret_cast< ScopingInfo* >( data );
NetConnection* connection = info->connection;
F32 difSq = ( object->getWorldSphere().center - info->scopePoint ).lenSquared();
if( difSq < info->scopeDistSquared )
{
// Not even close, it's in...
connection->objectInScope( object );
}
else
{
// Check a little more closely...
F32 realDif = mSqrt( difSq );
if( realDif - object->getWorldSphere().radius < info->scopeDist)
connection->objectInScope( object );
}
}
void SceneManager::scopeScene( CameraScopeQuery* query, NetConnection* netConnection )
{
PROFILE_SCOPE( SceneGraph_scopeScene );
// Note that this method does not use the zoning information in the scene
// to scope objects. The reason is that with the way that scoping is implemented
// in the networking layer--i.e. by killing off ghosts of objects that are out
// of scope--, it doesn't make sense to let, for example, all objects in the outdoor
// zone go out of scope, just because there is no exterior portal that is visible from
// the current camera viewpoint (in any direction).
//
// So, we perform a simple box query on the area covered by the camera query
// and then scope in everything that is in range.
// Set up scoping info.
ScopingInfo info;
info.scopePoint = query->pos;
info.scopeDist = query->visibleDistance;
info.scopeDistSquared = info.scopeDist * info.scopeDist;
info.connection = netConnection;
// Scope all objects in the query area.
Box3F area( query->visibleDistance );
area.setCenter( query->pos );
getContainer()->findObjects( area, 0xFFFFFFFF, _scopeCallback, &info );
}
//-----------------------------------------------------------------------------
bool SceneManager::addObjectToScene( SceneObject* object )
{
AssertFatal( !object->mSceneManager, "SceneManager::addObjectToScene - Object already part of a scene" );
// Mark the object as belonging to us.
object->mSceneManager = this;
// Register with managers except its the root zone.
if( !dynamic_cast< SceneRootZone* >( object ) )
{
// Add to container.
getContainer()->addObject( object );
// Register the object with the zone manager.
if( getZoneManager() )
getZoneManager()->registerObject( object );
}
// Notify the object.
return object->onSceneAdd();
}
//-----------------------------------------------------------------------------
void SceneManager::removeObjectFromScene( SceneObject* obj )
{
AssertFatal( obj->getSceneManager() == this, "SceneManager::removeObjectFromScene - Object not part of SceneManager" );
// Notify the object.
obj->onSceneRemove();
// Remove the object from the container.
getContainer()->removeObject( obj );
// Remove the object from the zoning system.
if( getZoneManager() )
getZoneManager()->unregisterObject( obj );
// Clear out the reference to us.
obj->mSceneManager = NULL;
}
//-----------------------------------------------------------------------------
void SceneManager::notifyObjectDirty( SceneObject* object )
{
// Update container state.
if( object->mContainer )
object->mContainer->checkBins( object );
// Mark zoning state as dirty.
if( getZoneManager() )
getZoneManager()->notifyObjectChanged( object );
}
//-----------------------------------------------------------------------------
void SceneManager::setDisplayTargetResolution( const Point2I &size )
{
mDisplayTargetResolution = size;
}
//-----------------------------------------------------------------------------
const Point2I & SceneManager::getDisplayTargetResolution() const
{
return mDisplayTargetResolution;
}
//-----------------------------------------------------------------------------
bool SceneManager::setLightManager( const char* lmName )
{
LightManager *lm = LightManager::findByName( lmName );
if ( !lm )
return false;
return _setLightManager( lm );
}
//-----------------------------------------------------------------------------
bool SceneManager::_setLightManager( LightManager* lm )
{
// Avoid unnecessary work reinitializing materials.
if ( lm == mLightManager )
return true;
// Make sure its valid... else fail!
if ( !lm->isCompatible() )
return false;
// We only deactivate it... all light managers are singletons
// and will manager their own lifetime.
if ( mLightManager )
mLightManager->deactivate();
mLightManager = lm;
if ( mLightManager )
mLightManager->activate( this );
return true;
}
//-----------------------------------------------------------------------------
RenderPassManager* SceneManager::getDefaultRenderPass() const
{
if( !mDefaultRenderPass )
{
Sim::findObject( "DiffuseRenderPassManager", mDefaultRenderPass );
AssertISV( mDefaultRenderPass, "SceneManager::_setDefaultRenderPass - No DiffuseRenderPassManager defined! Must be set up in script!" );
}
return mDefaultRenderPass;
}
//=============================================================================
// Console API.
//=============================================================================
// MARK: ---- Console API ----
//-----------------------------------------------------------------------------
DefineConsoleFunction( sceneDumpZoneStates, void, ( bool updateFirst ), ( true ),
"Dump the current zoning states of all zone spaces in the scene to the console.\n\n"
"@param updateFirst If true, zoning states are brought up to date first; if false, the zoning states "
"are dumped as is.\n\n"
"@note Only valid on the client.\n"
"@ingroup Game" )
{
if( !gClientSceneGraph )
{
Con::errorf( "sceneDumpZoneStates - Only valid on client!" );
return;
}
SceneZoneSpaceManager* manager = gClientSceneGraph->getZoneManager();
if( !manager )
{
Con::errorf( "sceneDumpZoneStates - Scene is not using zones!" );
return;
}
manager->dumpZoneStates( updateFirst );
}
//-----------------------------------------------------------------------------
DefineConsoleFunction( sceneGetZoneOwner, SceneObject*, ( U32 zoneId ), ( true ),
"Return the SceneObject that contains the given zone.\n\n"
"@param zoneId ID of zone.\n"
"@return A SceneObject or NULL if the given @a zoneId is invalid.\n\n"
"@note Only valid on the client.\n"
"@ingroup Game" )
{
if( !gClientSceneGraph )
{
Con::errorf( "sceneGetZoneOwner - Only valid on client!" );
return NULL;
}
SceneZoneSpaceManager* manager = gClientSceneGraph->getZoneManager();
if( !manager )
{
Con::errorf( "sceneGetZoneOwner - Scene is not using zones!" );
return NULL;
}
if( !manager->isValidZoneId( zoneId ) )
{
Con::errorf( "sceneGetZoneOwner - Invalid zone ID: %i", zoneId );
return NULL;
}
return manager->getZoneOwner( zoneId );
}

View file

@ -0,0 +1,354 @@
//-----------------------------------------------------------------------------
// 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 _SCENEMANAGER_H_
#define _SCENEMANAGER_H_
#ifndef _SCENEOBJECT_H_
#include "scene/sceneObject.h"
#endif
#ifndef _SCENEZONESPACEMANAGER_H_
#include "scene/zones/sceneZoneSpaceManager.h"
#endif
#ifndef _MRECT_H_
#include "math/mRect.h"
#endif
#ifndef _COLOR_H_
#include "core/color.h"
#endif
#ifndef _INTERPOLATEDCHANGEPROPERTY_H_
#include "util/interpolatedChangeProperty.h"
#endif
#ifndef _GFXTEXTUREHANDLE_H_
#include "gfx/gfxTextureHandle.h"
#endif
#ifndef _FOGSTRUCTS_H_
#include "scene/fogStructs.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _TSIGNAL_H_
#include "core/util/tSignal.h"
#endif
class LightManager;
class SceneRootZone;
class SceneRenderState;
class SceneCameraState;
class SceneZoneSpace;
class NetConnection;
class RenderPassManager;
/// The type of scene pass.
/// @see SceneManager
/// @see SceneRenderState
enum ScenePassType
{
/// The regular diffuse scene pass.
SPT_Diffuse,
/// The scene pass made for reflection rendering.
SPT_Reflect,
/// The scene pass made for shadow map rendering.
SPT_Shadow,
/// A scene pass that isn't one of the other
/// predefined scene pass types.
SPT_Other,
};
/// An object that manages the SceneObjects belonging to a scene.
class SceneManager
{
public:
/// A signal used to notify of render passes.
typedef Signal< void( SceneManager*, const SceneRenderState* ) > RenderSignal;
/// If true use the last stored locked frustum for culling
/// the diffuse render pass.
/// @see smLockedDiffuseFrustum
static bool smLockDiffuseFrustum;
/// If true, render the AABBs of objects for debugging.
static bool smRenderBoundingBoxes;
protected:
/// Whether this is the client-side scene.
bool mIsClient;
/// Manager for the zones in this scene.
SceneZoneSpaceManager* mZoneManager;
// NonClipProjection is the projection matrix without oblique frustum clipping
// applied to it (in reflections)
MatrixF mNonClipProj;
///
bool mUsePostEffectFog;
/// @see setDisplayTargetResolution
Point2I mDisplayTargetResolution;
/// The currently active render state or NULL if we're
/// not in the process of rendering.
SceneRenderState* mCurrentRenderState;
F32 mVisibleDistance;
F32 mNearClip;
FogData mFogData;
WaterFogData mWaterFogData;
/// The stored last diffuse pass frustum for locking the cull.
static SceneCameraState smLockedDiffuseCamera;
/// @name Lighting
/// @{
typedef InterpolatedChangeProperty< ColorF > AmbientLightInterpolator;
/// Light manager that is active for the scene.
LightManager* mLightManager;
/// Global ambient light level in the scene.
AmbientLightInterpolator mAmbientLightColor;
/// Deactivates the previous light manager and activates the new one.
bool _setLightManager( LightManager *lm );
/// @}
/// @name Rendering
/// @{
/// RenderPassManager for the default render pass. This is set up
/// in script and looked up by getDefaultRenderPass().
mutable RenderPassManager* mDefaultRenderPass;
///
Vector< SceneObject* > mBatchQueryList;
/// Render scene using the given state.
///
/// @param state SceneManager render state.
/// @param objectMask Object type mask with which to filter scene objects.
/// @param baseObject Zone manager to start traversal in. If null, the zone manager
/// that contains @a state's camera position will be used.
/// @param baseZone Zone in @a zone manager in which to start traversal. Ignored if
/// @a baseObject is NULL.
void _renderScene( SceneRenderState* state,
U32 objectMask = ( U32 ) -1,
SceneZoneSpace* baseObject = NULL,
U32 baseZone = 0 );
/// Callback for the container query.
static void _batchObjectCallback( SceneObject* object, void* key );
/// @}
public:
SceneManager( bool isClient );
~SceneManager();
/// Return the SceneContainer for this scene.
SceneContainer* getContainer() const { return mIsClient ? &gClientContainer : &gServerContainer; }
/// Return the manager for the zones in this scene.
/// @note Only client scenes have a zone manager as for the server, no zoning data is kept.
const SceneZoneSpaceManager* getZoneManager() const { return mZoneManager; }
SceneZoneSpaceManager* getZoneManager() { return mZoneManager; }
/// @name SceneObject Management
/// @{
/// Add the given object to the scene.
bool addObjectToScene( SceneObject* object );
/// Remove the given object from the scene.
void removeObjectFromScene( SceneObject* object );
/// Let the scene manager know that the given object has changed its transform or
/// sizing state.
void notifyObjectDirty( SceneObject* object );
/// @}
/// @name Rendering
/// @{
/// Return the default RenderPassManager for the scene.
RenderPassManager* getDefaultRenderPass() const;
/// Set the default render pass for the scene.
void setDefaultRenderPass( RenderPassManager* rpm ) { mDefaultRenderPass = rpm; }
/// Render the scene with the default render pass.
/// @note This uses the current GFX state (transforms, viewport, frustum) to initialize
/// the render state.
void renderScene( ScenePassType passType, U32 objectMask = DEFAULT_RENDER_TYPEMASK );
/// Render the scene with a custom rendering pass.
void renderScene( SceneRenderState *state, U32 objectMask = DEFAULT_RENDER_TYPEMASK, SceneZoneSpace* baseObject = NULL, U32 baseZone = 0 );
/// Render the scene with a custom rendering pass and no lighting set up.
void renderSceneNoLights( SceneRenderState *state, U32 objectMask = DEFAULT_RENDER_TYPEMASK, SceneZoneSpace* baseObject = NULL, U32 baseZone = 0 );
/// Returns the currently active scene state or NULL if no state is currently active.
SceneRenderState* getCurrentRenderState() const { return mCurrentRenderState; }
static RenderSignal& getPreRenderSignal()
{
static RenderSignal theSignal;
return theSignal;
}
static RenderSignal& getPostRenderSignal()
{
static RenderSignal theSignal;
return theSignal;
}
/// @}
/// @name Lighting
/// @{
/// Finds the light manager by name and activates it.
bool setLightManager( const char *lmName );
/// Return the current global ambient light color.
const ColorF& getAmbientLightColor() const { return mAmbientLightColor.getCurrentValue(); }
/// Set the time it takes for a new ambient light color to take full effect.
void setAmbientLightTransitionTime( SimTime time ) { mAmbientLightColor.setTransitionTime( time ); }
/// Set the interpolation curve to use for blending from one global ambient light
/// color to a different one.
void setAmbientLightTransitionCurve( const EaseF& ease ) { mAmbientLightColor.setTransitionCurve( ease ); }
/// @}
/// @name Networking
/// @{
/// Set the scoping states of the objects in the scene.
void scopeScene( CameraScopeQuery* query, NetConnection* netConnection );
/// @}
/// @name Fog/Visibility Management
/// @{
void setPostEffectFog( bool enable ) { mUsePostEffectFog = enable; }
bool usePostEffectFog() const { return mUsePostEffectFog; }
/// Accessor for the FogData structure.
const FogData& getFogData() { return mFogData; }
/// Sets the FogData structure.
void setFogData( const FogData &data ) { mFogData = data; }
/// Accessor for the WaterFogData structure.
const WaterFogData& getWaterFogData() { return mWaterFogData; }
/// Sets the WaterFogData structure.
void setWaterFogData( const WaterFogData &data ) { mWaterFogData = data; }
/// Used by LevelInfo to set the default visible distance for
/// rendering the scene.
///
/// Note this should not be used to alter culling which is
/// controlled by the active frustum when a SceneRenderState is created.
///
/// @see SceneRenderState
/// @see GameProcessCameraQuery
/// @see LevelInfo
void setVisibleDistance( F32 dist ) { mVisibleDistance = dist; }
/// Returns the default visible distance for the scene.
F32 getVisibleDistance() { return mVisibleDistance; }
/// Used by LevelInfo to set the default near clip plane
/// for rendering the scene.
///
/// @see GameProcessCameraQuery
/// @see LevelInfo
void setNearClip( F32 nearClip ) { mNearClip = nearClip; }
/// Returns the default near clip distance for the scene.
F32 getNearClip() { return mNearClip; }
/// @}
/// @name dtr Display Target Resolution
///
/// Some rendering must be targeted at a specific display resolution.
/// This display resolution is distinct from the current RT's size
/// (such as when rendering a reflection to a texture, for instance)
/// so we store the size at which we're going to display the results of
/// the current render.
///
/// @{
///
void setDisplayTargetResolution(const Point2I &size);
const Point2I &getDisplayTargetResolution() const;
/// @}
// NonClipProjection is the projection matrix without oblique frustum clipping
// applied to it (in reflections)
void setNonClipProjection( const MatrixF &proj ) { mNonClipProj = proj; }
const MatrixF& getNonClipProjection() const { return mNonClipProj; }
};
//-----------------------------------------------------------------------------
//TODO: these two need to go
/// The client-side scene graph. Not used if the engine is running
/// as a dedicated server.
extern SceneManager* gClientSceneGraph;
/// The server-side scene graph. Not used if the engine is running
/// as a pure client.
extern SceneManager* gServerSceneGraph;
#endif //_SCENEMANAGER_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,773 @@
//-----------------------------------------------------------------------------
// 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 _SCENEOBJECT_H_
#define _SCENEOBJECT_H_
#ifndef _NETOBJECT_H_
#include "sim/netObject.h"
#endif
#ifndef _COLLISION_H_
#include "collision/collision.h"
#endif
#ifndef _OBJECTTYPES_H_
#include "T3D/objectTypes.h"
#endif
#ifndef _COLOR_H_
#include "core/color.h"
#endif
#ifndef _BITSET_H_
#include "core/bitSet.h"
#endif
#ifndef _PROCESSLIST_H_
#include "T3D/gameBase/processList.h"
#endif
#ifndef _SCENECONTAINER_H_
#include "scene/sceneContainer.h"
#endif
class SceneManager;
class SceneRenderState;
class SceneTraversalState;
class SceneCameraState;
class SceneObjectLink;
class SceneObjectLightingPlugin;
class Convex;
class LightInfo;
class SFXAmbience;
struct ObjectRenderInst;
struct Move;
/// A 3D object.
///
/// @section SceneObject_intro Introduction
///
/// SceneObject exists as a foundation for 3D objects in Torque. It provides the
/// basic functionality for:
/// - A scene graph (in the Zones and Portals sections), allowing efficient
/// and robust rendering of the game scene.
/// - Various helper functions, including functions to get bounding information
/// and momentum/velocity.
/// - Collision detection, as well as ray casting.
/// - Lighting. SceneObjects can register lights both at lightmap generation time,
/// and dynamic lights at runtime (for special effects, such as from flame or
/// a projectile, or from an explosion).
/// - Manipulating scene objects, for instance varying scale.
///
/// @section SceneObject_example An Example
///
/// Melv May has written a most marvelous example object deriving from SceneObject.
/// Unfortunately this page is too small to contain it.
///
/// @see http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=3217
/// for a copy of Melv's example.
class SceneObject : public NetObject, private SceneContainer::Link, public ProcessObject
{
public:
typedef NetObject Parent;
friend class SceneManager;
friend class SceneContainer;
friend class SceneZoneSpaceManager;
friend class SceneCullingState; // _getZoneRefHead
friend class SceneObjectLink; // mSceneObjectLinks
enum
{
/// Maximum number of zones that an object can concurrently be assigned to.
MaxObjectZones = 128,
NumMountPoints = 32,
NumMountPointBits = 5,
};
/// Networking dirty mask.
enum SceneObjectMasks
{
InitialUpdateMask = BIT( 0 ),
ScaleMask = BIT( 1 ),
FlagMask = BIT( 2 ),
MountedMask = BIT( 3 ),
NextFreeMask = BIT( 4 )
};
/// Bit-flags stored in mObjectFlags.
/// If a derived class adds more flags they must overload
/// getObjectFlagMax to ensure those flags will be transmitted over
/// the network.
/// @see getObjectFlagMax
enum SceneObjectFlags
{
/// If set, the object can be rendered.
/// @note The per-class render disable flag can override the per-object flag.
RenderEnabledFlag = BIT( 0 ),
/// If set, the object can be selected in the editor.
/// @note The per-class selection disable flag can override the per-object flag.
SelectionEnabledFlag = BIT( 1 ),
/// If set, object will not be subjected to culling when in the editor.
/// This is useful to bypass zone culling and always render certain editor-only
/// visual elements (like the zones themselves).
DisableCullingInEditorFlag = BIT( 2 ),
/// If set, object will be used as a visual occluder. In this case,
/// the object should implement buildSilhouette() and return a
/// *convex* silhouette polygon.
VisualOccluderFlag = BIT( 3 ),
/// If set, object will be used as a sound occluder.
SoundOccluderFlag = BIT( 4 ),
NextFreeFlag = BIT( 5 )
};
protected:
/// Combination of SceneObjectFlags.
BitSet32 mObjectFlags;
/// SceneManager to which this SceneObject belongs.
SceneManager* mSceneManager;
/// Links installed by SceneTrackers attached to this object.
SceneObjectLink* mSceneObjectLinks;
/// SceneObjectLightingPlugin attached to this object.
SceneObjectLightingPlugin* mLightPlugin;
/// Object type mask.
/// @see SimObjectTypes
U32 mTypeMask;
/// @name Mounting
/// @{
/// Mounted object.
struct MountInfo
{
SceneObject* list; ///< Linked-list of objects mounted on this object
SceneObject* object; ///< Object this object is mounted on.
SceneObject* link; ///< Link to next object mounted to this object's mount
S32 node; ///< Node point we are mounted to.
MatrixF xfm;
};
///
MountInfo mMount;
///
SimPersistID* mMountPID;
/// @}
/// @name Zoning
/// @{
/// Bidirectional link between a zone manager and its objects.
struct ZoneRef : public SceneObjectRefBase< ZoneRef >
{
/// ID of zone.
U32 zone;
};
/// Iterator over the zones that the object is assigned to.
/// @note This iterator expects a clean zoning state. It will not update the
/// zoning state in case it is dirty.
struct ObjectZonesIterator
{
ObjectZonesIterator( SceneObject* object )
: mCurrent( object->_getZoneRefHead() ) {}
bool isValid() const
{
return ( mCurrent != NULL );
}
ObjectZonesIterator& operator ++()
{
AssertFatal( isValid(), "SceneObject::ObjectZonesIterator::operator++ - Invalid iterator!" );
mCurrent = mCurrent->nextInObj;
return *this;
}
U32 operator *() const
{
AssertFatal( isValid(), "SceneObject::ObjectZonesIterator::operator* - Invalid iterator!" );
return mCurrent->zone;
}
private:
ZoneRef* mCurrent;
};
friend struct ObjectZonesIterator;
/// If an object moves, its zoning state needs to be updated. This is deferred
/// to when the state is actually needed and this flag indicates a refresh
/// is necessary.
mutable bool mZoneRefDirty;
/// Number of zones this object is assigned to.
/// @note If #mZoneRefDirty is set, this might be outdated.
mutable U32 mNumCurrZones;
/// List of zones that this object is part of.
/// @note If #mZoneRefDirty is set, this might be outdated.
mutable ZoneRef* mZoneRefHead;
/// Refresh the zoning state of this object, if it isn't up-to-date anymore.
void _updateZoningState() const;
/// Return the first link in the zone list of this object. Each link represents
/// a single zone that the object is assigned to.
///
/// @note This method will return the zoning list as is. In case the zoning state
/// of the object is dirty, the list contents may be outdated.
ZoneRef* _getZoneRefHead() const { return mZoneRefHead; }
/// Gets the number of zones containing this object.
U32 _getNumCurrZones() const { return mNumCurrZones; }
/// Returns the nth zone containing this object.
U32 _getCurrZone( const U32 index ) const;
/// @}
/// @name Transform and Collision Members
/// @{
/// Transform from object space to world space.
MatrixF mObjToWorld;
/// Transform from world space to object space (inverse).
MatrixF mWorldToObj;
/// Object scale.
Point3F mObjScale;
/// Bounding box in object space.
Box3F mObjBox;
/// Bounding box (AABB) in world space.
Box3F mWorldBox;
/// Bounding sphere in world space.
SphereF mWorldSphere;
/// Render matrix to transform object space to world space.
MatrixF mRenderObjToWorld;
/// Render matrix to transform world space to object space.
MatrixF mRenderWorldToObj;
/// Render bounding box in world space.
Box3F mRenderWorldBox;
/// Render bounding sphere in world space.
SphereF mRenderWorldSphere;
/// Whether this object is considered to have an infinite bounding box.
bool mGlobalBounds;
///
S32 mCollisionCount;
/// Regenerates the world-space bounding box and bounding sphere.
void resetWorldBox();
/// Regenerates the render-world-space bounding box and sphere.
void resetRenderWorldBox();
/// Regenerates the object-space bounding box from the world-space
/// bounding box, the world space to object space transform, and
/// the object scale.
void resetObjectBox();
/// Called when the size of the object changes.
virtual void onScaleChanged() {}
/// @}
/// Object which must be ticked before this object.
SimObjectPtr< SceneObject > mAfterObject;
/// @name SceneContainer Interface
///
/// When objects are searched, we go through all the zones and ask them for
/// all of their objects. Because an object can exist in multiple zones, the
/// container sequence key is set to the id of the current search. Then, while
/// searching, we check to see if an object's sequence key is the same as the
/// current search key. If it is, it will NOT be added to the list of returns
/// since it has already been processed.
///
/// @{
/// Container database that the object is assigned to.
SceneContainer* mContainer;
/// SceneContainer sequence key.
U32 mContainerSeqKey;
///
SceneObjectRef* mBinRefHead;
U32 mBinMinX;
U32 mBinMaxX;
U32 mBinMinY;
U32 mBinMaxY;
/// Returns the container sequence key.
U32 getContainerSeqKey() const { return mContainerSeqKey; }
/// Sets the container sequence key.
void setContainerSeqKey( const U32 key ) { mContainerSeqKey = key; }
/// @}
/// Called when this is added to a SceneManager.
virtual bool onSceneAdd() { return true; }
/// Called when this is removed from its current SceneManager.
virtual void onSceneRemove() {}
/// Returns the greatest object flag bit defined.
/// Only bits within this range will be transmitted over the network.
virtual U32 getObjectFlagMax() const { return NextFreeFlag - 1; }
public:
SceneObject();
virtual ~SceneObject();
/// Triggered when a SceneObject onAdd is called.
static Signal< void( SceneObject* ) > smSceneObjectAdd;
/// Triggered when a SceneObject onRemove is called.
static Signal< void( SceneObject* ) > smSceneObjectRemove;
/// Return the type mask that indicates to which broad object categories
/// this object belongs.
U32 getTypeMask() const { return mTypeMask; }
/// @name SceneManager Functionality
/// @{
/// Return the SceneManager that this SceneObject belongs to.
SceneManager* getSceneManager() const { return mSceneManager; }
/// Adds object to the client or server container depending on the object
void addToScene();
/// Removes the object from the client/server container
void removeFromScene();
/// Returns a pointer to the container that contains this object
SceneContainer* getContainer() { return mContainer; }
/// @}
/// @name Flags
/// @{
/// Return true if this object is rendered.
bool isRenderEnabled() const;
/// Set whether the object gets rendered.
void setRenderEnabled( bool value );
/// Return true if this object can be selected in the editor.
bool isSelectionEnabled() const;
/// Set whether the object can be selected in the editor.
void setSelectionEnabled( bool value );
/// Return true if the object doesn't want to be subjected to culling
/// when in the editor.
bool isCullingDisabledInEditor() const { return mObjectFlags.test( DisableCullingInEditorFlag ); }
/// Return true if the object should be taken into account for visual occlusion.
bool isVisualOccluder() const { return mObjectFlags.test( VisualOccluderFlag ); }
/// @}
/// @name Collision and transform related interface
///
/// The Render Transform is the interpolated transform with respect to the
/// frame rate. The Render Transform will differ from the object transform
/// because the simulation is updated in fixed intervals, which controls the
/// object transform. The framerate is, most likely, higher than this rate,
/// so that is why the render transform is interpolated and will differ slightly
/// from the object transform.
///
/// @{
/// Disables collisions for this object including raycasts
virtual void disableCollision();
/// Enables collisions for this object
virtual void enableCollision();
/// Returns true if collisions are enabled
bool isCollisionEnabled() const { return mCollisionCount == 0; }
/// This gets called when an object collides with this object.
/// @param object Object colliding with this object
/// @param vec Vector along which collision occurred
virtual void onCollision( SceneObject *object, const VectorF &vec ) {}
/// Returns true if this object allows itself to be displaced
/// @see displaceObject
virtual bool isDisplacable() const { return false; }
/// Returns the momentum of this object
virtual Point3F getMomentum() const { return Point3F( 0, 0, 0 ); }
/// Sets the momentum of this object
/// @param momentum Momentum
virtual void setMomentum( const Point3F& momentum ) {}
/// Returns the mass of this object
virtual F32 getMass() const { return 1.f; }
/// Displaces this object by a vector
/// @param displaceVector Displacement vector
virtual bool displaceObject( const Point3F& displaceVector ) { return false; }
/// Returns the transform which can be used to convert object space
/// to world space
virtual const MatrixF& getTransform() const { return mObjToWorld; }
/// Returns the transform which can be used to convert world space
/// into object space
const MatrixF& getWorldTransform() const { return mWorldToObj; }
/// Returns the scale of the object
const VectorF& getScale() const { return mObjScale; }
/// Returns the bounding box for this object in local coordinates.
const Box3F& getObjBox() const { return mObjBox; }
/// Returns the bounding box for this object in world coordinates.
const Box3F& getWorldBox() const { return mWorldBox; }
/// Returns the bounding sphere for this object in world coordinates.
const SphereF& getWorldSphere() const { return mWorldSphere; }
/// Returns the center of the bounding box in world coordinates
Point3F getBoxCenter() const { return ( mWorldBox.minExtents + mWorldBox.maxExtents ) * 0.5f; }
/// Sets the Object -> World transform
///
/// @param mat New transform matrix
virtual void setTransform( const MatrixF &mat );
/// Sets the scale for the object
/// @param scale Scaling values
virtual void setScale( const VectorF &scale );
/// This sets the render transform for this object
/// @param mat New render transform
virtual void setRenderTransform(const MatrixF &mat);
/// Returns the render transform
const MatrixF& getRenderTransform() const { return mRenderObjToWorld; }
/// Returns the render transform to convert world to local coordinates
const MatrixF& getRenderWorldTransform() const { return mRenderWorldToObj; }
/// Returns the render world box
const Box3F& getRenderWorldBox() const { return mRenderWorldBox; }
/// Sets the state of this object as hidden or not. If an object is hidden
/// it is removed entirely from collisions, it is not ghosted and is
/// essentially "non existant" as far as simulation is concerned.
/// @param hidden True if object is to be hidden
virtual void setHidden( bool hidden );
/// Builds a convex hull for this object.
///
/// Think of a convex hull as a low-res mesh which covers, as tightly as
/// possible, the object mesh, and is used as a collision mesh.
/// @param box
/// @param convex Convex mesh generated (out)
virtual void buildConvex( const Box3F& box,Convex* convex ) {}
/// Builds a list of polygons which intersect a bounding volume.
///
/// This will use either the sphere or the box, not both, the
/// SceneObject implementation ignores sphere.
///
/// @see AbstractPolyList
/// @param context A contentual hint as to the type of polylist to build.
/// @param polyList Poly list build (out)
/// @param box Box bounding volume
/// @param sphere Sphere bounding volume
///
virtual bool buildPolyList( PolyListContext context,
AbstractPolyList* polyList,
const Box3F& box,
const SphereF& sphere ) { return false; }
/// Casts a ray and obtain collision information, returns true if RayInfo is modified.
///
/// @param start Start point of ray
/// @param end End point of ray
/// @param info Collision information obtained (out)
virtual bool castRay( const Point3F& start, const Point3F& end, RayInfo* info ) { return false; }
/// Casts a ray against rendered geometry, returns true if RayInfo is modified.
///
/// @param start Start point of ray
/// @param end End point of ray
/// @param info Collision information obtained (out)
virtual bool castRayRendered( const Point3F& start, const Point3F& end, RayInfo* info );
/// Build a world-space silhouette polygon for the object for the given camera settings.
/// This is used for occlusion.
///
/// @param cameraState Camera view parameters.
/// @param outPoints Vector to store the resulting polygon points in. Leave untouched
/// if method is not implemented.
virtual void buildSilhouette( const SceneCameraState& cameraState, Vector< Point3F >& outPoints ) {}
/// Return true if the given point is contained by the object's (collision) shape.
///
/// The default implementation will return true if the point is within the object's
/// bounding box. Subclasses should implement more precise tests.
virtual bool containsPoint( const Point3F &point );
virtual bool collideBox( const Point3F& start, const Point3F& end, RayInfo* info );
/// Returns the position of the object.
virtual Point3F getPosition() const;
/// Returns the render-position of the object.
///
/// @see getRenderTransform
Point3F getRenderPosition() const;
/// Sets the position of the object
void setPosition ( const Point3F& pos );
/// Gets the velocity of the object.
virtual Point3F getVelocity() const { return Point3F::Zero; }
/// Sets the velocity of the object
/// @param v Velocity
virtual void setVelocity( const Point3F &v ) {}
/// Applies an impulse force to this object
/// @param pos Position where impulse came from in world space
/// @param vec Velocity vector (Impulse force F = m * v)
virtual void applyImpulse( const Point3F &pos, const VectorF &vec ) {}
/// Applies a radial impulse to the object
/// using the impulse origin and force.
/// @param origin Point of origin of the radial impulse.
/// @param radius The radius of the impulse area.
/// @param magnitude The strength of the impulse.
virtual void applyRadialImpulse( const Point3F &origin, F32 radius, F32 magnitude ) {}
/// Returns the distance from this object to a point
/// @param pnt World space point to measure to
virtual F32 distanceTo( const Point3F &pnt ) const;
/// @}
/// @name Mounting
/// @{
/// ex: Mount B to A at A's node N
/// A.mountObject( B, N )
///
/// @param obj Object to mount
/// @param node Mount node ID
virtual void mountObject( SceneObject *obj, S32 node, const MatrixF &xfm = MatrixF::Identity );
/// Remove an object mounting
/// @param obj Object to unmount
virtual void unmountObject( SceneObject *obj );
/// Unmount this object from it's mount
virtual void unmount();
/// Callback when this object is mounted.
/// @param obj Object we are mounting to.
/// @param node Node we are unmounting from.
virtual void onMount( SceneObject *obj, S32 node );
/// Callback when this object is unmounted. This should be overridden to
/// set maskbits or do other object type specific work.
/// @param obj Object we are unmounting from.
/// @param node Node we are unmounting from.
virtual void onUnmount( SceneObject *obj, S32 node );
// Returns mount point to world space transform at tick time.
virtual void getMountTransform( S32 index, const MatrixF &xfm, MatrixF *outMat );
// Returns mount point to world space transform at render time.
// Note this will only be correct if called after this object has interpolated.
virtual void getRenderMountTransform( F32 delta, S32 index, const MatrixF &xfm, MatrixF *outMat );
/// Return the object that this object is mounted to.
virtual SceneObject* getObjectMount() { return mMount.object; }
/// Return object link of next object mounted to this object's mount
virtual SceneObject* getMountLink() { return mMount.link; }
/// Returns object list of objects mounted to this object.
virtual SceneObject* getMountList() { return mMount.list; }
/// Returns the mount id that this is mounted to.
virtual U32 getMountNode() { return mMount.node; }
/// Returns true if this object is mounted to anything at all
/// Also try to resolve the PID to objectId here if it is pending.
virtual bool isMounted();
/// Returns the number of object mounted along with this
virtual S32 getMountedObjectCount();
/// Returns the object mounted at a position in the mount list
/// @param idx Position on the mount list
virtual SceneObject* getMountedObject( S32 idx );
/// Returns the node the object at idx is mounted to
/// @param idx Index
virtual S32 getMountedObjectNode( S32 idx );
/// Returns the object a object on the mount list is mounted to
/// @param node
virtual SceneObject* getMountNodeObject( S32 node );
void resolveMountPID();
/// @}
/// @name Sound
/// @{
/// Return whether the object's collision shape is blocking sound.
bool isOccludingSound() const { return mObjectFlags.test( SoundOccluderFlag ); }
/// Return the ambient sound space active inside the volume of this object or NULL if the object does
/// not have its own ambient space.
virtual SFXAmbience* getSoundAmbience() const { return NULL; }
/// @}
/// @name Rendering
/// @{
/// Called when the SceneManager is ready for the registration of render instances.
/// @param state Rendering state.
virtual void prepRenderImage( SceneRenderState* state ) {}
/// @}
/// @name Lighting
/// @{
void setLightingPlugin( SceneObjectLightingPlugin* plugin ) { mLightPlugin = plugin; }
SceneObjectLightingPlugin* getLightingPlugin() { return mLightPlugin; }
/// @}
/// @name Global Bounds
/// @{
const bool isGlobalBounds() const
{
return mGlobalBounds;
}
/// If global bounds are set to be true, then the object is assumed to
/// have an infinitely large bounding box for collision and rendering
/// purposes.
///
/// They can't be toggled currently.
void setGlobalBounds();
/// @}
/// Return the ProcessList for this object to use.
ProcessList* getProcessList() const;
// ProcessObject,
virtual void processAfter( ProcessObject *obj );
virtual void clearProcessAfter();
virtual ProcessObject* getAfterObject() const { return mAfterObject; }
virtual void setProcessTick( bool t );
// NetObject.
virtual U32 packUpdate( NetConnection* conn, U32 mask, BitStream* stream );
virtual void unpackUpdate( NetConnection* conn, BitStream* stream );
virtual void onCameraScopeQuery( NetConnection* connection, CameraScopeQuery* query );
// SimObject.
virtual bool onAdd();
virtual void onRemove();
virtual void onDeleteNotify( SimObject *object );
virtual void inspectPostApply();
virtual bool writeField( StringTableEntry fieldName, const char* value );
static void initPersistFields();
DECLARE_CONOBJECT( SceneObject );
private:
SceneObject( const SceneObject& ); ///< @deprecated disallowed
/// For ScopeAlways objects to be able to properly implement setHidden(), they
/// need to temporarily give up ScopeAlways status while being hidden. Otherwise
/// the client-side ghost will not disappear as the server-side object will be
/// forced to stay in scope.
bool mIsScopeAlways;
/// @name Protected field getters/setters
/// @{
static const char* _getRenderEnabled( void *object, const char *data );
static bool _setRenderEnabled( void *object, const char *index, const char *data );
static const char* _getSelectionEnabled( void *object, const char *data );
static bool _setSelectionEnabled( void *object, const char *index, const char *data );
static bool _setFieldPosition( void *object, const char *index, const char *data );
static bool _setFieldRotation( void *object, const char *index, const char *data );
static bool _setFieldScale( void *object, const char *index, const char *data );
static bool _setMountPID( void* object, const char* index, const char* data );
/// @}
};
#endif // _SCENEOBJECT_H_

View file

@ -0,0 +1,44 @@
//-----------------------------------------------------------------------------
// 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 _SCENEOBJECTLIGHTINGPLUGIN_H_
#define _SCENEOBJECTLIGHTINGPLUGIN_H_
class SceneObject;
class NetConnection;
class BitStream;
class SceneObjectLightingPlugin
{
public:
virtual ~SceneObjectLightingPlugin() {}
/// Reset light plugin to clean state.
virtual void reset() {}
// Called by statics
virtual U32 packUpdate( SceneObject* obj, U32 checkMask, NetConnection* conn, U32 mask, BitStream* stream ) = 0;
virtual void unpackUpdate( SceneObject* obj, NetConnection* conn, BitStream* stream ) = 0;
};
#endif // !_SCENEOBJECTLIGHTINGPLUGIN_H_

View file

@ -0,0 +1,26 @@
//-----------------------------------------------------------------------------
// 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/scenePolyhedralSpace.h"
#include "scene/mixin/scenePolyhedralObject.impl.h"

View file

@ -0,0 +1,47 @@
//-----------------------------------------------------------------------------
// 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 _SCENEPOLYHEDRALSPACE_H_
#define _SCENEPOLYHEDRALSPACE_H_
#ifndef _SCENESPACE_H_
#include "scene/sceneSpace.h"
#endif
#ifndef _SCENEPOLYHEDRALOBJECT_H_
#include "scene/mixin/scenePolyhedralObject.h"
#endif
///
class ScenePolyhedralSpace : public ScenePolyhedralObject< SceneSpace >
{
public:
typedef ScenePolyhedralObject< SceneSpace > Parent;
ScenePolyhedralSpace() {}
ScenePolyhedralSpace( const PolyhedronType& polyhedron )
: Parent( polyhedron ) {}
};
#endif // !_SCENEPOLYHEDRALSPACE_H_

View file

@ -0,0 +1,110 @@
//-----------------------------------------------------------------------------
// 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/sceneRenderState.h"
#include "renderInstance/renderPassManager.h"
#include "math/util/matrixSet.h"
//-----------------------------------------------------------------------------
SceneRenderState::SceneRenderState( SceneManager* sceneManager,
ScenePassType passType,
const SceneCameraState& view,
RenderPassManager* renderPass /* = NULL */,
bool usePostEffects /* = true */ )
: mSceneManager( sceneManager ),
mCullingState( sceneManager, view ),
mRenderPass( renderPass ? renderPass : sceneManager->getDefaultRenderPass() ),
mScenePassType( passType ),
mRenderNonLightmappedMeshes( true ),
mRenderLightmappedMeshes( true ),
mUsePostEffects( usePostEffects ),
mDisableAdvancedLightingBins( false ),
mRenderArea( view.getFrustum().getBounds() ),
mAmbientLightColor( sceneManager->getAmbientLightColor() )
{
// Setup the default parameters for the screen metrics methods.
mDiffuseCameraTransform = view.getViewWorldMatrix();
// The vector eye is the camera vector with its
// length normalized to 1 / zFar.
getCameraTransform().getColumn( 1, &mVectorEye );
mVectorEye.normalize( 1.0f / getFarPlane() );
// TODO: What about ortho modes? Is near plane ok
// or do i need to remove it... maybe ortho has a near
// plane of 1 and it just works out?
const Frustum& frustum = view.getFrustum();
const RectI& viewport = view.getViewport();
mWorldToScreenScale.set( ( frustum.getNearDist() * viewport.extent.x ) / ( frustum.getNearRight() - frustum.getNearLeft() ),
( frustum.getNearDist() * viewport.extent.y ) / ( frustum.getNearTop() - frustum.getNearBottom() ) );
// Assign shared matrix data to the render pass.
mRenderPass->assignSharedXform( RenderPassManager::View, view.getWorldViewMatrix() );
mRenderPass->assignSharedXform( RenderPassManager::Projection, view.getProjectionMatrix() );
}
//-----------------------------------------------------------------------------
SceneRenderState::~SceneRenderState()
{
}
//-----------------------------------------------------------------------------
const MatrixF& SceneRenderState::getWorldViewMatrix() const
{
return getRenderPass()->getMatrixSet().getWorldToCamera();
}
//-----------------------------------------------------------------------------
const MatrixF& SceneRenderState::getProjectionMatrix() const
{
return getRenderPass()->getMatrixSet().getCameraToScreen();
}
//-----------------------------------------------------------------------------
void SceneRenderState::renderObjects( SceneObject** objects, U32 numObjects )
{
// Let the objects batch their stuff.
PROFILE_START( SceneRenderState_prepRenderImages );
for( U32 i = 0; i < numObjects; ++ i )
{
SceneObject* object = objects[ i ];
object->prepRenderImage( this );
}
PROFILE_END();
// Render what the objects have batched.
getRenderPass()->renderPass( this );
}

View file

@ -0,0 +1,315 @@
//-----------------------------------------------------------------------------
// 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 _SCENERENDERSTATE_H_
#define _SCENERENDERSTATE_H_
#ifndef _COLOR_H_
#include "core/color.h"
#endif
#ifndef _SCENEMANAGER_H_
#include "scene/sceneManager.h"
#endif
#ifndef _SCENECULLINGSTATE_H_
#include "scene/culling/sceneCullingState.h"
#endif
#ifndef _UTIL_DELEGATE_H_
#include "core/util/delegate.h"
#endif
class SceneObject;
class RenderPassManager;
class BaseMatInstance;
/// The SceneRenderState describes the state of the scene being rendered.
///
/// It keeps track of the information that objects need to render properly with regard to
/// the camera position, any fog information, viewing frustum, the global environment
/// map for reflections, viewable distance, etc.
///
/// It also owns the current culling state.
class SceneRenderState
{
public:
/// The delegate used for material overrides.
/// @see getOverrideMaterial
typedef Delegate< BaseMatInstance*( BaseMatInstance* ) > MatDelegate;
protected:
/// SceneManager being rendered in this state.
SceneManager* mSceneManager;
/// The type of scene render pass we're doing.
ScenePassType mScenePassType;
/// The render pass which we are setting up with this scene state.
RenderPassManager* mRenderPass;
/// Culling state of the scene.
SceneCullingState mCullingState;
/// The optional material override delegate.
MatDelegate mMatDelegate;
///
MatrixF mDiffuseCameraTransform;
/// The world to screen space scalar used for LOD calculations.
Point2F mWorldToScreenScale;
/// The AABB that encloses the space in the scene that we render.
Box3F mRenderArea;
/// The camera vector normalized to 1 / far dist.
Point3F mVectorEye;
/// Global ambient light color.
ColorF mAmbientLightColor;
/// Forces bin based post effects to be disabled
/// during rendering with this scene state.
bool mUsePostEffects;
/// Disables AdvancedLighting bin draws during rendering with this scene state.
bool mDisableAdvancedLightingBins;
/// If true (default) lightmapped meshes should be rendered.
bool mRenderLightmappedMeshes;
/// If true (default) non-lightmapped meshes should be rendered.
bool mRenderNonLightmappedMeshes;
public:
/// Construct a new SceneRenderState.
///
/// @param sceneManager SceneManager rendered in this SceneRenderState.
/// @param passType Type of rendering pass that the SceneRenderState is for.
/// @param view The view that is being rendered
/// @param renderPass The render pass which is being set up by this SceneRenderState. If NULL,
/// then Scene::getDefaultRenderPass() is used.
/// @param usePostEffect Whether PostFX are enabled in the rendering pass.
SceneRenderState( SceneManager* sceneManager,
ScenePassType passType,
const SceneCameraState& view = SceneCameraState::fromGFX(),
RenderPassManager* renderPass = NULL,
bool usePostEffects = true );
~SceneRenderState();
/// Return the SceneManager that is being rendered in this SceneRenderState.
SceneManager* getSceneManager() const { return mSceneManager; }
/// If true then bin based post effects are disabled
/// during rendering with this scene state.
bool usePostEffects() const { return mUsePostEffects; }
void usePostEffects( bool value ) { mUsePostEffects = value; }
/// @name Culling
/// @{
/// Return the culling state for the scene.
const SceneCullingState& getCullingState() const { return mCullingState; }
SceneCullingState& getCullingState() { return mCullingState; }
/// Returns the root frustum.
const Frustum& getFrustum() const { return getCullingState().getFrustum(); }
/// @}
/// @name Rendering
/// @{
/// Get the AABB around the scene portion that we render.
const Box3F& getRenderArea() const { return mRenderArea; }
/// Set the AABB of the space that should be rendered.
void setRenderArea( const Box3F& area ) { mRenderArea = area; }
/// Batch the given objects to the render pass manager and then
/// render the batched instances.
///
/// @param objects List of objects.
/// @param numObjects Number of objects in @a objects.
void renderObjects( SceneObject** objects, U32 numObjects );
/// @}
/// @name Lighting
/// @{
/// Return the ambient light color to use for rendering the scene.
///
/// At the moment, we only support a single global ambient color with which
/// all objects in the scene are rendered. This is because when using
/// Advanced Lighting, we are not resolving light contribution on a per-surface
/// or per-object basis but rather do it globally by gathering light
/// contribution to the whole scene and since the ambient factor is decided
/// by the sun/vector light, it simply becomes a base light level onto which
/// shadowing/lighting is blended based on the shadow maps of the sun/vector
/// light.
///
/// @return The ambient light color for rendering.
ColorF getAmbientLightColor() const { return mAmbientLightColor; }
/// Set the global ambient light color to render with.
void setAmbientLightColor( const ColorF& color ) { mAmbientLightColor = color; }
/// If true then Advanced Lighting bin draws are disabled during rendering with
/// this scene state.
bool disableAdvancedLightingBins() const { return mDisableAdvancedLightingBins; }
void disableAdvancedLightingBins(bool enabled) { mDisableAdvancedLightingBins = enabled; }
bool renderLightmappedMeshes() const { return mRenderLightmappedMeshes; }
void renderLightmappedMeshes( bool enabled ) { mRenderLightmappedMeshes = enabled; }
bool renderNonLightmappedMeshes() const { return mRenderNonLightmappedMeshes; }
void renderNonLightmappedMeshes( bool enabled ) { mRenderNonLightmappedMeshes = enabled; }
/// @}
/// @name Passes
/// @{
/// Return the RenderPassManager that manages rendering objects batched
/// for this SceneRenderState.
RenderPassManager* getRenderPass() const { return mRenderPass; }
/// Returns the type of scene rendering pass that we're doing.
ScenePassType getScenePassType() const { return mScenePassType; }
/// Returns true if this is a diffuse scene rendering pass.
bool isDiffusePass() const { return mScenePassType == SPT_Diffuse; }
/// Returns true if this is a reflection scene rendering pass.
bool isReflectPass() const { return mScenePassType == SPT_Reflect; }
/// Returns true if this is a shadow scene rendering pass.
bool isShadowPass() const { return mScenePassType == SPT_Shadow; }
/// Returns true if this is not one of the other rendering passes.
bool isOtherPass() const { return mScenePassType >= SPT_Other; }
/// @}
/// @name Transforms, projections, and viewports.
/// @{
/// Return the screen-space viewport rectangle.
const RectI& getViewport() const { return getCullingState().getCameraState().getViewport(); }
/// Return the world->view transform matrix.
const MatrixF& getWorldViewMatrix() const;
/// Return the project transform matrix.
const MatrixF& getProjectionMatrix() const;
/// Returns the actual camera position.
/// @see getDiffuseCameraPosition
const Point3F& getCameraPosition() const { return getCullingState().getCameraState().getViewPosition(); }
/// Returns the camera transform (view->world) this SceneRenderState is using.
const MatrixF& getCameraTransform() const { return getCullingState().getCameraState().getViewWorldMatrix(); }
/// Returns the minimum distance something must be from the camera to not be culled.
F32 getNearPlane() const { return getFrustum().getNearDist(); }
/// Returns the maximum distance something can be from the camera to not be culled.
F32 getFarPlane() const { return getFrustum().getFarDist(); }
/// Returns the camera vector normalized to 1 / far distance.
const Point3F& getVectorEye() const { return mVectorEye; }
/// Returns the possibly overloaded world to screen scale.
/// @see projectRadius
const Point2F& getWorldToScreenScale() const { return mWorldToScreenScale; }
/// Set a new world to screen scale to overload
/// future screen metrics operations.
void setWorldToScreenScale( const Point2F& scale ) { mWorldToScreenScale = scale; }
/// Returns the pixel size of the radius projected to the screen at a desired distance.
///
/// Internally this uses the stored world to screen scale and viewport extents. This
/// allows the projection to be overloaded in special cases like when rendering shadows
/// or reflections.
///
/// @see getWorldToScreenScale
/// @see getViewportExtent
F32 projectRadius( F32 dist, F32 radius ) const
{
// We fixup any negative or zero distance
// so we don't get a divide by zero.
dist = dist > 0.0f ? dist : 0.001f;
return ( radius / dist ) * mWorldToScreenScale.y;
}
/// Returns the camera position used during the diffuse rendering pass which may be different
/// from the actual camera position.
///
/// This is useful when doing level of detail calculations that need to be relative to the
/// diffuse pass.
///
/// @see getCameraPosition
Point3F getDiffuseCameraPosition() const { return mDiffuseCameraTransform.getPosition(); }
const MatrixF& getDiffuseCameraTransform() const { return mDiffuseCameraTransform; }
/// Set a new diffuse camera transform.
/// @see getDiffuseCameraTransform
void setDiffuseCameraTransform( const MatrixF &mat ) { mDiffuseCameraTransform = mat; }
/// @}
/// @name Material Overrides
/// @{
/// When performing a special render pass like shadows this
/// returns a specialized override material. It can return
/// NULL if the override wants to disable rendering. If
/// there is no override in place then the input material is
/// returned unaltered.
BaseMatInstance* getOverrideMaterial( BaseMatInstance* matInst ) const
{
if ( !matInst || mMatDelegate.empty() )
return matInst;
return mMatDelegate( matInst );
}
/// Returns the optional material override delegate which is
/// used during some special render passes.
/// @see getOverrideMaterial
MatDelegate& getMaterialDelegate() { return mMatDelegate; }
const MatDelegate& getMaterialDelegate() const { return mMatDelegate; }
/// @}
};
#endif // _SCENERENDERSTATE_H_

View file

@ -0,0 +1,206 @@
//-----------------------------------------------------------------------------
// 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/sceneSpace.h"
#include "core/stream/bitStream.h"
#include "console/engineAPI.h"
#include "math/mathIO.h"
#include "math/mOrientedBox.h"
#include "gfx/primBuilder.h"
#include "gfx/gfxDrawUtil.h"
#include "gfx/gfxTransformSaver.h"
#include "renderInstance/renderPassManager.h"
#include "materials/materialDefinition.h"
#include "materials/baseMatInstance.h"
#include "scene/sceneRenderState.h"
extern bool gEditingMission;
//-----------------------------------------------------------------------------
SceneSpace::SceneSpace()
: mEditorRenderMaterial( NULL )
{
mNetFlags.set( Ghostable | ScopeAlways );
mTypeMask |= StaticObjectType;
// Except when their rendering is otherwise suppressed, we do not want
// spaces to get culled away when in the editor.
mObjectFlags |= DisableCullingInEditorFlag;
}
//-----------------------------------------------------------------------------
SceneSpace::~SceneSpace()
{
SAFE_DELETE( mEditorRenderMaterial );
}
//-----------------------------------------------------------------------------
bool SceneSpace::onAdd()
{
if( !Parent::onAdd() )
return false;
addToScene();
return true;
}
//-----------------------------------------------------------------------------
void SceneSpace::onRemove()
{
removeFromScene();
Parent::onRemove();
}
//-----------------------------------------------------------------------------
void SceneSpace::setTransform(const MatrixF & mat)
{
Parent::setTransform( mat );
if( isServerObject() )
setMaskBits( TransformMask );
}
//-----------------------------------------------------------------------------
void SceneSpace::onEditorEnable()
{
// If we haven't created a material for editor rendering yet,
// try so now.
if( isClientObject() && !mEditorRenderMaterial )
mEditorRenderMaterial = _createEditorRenderMaterial();
}
//-----------------------------------------------------------------------------
void SceneSpace::onEditorDisable()
{
SAFE_DELETE( mEditorRenderMaterial );
}
//-----------------------------------------------------------------------------
BaseMatInstance* SceneSpace::_createEditorRenderMaterial()
{
String materialName = String::ToString( "Editor%sMaterial", getClassName() );
Material* material;
if( !Sim::findObject( materialName, material ) )
return NULL;
return material->createMatInstance();
}
//-----------------------------------------------------------------------------
void SceneSpace::prepRenderImage( SceneRenderState* state )
{
if( !gEditingMission )
return;
if( !state->isDiffusePass() )
return;
ObjectRenderInst* ri = state->getRenderPass()->allocInst< ObjectRenderInst >();
ri->renderDelegate.bind( this, &SceneSpace::_renderObject );
ri->type = RenderPassManager::RIT_Editor;
ri->defaultKey = 0;
ri->defaultKey2 = 0;
state->getRenderPass()->addInst( ri );
}
//-----------------------------------------------------------------------------
void SceneSpace::_renderObject( ObjectRenderInst* ri, SceneRenderState* state, BaseMatInstance* overrideMat )
{
if( overrideMat )
return;
if( !mEditorRenderMaterial )
{
// We have no material for rendering so just render
// a plain box.
GFXTransformSaver saver;
MatrixF mat = getRenderTransform();
mat.scale( getScale() );
GFX->multWorld( mat );
GFXStateBlockDesc desc;
desc.setZReadWrite( true, false );
desc.setBlend( true );
desc.setCullMode( GFXCullNone );
GFXDrawUtil *drawer = GFX->getDrawUtil();
drawer->drawCube( desc, mObjBox, _getDefaultEditorSolidColor() );
// Render black wireframe.
desc.setFillModeWireframe();
drawer->drawCube( desc, mObjBox, _getDefaultEditorWireframeColor() );
}
else
{
//RDTODO
}
}
//-----------------------------------------------------------------------------
U32 SceneSpace::packUpdate( NetConnection* connection, U32 mask, BitStream* stream )
{
U32 retMask = Parent::packUpdate( connection, mask, stream );
if( stream->writeFlag( mask & TransformMask ) )
{
mathWrite( *stream, mObjToWorld );
mathWrite( *stream, mObjScale );
}
return retMask;
}
//-----------------------------------------------------------------------------
void SceneSpace::unpackUpdate( NetConnection* connection, BitStream* stream )
{
Parent::unpackUpdate( connection, stream );
if( stream->readFlag() ) // TransformMask
{
mathRead( *stream, &mObjToWorld );
mathRead( *stream, &mObjScale );
setTransform( mObjToWorld );
}
}

View file

@ -0,0 +1,83 @@
//-----------------------------------------------------------------------------
// 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 _SCENESPACE_H_
#define _SCENESPACE_H_
#ifndef _SCENEOBJECT_H_
#include "scene/sceneObject.h"
#endif
/// Abstract base class for SceneObjects that define subspaces in a scene.
///
/// Use SceneObject::containsPoint to find out whether a given space contains a particular point.
class SceneSpace : public SceneObject
{
public:
typedef SceneObject Parent;
protected:
enum
{
TransformMask = Parent::NextFreeMask << 0, ///< Object transform has changed.
NextFreeMask = Parent::NextFreeMask << 1,
};
///
BaseMatInstance* mEditorRenderMaterial;
///
virtual BaseMatInstance* _createEditorRenderMaterial();
/// Render a visualization of the volume.
virtual void _renderObject( ObjectRenderInst* ri, SceneRenderState* state, BaseMatInstance* overrideMat );
///
virtual ColorI _getDefaultEditorSolidColor() const { return ColorI( 255, 255, 255, 45 ); }
virtual ColorI _getDefaultEditorWireframeColor() const { return ColorI::BLACK; }
public:
SceneSpace();
~SceneSpace();
// SimObject.
virtual bool onAdd();
virtual void onRemove();
// SceneObject.
virtual void setTransform( const MatrixF &mat );
virtual void prepRenderImage( SceneRenderState* state );
// NetObject.
virtual U32 packUpdate( NetConnection* connection, U32 mask, BitStream* stream );
virtual void unpackUpdate( NetConnection* connection, BitStream* stream );
// SimObject.
virtual void onEditorEnable();
virtual void onEditorDisable();
};
#endif // !_SCENESPACE_H_

View file

@ -0,0 +1,128 @@
//-----------------------------------------------------------------------------
// 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 "scene/sceneTracker.h"
//=============================================================================
// SceneObjectLink.
//=============================================================================
//-----------------------------------------------------------------------------
SceneObjectLink::SceneObjectLink( SceneTracker* tracker, SceneObject* object )
: mTracker( tracker ),
mObject( object ),
mNextLink( NULL ),
mPrevLink( NULL )
{
if( object )
{
mNextLink = object->mSceneObjectLinks;
if( mNextLink )
mNextLink->mPrevLink = this;
object->mSceneObjectLinks = this;
}
}
//-----------------------------------------------------------------------------
SceneObjectLink::~SceneObjectLink()
{
if( mObject )
{
// Unlink from SceneObject's tracker list.
if( mNextLink )
mNextLink->mPrevLink = mPrevLink;
if( mPrevLink )
mPrevLink->mNextLink = mNextLink;
else
mObject->mSceneObjectLinks = mNextLink;
}
}
//-----------------------------------------------------------------------------
void SceneObjectLink::update()
{
getTracker()->updateObject( this );
}
//-----------------------------------------------------------------------------
SceneObjectLink* SceneObjectLink::getLinkForTracker( SceneTracker* tracker, SceneObject* fromObject )
{
for( SceneObjectLink* link = fromObject->mSceneObjectLinks; link != NULL; link = link->getNextLink() )
if( link->getTracker() == tracker )
return link;
return NULL;
}
//=============================================================================
// SceneObjectLink.
//=============================================================================
//-----------------------------------------------------------------------------
SceneTracker::SceneTracker( bool isClientTracker, U32 typeMask )
: mIsClientTracker( isClientTracker ),
mObjectTypeMask( typeMask )
{
// Hook up to SceneObject add/remove notifications.
SceneObject::smSceneObjectAdd.notify( this, &SceneTracker::registerObject );
SceneObject::smSceneObjectRemove.notify( this, &SceneTracker::unregisterObject );
}
//-----------------------------------------------------------------------------
SceneTracker::~SceneTracker()
{
SceneObject::smSceneObjectAdd.remove( this, &SceneTracker::registerObject );
SceneObject::smSceneObjectRemove.remove( this, &SceneTracker::unregisterObject );
}
//-----------------------------------------------------------------------------
void SceneTracker::init()
{
// Register existing scene graph objects.
SceneContainer* container;
if( isClientTracker() )
container = &gClientContainer;
else
container = &gServerContainer;
container->findObjects( getObjectTypeMask(),
( SceneContainer::FindCallback ) &_containerFindCallback,
this );
}
//-----------------------------------------------------------------------------
void SceneTracker::_containerFindCallback( SceneObject* object, SceneTracker* tracker )
{
tracker->registerObject( object );
}

View file

@ -0,0 +1,163 @@
//-----------------------------------------------------------------------------
// 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 _SCENETRACKER_H_
#define _SCENETRACKER_H_
#ifndef _SCENEOBJECT_H_
#include "scene/sceneObject.h"
#endif
/// @file
/// This file contains an abstract framework for tracking SceneObjects.
class SceneTracker;
//-----------------------------------------------------------------------------
// SceneObjectLink.
//-----------------------------------------------------------------------------
/// A SceneObjectLink represents the link between a SceneObject and a SceneTracker.
class SceneObjectLink
{
public:
typedef void Parent;
friend class SceneTracker; // Administers our link fields.
protected:
/// SceneObject being linked to; always set and never changes.
SceneObject* mObject;
/// The scene tracker to which this link belongs.
SceneTracker* mTracker;
/// Next scope link on this SceneObject; NULL if last.
SceneObjectLink* mNextLink;
/// Previous scope link on this SceneObject; NULL if first.
SceneObjectLink* mPrevLink;
public:
///
SceneObjectLink( SceneTracker* tracker, SceneObject* object );
virtual ~SceneObjectLink();
/// @return The SceneScopeTracker managing this link.
SceneTracker* getTracker() const { return mTracker; }
/// @return The object being linked to.
SceneObject* getObject() const { return mObject; }
/// @return The next link in this link chain.
SceneObjectLink* getNextLink() const { return mNextLink; }
/// @return The previous link in this link chain.
SceneObjectLink* getPrevLink() const { return mPrevLink; }
/// Notify the associated tracker that the transform state of the
/// scene object represented by this link has changed.
void update();
///
static SceneObjectLink* getLinkForTracker( SceneTracker* tracker, SceneObject* fromObject );
};
//-----------------------------------------------------------------------------
// SceneTracker.
//-----------------------------------------------------------------------------
/// A SceneTracker tracks SceneObjects.
///
/// This is an abstract base class.
class SceneTracker
{
public:
typedef void Parent;
friend class SceneObjectLink; // SceneObjectLink::update() notifies us on SceneObject state changes.
protected:
/// If true, only client SceneObjects will be tracked; otherwise it's only server SceneObjects.
bool mIsClientTracker;
/// Type mask that SceneObjects must match in order to be allowed to register.
U32 mObjectTypeMask;
/// Return true if the given object qualifies for being managed by this SceneTracker.
virtual bool _isTrackableObject( SceneObject* object ) const
{
return ( object->isClientObject() == mIsClientTracker
&& ( object->getTypeMask() & getObjectTypeMask() ) );
}
/// Callback used for the initial scan of objects in init().
static void _containerFindCallback( SceneObject* object, SceneTracker* tracker );
public:
///
SceneTracker( bool isClientTracker, U32 typeMask = 0xFFFFFFFF );
virtual ~SceneTracker();
/// Initialize the tracker from the current scene.
virtual void init();
/// @return The type mask that must be matched by objects in order to be allowed to register.
U32 getObjectTypeMask() const { return mObjectTypeMask; }
/// Set the type mask that objects must match in order to be allowed to register.
void setObjectTypeMask( U32 typeMask ) { mObjectTypeMask = typeMask; }
/// @return True if this tracker only deals with client objects; false if only server objects.
bool isClientTracker() const { return mIsClientTracker; }
/// Register a SceneObject for being tracked by this tracker.
///
/// Only objects that fit the tracker's client/server state and
/// object type mask will actually get registered. For other objects,
/// this is a NOP.
///
/// @param object Scene object.
virtual void registerObject( SceneObject* object ) = 0;
/// Unregister the given object from the tracker.
/// @param object Scene object.
virtual void unregisterObject( SceneObject* object ) = 0;
/// Notify the tracker that the transform state of the given scene object has changed.
/// @param object Scene object.
virtual void updateObject( SceneObjectLink* object ) = 0;
};
#endif // !_SCENETRACKER_H_

View file

@ -0,0 +1,327 @@
//-----------------------------------------------------------------------------
// 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 "scene/sgUtil.h"
#include "math/mRect.h"
#include "math/mMatrix.h"
#include "math/mPlane.h"
namespace {
// Static state for sgComputeNewFrustum
//
Point3F sgCamPoint;
MatrixF sgWSToOSMatrix;
MatrixF sgProjMatrix;
PlaneF sgOSPlaneFar;
PlaneF sgOSPlaneXMin;
PlaneF sgOSPlaneXMax;
PlaneF sgOSPlaneYMin;
PlaneF sgOSPlaneYMax;
void clipToPlane(Point3F* points, U32& rNumPoints, const PlaneF& rPlane)
{
S32 start = -1;
for (U32 i = 0; i < rNumPoints; i++) {
if (rPlane.whichSide(points[i]) == PlaneF::Front) {
start = i;
break;
}
}
// Nothing was in front of the plane...
if (start == -1) {
rNumPoints = 0;
return;
}
Point3F finalPoints[128];
U32 numFinalPoints = 0;
U32 baseStart = start;
U32 end = (start + 1) % rNumPoints;
while (end != baseStart) {
const Point3F& rStartPoint = points[start];
const Point3F& rEndPoint = points[end];
PlaneF::Side fSide = rPlane.whichSide(rStartPoint);
PlaneF::Side eSide = rPlane.whichSide(rEndPoint);
S32 code = fSide * 3 + eSide;
switch (code) {
case 4: // f f
case 3: // f o
case 1: // o f
case 0: // o o
// No Clipping required
finalPoints[numFinalPoints++] = points[start];
start = end;
end = (end + 1) % rNumPoints;
break;
case 2: { // f b
// In this case, we emit the front point, Insert the intersection,
// and advancing to point to first point that is in front or on...
//
finalPoints[numFinalPoints++] = points[start];
Point3F vector = rEndPoint - rStartPoint;
F32 t = -(rPlane.distToPlane(rStartPoint) / mDot(rPlane, vector));
Point3F intersection = rStartPoint + (vector * t);
finalPoints[numFinalPoints++] = intersection;
U32 endSeek = (end + 1) % rNumPoints;
while (rPlane.whichSide(points[endSeek]) == PlaneF::Back)
endSeek = (endSeek + 1) % rNumPoints;
end = endSeek;
start = (end + (rNumPoints - 1)) % rNumPoints;
const Point3F& rNewStartPoint = points[start];
const Point3F& rNewEndPoint = points[end];
vector = rNewEndPoint - rNewStartPoint;
t = -(rPlane.distToPlane(rNewStartPoint) / mDot(rPlane, vector));
intersection = rNewStartPoint + (vector * t);
points[start] = intersection;
}
break;
case -1: {// o b
// In this case, we emit the front point, and advance to point to first
// point that is in front or on...
//
finalPoints[numFinalPoints++] = points[start];
U32 endSeek = (end + 1) % rNumPoints;
while (rPlane.whichSide(points[endSeek]) == PlaneF::Back)
endSeek = (endSeek + 1) % rNumPoints;
end = endSeek;
start = (end + (rNumPoints - 1)) % rNumPoints;
const Point3F& rNewStartPoint = points[start];
const Point3F& rNewEndPoint = points[end];
Point3F vector = rNewEndPoint - rNewStartPoint;
F32 t = -(rPlane.distToPlane(rNewStartPoint) / mDot(rPlane, vector));
Point3F intersection = rNewStartPoint + (vector * t);
points[start] = intersection;
}
break;
case -2: // b f
case -3: // b o
case -4: // b b
// In the algorithm used here, this should never happen...
AssertISV(false, "SGUtil::clipToPlane: error in polygon clipper");
break;
default:
AssertFatal(false, "SGUtil::clipToPlane: bad outcode");
break;
}
}
// Emit the last point.
finalPoints[numFinalPoints++] = points[start];
AssertFatal(numFinalPoints >= 3, avar("Error, this shouldn't happen! Invalid winding in clipToPlane: %d", numFinalPoints));
// Copy the new rWinding, and we're set!
//
dMemcpy(points, finalPoints, numFinalPoints * sizeof(Point3F));
rNumPoints = numFinalPoints;
AssertISV(rNumPoints <= 128, "MaxWindingPoints exceeded in scenegraph. Fatal error.");
}
void fixupViewport(const F64* oldFrustum,
const RectI& oldViewport,
F64* newFrustum,
RectI& newViewport)
{
F64 widthV = newFrustum[1] - newFrustum[0];
F64 heightV = newFrustum[3] - newFrustum[2];
F64 fx0 = (newFrustum[0] - oldFrustum[0]) / (oldFrustum[1] - oldFrustum[0]);
F64 fx1 = (oldFrustum[1] - newFrustum[1]) / (oldFrustum[1] - oldFrustum[0]);
F64 dV0 = F64(oldViewport.point.x) + fx0 * F64(oldViewport.extent.x);
F64 dV1 = F64(oldViewport.point.x +
oldViewport.extent.x) - fx1 * F64(oldViewport.extent.x);
F64 fdV0 = mFloor(dV0);
F64 cdV1 = mCeil(dV1);
F64 new0 = newFrustum[0] - ((dV0 - fdV0) * (widthV / F64(oldViewport.extent.x)));
F64 new1 = newFrustum[1] + ((cdV1 - dV1) * (widthV / F64(oldViewport.extent.x)));
newFrustum[0] = new0;
newFrustum[1] = new1;
newViewport.point.x = S32(fdV0);
newViewport.extent.x = S32(cdV1) - newViewport.point.x;
F64 fy0 = (oldFrustum[3] - newFrustum[3]) / (oldFrustum[3] - oldFrustum[2]);
F64 fy1 = (newFrustum[2] - oldFrustum[2]) / (oldFrustum[3] - oldFrustum[2]);
dV0 = F64(oldViewport.point.y) + fy0 * F64(oldViewport.extent.y);
dV1 = F64(oldViewport.point.y + oldViewport.extent.y) - fy1 * F64(oldViewport.extent.y);
fdV0 = mFloor(dV0);
cdV1 = mCeil(dV1);
new0 = newFrustum[2] - ((cdV1 - dV1) * (heightV / F64(oldViewport.extent.y)));
new1 = newFrustum[3] + ((dV0 - fdV0) * (heightV / F64(oldViewport.extent.y)));
newFrustum[2] = new0;
newFrustum[3] = new1;
newViewport.point.y = S32(fdV0);
newViewport.extent.y = S32(cdV1) - newViewport.point.y;
}
bool projectClipAndBoundWinding(const SGWinding& rWinding, F64* pResult)
{
AssertFatal(rWinding.numPoints >= 3, "Error, that's not a winding!");
static Point3F windingPoints[128];
U32 i;
for (i = 0; i < rWinding.numPoints; i++)
windingPoints[i] = rWinding.points[i];
U32 numPoints = rWinding.numPoints;
clipToPlane(windingPoints, numPoints, sgOSPlaneFar);
if (numPoints != 0)
clipToPlane(windingPoints, numPoints, sgOSPlaneXMin);
if (numPoints != 0)
clipToPlane(windingPoints, numPoints, sgOSPlaneXMax);
if (numPoints != 0)
clipToPlane(windingPoints, numPoints, sgOSPlaneYMin);
if (numPoints != 0)
clipToPlane(windingPoints, numPoints, sgOSPlaneYMax);
if (numPoints == 0)
return false;
Point4F projPoint;
for (i = 0; i < numPoints; i++) {
projPoint.set(windingPoints[i].x, windingPoints[i].y, windingPoints[i].z, 1.0);
sgProjMatrix.mul(projPoint);
AssertFatal(projPoint.w != 0.0, "Error, that's bad! (Point projected with non-zero w.)");
projPoint.x /= projPoint.w;
projPoint.y /= projPoint.w;
if (projPoint.x < pResult[0])
pResult[0] = projPoint.x;
if (projPoint.x > pResult[1])
pResult[1] = projPoint.x;
if (projPoint.y < pResult[2])
pResult[2] = projPoint.y;
if (projPoint.y > pResult[3])
pResult[3] = projPoint.y;
}
if (pResult[0] < -1.0f) pResult[0] = -1.0f;
if (pResult[2] < -1.0f) pResult[2] = -1.0f;
if (pResult[1] > 1.0f) pResult[1] = 1.0f;
if (pResult[3] > 1.0f) pResult[3] = 1.0f;
return true;
}
} // namespace { }
//--------------------------------------------------------------------------
bool sgComputeNewFrustum(const Frustum &oldFrustum,
const F64 nearPlane,
const F64 farPlane,
const RectI& oldViewport,
const SGWinding* windings,
const U32 numWindings,
const MatrixF& modelview,
F64 *newFrustum,
RectI& newViewport,
const bool flippedMatrix)
{
return false;
}
void sgComputeOSFrustumPlanes(const F64 frustumParameters[6],
const MatrixF& worldSpaceToObjectSpace,
const Point3F& wsCamPoint,
PlaneF& outFarPlane,
PlaneF& outXMinPlane,
PlaneF& outXMaxPlane,
PlaneF& outYMinPlane,
PlaneF& outYMaxPlane)
{
// Create the object space clipping planes...
Point3F ul(frustumParameters[0] * 1000.0, frustumParameters[4] * 1000.0, frustumParameters[3] * 1000.0);
Point3F ur(frustumParameters[1] * 1000.0, frustumParameters[4] * 1000.0, frustumParameters[3] * 1000.0);
Point3F ll(frustumParameters[0] * 1000.0, frustumParameters[4] * 1000.0, frustumParameters[2] * 1000.0);
Point3F lr(frustumParameters[1] * 1000.0, frustumParameters[4] * 1000.0, frustumParameters[2] * 1000.0);
Point3F farPlane(0, frustumParameters[5], 0);
worldSpaceToObjectSpace.mulP(ul);
worldSpaceToObjectSpace.mulP(ur);
worldSpaceToObjectSpace.mulP(ll);
worldSpaceToObjectSpace.mulP(lr);
worldSpaceToObjectSpace.mulP(farPlane);
outFarPlane.set(farPlane, wsCamPoint - farPlane);
outXMinPlane.set(wsCamPoint, ul, ll);
outXMaxPlane.set(wsCamPoint, lr, ur);
outYMinPlane.set(wsCamPoint, ur, ul);
outYMaxPlane.set(wsCamPoint, ll, lr);
}
// MM/JF: Added for mirrorSubObject fix.
void sgOrientClipPlanes(
PlaneF * planes,
const Point3F & camPos,
const Point3F & leftUp,
const Point3F & leftDown,
const Point3F & rightUp,
const Point3F & rightDown)
{
AssertFatal(planes, "orientClipPlanes: NULL planes ptr");
planes[0].set(camPos, leftUp, leftDown);
planes[1].set(camPos, rightUp, leftUp);
planes[2].set(camPos, rightDown, rightUp);
planes[3].set(camPos, leftDown, rightDown);
planes[4].set(leftUp, rightUp, rightDown);
// clip-planes through mirror portal are inverted
PlaneF plane(leftUp, rightUp, rightDown);
if(plane.whichSide(camPos) == PlaneF::Back)
for(U32 i = 0; i < 5; i++)
planes[i].invert();
}

View file

@ -0,0 +1,75 @@
//-----------------------------------------------------------------------------
// 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 _SGUTIL_H_
#define _SGUTIL_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _MPOINT3_H_
#include "math/mPoint3.h"
#endif
class Frustum;
class RectI;
class MatrixF;
class PlaneF;
struct SGWinding
{
Point3F points[32];
U32 numPoints;
};
bool sgComputeNewFrustum(const Frustum &oldFrustum,
const F64 nearPlane,
const F64 farPlane,
const RectI& oldViewport,
const SGWinding* windings,
const U32 numWindings,
const MatrixF& modelview,
F64 *newFrustum,
RectI& newViewport,
const bool flippedMatrix);
/// Compute frustrum planes.
///
/// Frustum parameters are:
/// - [0] = left
/// - [1] = right
/// - [2] = top
/// - [3] = bottom
/// - [4] = near
/// - [5] = far
void sgComputeOSFrustumPlanes(const F64 frustumParameters[6],
const MatrixF& worldSpaceToObjectSpace,
const Point3F& wsCamPoint,
PlaneF& outFarPlane,
PlaneF& outXMinPlane,
PlaneF& outXMaxPlane,
PlaneF& outYMinPlane,
PlaneF& outYMaxPlane);
void sgOrientClipPlanes(PlaneF * planes, const Point3F & camPos, const Point3F & leftUp, const Point3F & leftDown, const Point3F & rightUp, const Point3F & rightDown);
#endif // _H_SGUTIL_

View file

@ -0,0 +1,526 @@
//-----------------------------------------------------------------------------
// 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/simPath.h"
#include "gfx/gfxDevice.h"
#include "gfx/gfxVertexBuffer.h"
#include "gfx/gfxPrimitiveBuffer.h"
#include "gfx/gfxTransformSaver.h"
#include "console/consoleTypes.h"
#include "scene/pathManager.h"
#include "scene/sceneRenderState.h"
#include "math/mathIO.h"
#include "core/stream/bitStream.h"
#include "renderInstance/renderPassManager.h"
#include "console/engineAPI.h"
extern bool gEditingMission;
//--------------------------------------------------------------------------
//-------------------------------------- Console functions and cmp funcs
//
DefineEngineFunction(pathOnMissionLoadDone, void, (),,
"@brief Load all Path information from the mission.\n\n"
"This function is usually called from the loadMissionStage2() server-side function "
"after the mission file has loaded. Internally it places all Paths into the server's "
"PathManager. From this point the Paths are ready for transmission to the clients.\n\n"
"@tsexample\n"
"// Inform the engine to load all Path information from the mission.\n"
"pathOnMissionLoadDone();\n\n"
"@endtsexample\n"
"@see NetConnection::transmitPaths()\n"
"@see NetConnection::clearPaths()\n"
"@see Path\n"
"@ingroup Networking")
{
// Need to load subobjects for all loaded interiors...
SimGroup* pMissionGroup = dynamic_cast<SimGroup*>(Sim::findObject("MissionGroup"));
AssertFatal(pMissionGroup != NULL, "Error, mission done loading and no mission group?");
U32 currStart = 0;
U32 currEnd = 1;
Vector<SimGroup*> groups;
groups.push_back(pMissionGroup);
while (true) {
for (U32 i = currStart; i < currEnd; i++) {
for (SimGroup::iterator itr = groups[i]->begin(); itr != groups[i]->end(); itr++) {
if (dynamic_cast<SimGroup*>(*itr) != NULL)
groups.push_back(static_cast<SimGroup*>(*itr));
}
}
if (groups.size() == currEnd) {
break;
} else {
currStart = currEnd;
currEnd = groups.size();
}
}
for (U32 i = 0; i < groups.size(); i++) {
SimPath::Path* pPath = dynamic_cast<SimPath::Path*>(groups[i]);
if (pPath)
pPath->updatePath();
}
}
S32 FN_CDECL cmpPathObject(const void* p1, const void* p2)
{
SimObject* o1 = *((SimObject**)p1);
SimObject* o2 = *((SimObject**)p2);
Marker* m1 = dynamic_cast<Marker*>(o1);
Marker* m2 = dynamic_cast<Marker*>(o2);
if (m1 == NULL && m2 == NULL)
return 0;
else if (m1 != NULL && m2 == NULL)
return 1;
else if (m1 == NULL && m2 != NULL)
return -1;
else {
// Both markers...
return S32(m1->mSeqNum) - S32(m2->mSeqNum);
}
}
ConsoleDocClass(SimPath::Path,
"@brief A spline along which various objects can move along. The spline object acts like a container for Marker objects, which make\n"
"up the joints, or knots, along the path. Paths can be assigned a speed, can be looping or non-looping. Each of a path's markers can be\n"
"one of three primary movement types: \"normal\", \"Position Only\", or \"Kink\". \n"
"@tsexample\n"
"new path()\n"
" {\n"
" isLooping = \"1\";\n"
"\n"
" new Marker()\n"
" {\n"
" seqNum = \"0\";\n"
" type = \"Normal\";\n"
" msToNext = \"1000\";\n"
" smoothingType = \"Spline\";\n"
" position = \"-0.054708 -35.0612 234.802\";\n"
" rotation = \"1 0 0 0\";\n"
" };\n"
"\n"
" };\n"
"@endtsexample\n"
"@see Marker\n"
"@see NetConnection::transmitPaths()\n"
"@see NetConnection::clearPaths()\n"
"@see Path\n"
"@ingroup enviroMisc\n"
);
namespace SimPath
{
//--------------------------------------------------------------------------
//-------------------------------------- Implementation
//
IMPLEMENT_CONOBJECT(Path);
Path::Path()
{
mPathIndex = NoPathIndex;
mIsLooping = true;
}
Path::~Path()
{
//
}
//--------------------------------------------------------------------------
void Path::initPersistFields()
{
addField("isLooping", TypeBool, Offset(mIsLooping, Path), "If this is true, the loop is closed, otherwise it is open.\n");
Parent::initPersistFields();
//
}
//--------------------------------------------------------------------------
bool Path::onAdd()
{
if(!Parent::onAdd())
return false;
return true;
}
void Path::onRemove()
{
//
Parent::onRemove();
}
//--------------------------------------------------------------------------
/// Sort the markers objects into sequence order
void Path::sortMarkers()
{
dQsort(objectList.address(), objectList.size(), sizeof(SimObject*), cmpPathObject);
}
void Path::updatePath()
{
// If we need to, allocate a path index from the manager
if (mPathIndex == NoPathIndex)
mPathIndex = gServerPathManager->allocatePathId();
sortMarkers();
Vector<Point3F> positions;
Vector<QuatF> rotations;
Vector<U32> times;
Vector<U32> smoothingTypes;
for (iterator itr = begin(); itr != end(); itr++)
{
Marker* pMarker = dynamic_cast<Marker*>(*itr);
if (pMarker != NULL)
{
Point3F pos;
pMarker->getTransform().getColumn(3, &pos);
positions.push_back(pos);
QuatF rot;
rot.set(pMarker->getTransform());
rotations.push_back(rot);
times.push_back(pMarker->mMSToNext);
smoothingTypes.push_back(pMarker->mSmoothingType);
}
}
// DMMTODO: Looping paths.
gServerPathManager->updatePath(mPathIndex, positions, rotations, times, smoothingTypes);
}
void Path::addObject(SimObject* obj)
{
Parent::addObject(obj);
if (mPathIndex != NoPathIndex) {
// If we're already finished, and this object is a marker, then we need to
// update our path information...
if (dynamic_cast<Marker*>(obj) != NULL)
updatePath();
}
}
void Path::removeObject(SimObject* obj)
{
bool recalc = dynamic_cast<Marker*>(obj) != NULL;
Parent::removeObject(obj);
if (mPathIndex != NoPathIndex && recalc == true)
updatePath();
}
DefineEngineMethod( Path, getPathId, S32, (),,
"@brief Returns the PathID (not the object ID) of this path.\n\n"
"@return PathID (not the object ID) of this path.\n"
"@tsexample\n"
"// Acquire the PathID of this path object.\n"
"%pathID = %thisPath.getPathId();\n\n"
"@endtsexample\n\n"
)
{
Path *path = static_cast<Path *>(object);
return path->getPathIndex();
}
} // Namespace
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
GFXStateBlockRef Marker::smStateBlock;
GFXVertexBufferHandle<GFXVertexPC> Marker::smVertexBuffer;
GFXPrimitiveBufferHandle Marker::smPrimitiveBuffer;
static Point3F wedgePoints[4] = {
Point3F(-1, -1, 0),
Point3F( 0, 1, 0),
Point3F( 1, -1, 0),
Point3F( 0,-.75, .5),
};
void Marker::initGFXResources()
{
if(smVertexBuffer != NULL)
return;
GFXStateBlockDesc d;
d.cullDefined = true;
d.cullMode = GFXCullNone;
smStateBlock = GFX->createStateBlock(d);
smVertexBuffer.set(GFX, 4, GFXBufferTypeStatic);
GFXVertexPC* verts = smVertexBuffer.lock();
verts[0].point = wedgePoints[0] * 0.25f;
verts[1].point = wedgePoints[1] * 0.25f;
verts[2].point = wedgePoints[2] * 0.25f;
verts[3].point = wedgePoints[3] * 0.25f;
verts[0].color = verts[1].color = verts[2].color = verts[3].color = GFXVertexColor(ColorI(0, 255, 0, 255));
smVertexBuffer.unlock();
smPrimitiveBuffer.set(GFX, 24, 12, GFXBufferTypeStatic);
U16* prims;
smPrimitiveBuffer.lock(&prims);
prims[0] = 0;
prims[1] = 3;
prims[2] = 3;
prims[3] = 1;
prims[4] = 1;
prims[5] = 0;
prims[6] = 3;
prims[7] = 1;
prims[8] = 1;
prims[9] = 2;
prims[10] = 2;
prims[11] = 3;
prims[12] = 0;
prims[13] = 3;
prims[14] = 3;
prims[15] = 2;
prims[16] = 2;
prims[17] = 0;
prims[18] = 0;
prims[19] = 2;
prims[20] = 2;
prims[21] = 1;
prims[22] = 1;
prims[23] = 0;
smPrimitiveBuffer.unlock();
}
IMPLEMENT_CO_NETOBJECT_V1(Marker);
ConsoleDocClass( Marker,
"@brief A single joint, or knot, along a path. Should be stored inside a Path container object. A path markers can be\n"
"one of three primary movement types: \"normal\", \"Position Only\", or \"Kink\". \n"
"@tsexample\n"
"new path()\n"
" {\n"
" isLooping = \"1\";\n"
"\n"
" new Marker()\n"
" {\n"
" seqNum = \"0\";\n"
" type = \"Normal\";\n"
" msToNext = \"1000\";\n"
" smoothingType = \"Spline\";\n"
" position = \"-0.054708 -35.0612 234.802\";\n"
" rotation = \"1 0 0 0\";\n"
" };\n"
"\n"
" };\n"
"@endtsexample\n"
"@see Path\n"
"@ingroup enviroMisc\n"
);
Marker::Marker()
{
// Not ghostable unless we're editing...
mNetFlags.clear(Ghostable);
mTypeMask |= MarkerObjectType;
mSeqNum = 0;
mSmoothingType = SmoothingTypeLinear;
mMSToNext = 1000;
mSmoothingType = SmoothingTypeSpline;
mKnotType = KnotTypeNormal;
}
Marker::~Marker()
{
//
}
//--------------------------------------------------------------------------
ImplementEnumType( MarkerSmoothingType,
"The type of smoothing this marker will have for pathed objects.\n"
"@ingroup enviroMisc\n\n")
{ Marker::SmoothingTypeSpline , "Spline", "Marker will cause the movements of the pathed object to be smooth.\n" },
{ Marker::SmoothingTypeLinear , "Linear", "Marker will have no smoothing effect.\n" },
//{ Marker::SmoothingTypeAccelerate , "Accelerate" },
EndImplementEnumType;
ImplementEnumType( MarkerKnotType,
"The type of knot that this marker will be.\n"
"@ingroup enviroMisc\n\n")
{ Marker::KnotTypeNormal , "Normal", "Knot will have a smooth camera translation/rotation effect.\n" },
{ Marker::KnotTypePositionOnly, "Position Only", "Will do the same for translations, leaving rotation un-touched.\n" },
{ Marker::KnotTypeKink, "Kink", "The rotation will take effect immediately for an abrupt rotation change.\n" },
EndImplementEnumType;
void Marker::initPersistFields()
{
addGroup( "Misc" );
addField("seqNum", TypeS32, Offset(mSeqNum, Marker), "Marker position in sequence of markers on this path.\n");
addField("type", TYPEID< KnotType >(), Offset(mKnotType, Marker), "Type of this marker/knot. A \"normal\" knot will have a smooth camera translation/rotation effect.\n\"Position Only\"<EFBFBD>will do the same for translations, leaving rotation un-touched.\nLastly, a \"Kink\" means the rotation will take effect immediately for an abrupt rotation change.\n");
addField("msToNext", TypeS32, Offset(mMSToNext, Marker), "Milliseconds to next marker in sequence.\n");
addField("smoothingType", TYPEID< SmoothingType >(), Offset(mSmoothingType, Marker), "Path smoothing at this marker/knot. \"Linear\"<EFBFBD>means no smoothing, while \"Spline\" means to smooth.\n");
endGroup("Misc");
Parent::initPersistFields();
}
//--------------------------------------------------------------------------
bool Marker::onAdd()
{
if(!Parent::onAdd())
return false;
mObjBox = Box3F(Point3F(-.25, -.25, -.25), Point3F(.25, .25, .25));
resetWorldBox();
if(gEditingMission)
onEditorEnable();
return true;
}
void Marker::onRemove()
{
if(gEditingMission)
onEditorDisable();
Parent::onRemove();
smVertexBuffer = NULL;
smPrimitiveBuffer = NULL;
}
void Marker::onGroupAdd()
{
mSeqNum = getGroup()->size() - 1;
}
/// Enable scoping so we can see this thing on the client.
void Marker::onEditorEnable()
{
mNetFlags.set(Ghostable);
setScopeAlways();
addToScene();
}
/// Disable scoping so we can see this thing on the client
void Marker::onEditorDisable()
{
removeFromScene();
mNetFlags.clear(Ghostable);
clearScopeAlways();
}
/// Tell our parent that this Path has been modified
void Marker::inspectPostApply()
{
SimPath::Path *path = dynamic_cast<SimPath::Path*>(getGroup());
if (path)
path->updatePath();
}
//--------------------------------------------------------------------------
void Marker::prepRenderImage( SceneRenderState* state )
{
// This should be sufficient for most objects that don't manage zones, and
// don't need to return a specialized RenderImage...
ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
ri->renderDelegate.bind( this, &Marker::renderObject );
ri->type = RenderPassManager::RIT_Editor;
state->getRenderPass()->addInst(ri);
}
void Marker::renderObject(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance* overrideMat)
{
initGFXResources();
for(U32 i = 0; i < GFX->getNumSamplers(); i++)
GFX->setTexture(i, NULL);
GFXTransformSaver saver;
MatrixF mat = getRenderTransform();
mat.scale(mObjScale);
GFX->multWorld(mat);
GFX->setStateBlock(smStateBlock);
GFX->setVertexBuffer(smVertexBuffer);
GFX->setPrimitiveBuffer(smPrimitiveBuffer);
GFX->setupGenericShaders();
GFX->drawIndexedPrimitive(GFXLineList, 0, 0, 4, 0, 12);
}
//--------------------------------------------------------------------------
U32 Marker::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
{
U32 retMask = Parent::packUpdate(con, mask, stream);
// Note that we don't really care about efficiency here, since this is an
// edit-only ghost...
stream->writeAffineTransform(mObjToWorld);
return retMask;
}
void Marker::unpackUpdate(NetConnection* con, BitStream* stream)
{
Parent::unpackUpdate(con, stream);
// Transform
MatrixF otow;
stream->readAffineTransform(&otow);
setTransform(otow);
}

View file

@ -0,0 +1,157 @@
//-----------------------------------------------------------------------------
// 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 _SIMPATH_H_
#define _SIMPATH_H_
#ifndef _SCENEOBJECT_H_
#include "scene/sceneObject.h"
#endif
#ifndef _GFXSTATEBLOCK_H_
#include "gfx/gfxStateBlock.h"
#endif
#ifndef _GFXVERTEXBUFFER_H_
#include "gfx/gfxVertexBuffer.h"
#endif
#ifndef _GFXPRIMITIVEBUFFER_H_
#include "gfx/gfxPrimitiveBuffer.h"
#endif
class BaseMatInstance;
namespace SimPath
{
//--------------------------------------------------------------------------
/// A path!
class Path : public SimGroup
{
typedef SimGroup Parent;
public:
enum {
NoPathIndex = 0xFFFFFFFF
};
private:
U32 mPathIndex;
bool mIsLooping;
protected:
bool onAdd();
void onRemove();
public:
Path();
~Path();
void addObject(SimObject*);
void removeObject(SimObject*);
void sortMarkers();
void updatePath();
bool isLooping() { return mIsLooping; }
U32 getPathIndex() const;
DECLARE_CONOBJECT(Path);
static void initPersistFields();
};
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
inline U32 Path::getPathIndex() const
{
return mPathIndex;
}
} // Namespace
//--------------------------------------------------------------------------
class Marker : public SceneObject
{
typedef SceneObject Parent;
friend class Path;
public:
enum SmoothingType
{
SmoothingTypeLinear,
SmoothingTypeSpline,
SmoothingTypeAccelerate,
};
enum KnotType
{
KnotTypeNormal,
KnotTypePositionOnly,
KnotTypeKink,
};
U32 mSeqNum;
U32 mSmoothingType;
U32 mKnotType;
U32 mMSToNext;
// Rendering
protected:
void prepRenderImage(SceneRenderState *state);
void renderObject(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance* overrideMat);
protected:
bool onAdd();
void onRemove();
void onGroupAdd();
void onEditorEnable();
void onEditorDisable();
static void initGFXResources();
static GFXStateBlockRef smStateBlock;
static GFXVertexBufferHandle<GFXVertexPC> smVertexBuffer;
static GFXPrimitiveBufferHandle smPrimitiveBuffer;
public:
Marker();
~Marker();
DECLARE_CONOBJECT(Marker);
static void initPersistFields();
void inspectPostApply();
U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream);
void unpackUpdate(NetConnection *conn, BitStream *stream);
};
typedef Marker::SmoothingType MarkerSmoothingType;
typedef Marker::KnotType MarkerKnotType;
DefineEnumType( MarkerSmoothingType );
DefineEnumType( MarkerKnotType );
#endif // _H_PATH

View file

@ -0,0 +1,102 @@
//-----------------------------------------------------------------------------
// 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/zones/scenePolyhedralZone.h"
#include "scene/mixin/scenePolyhedralObject.impl.h"
//-----------------------------------------------------------------------------
ScenePolyhedralZone::ScenePolyhedralZone( const PolyhedronType& polyhedron )
: Parent( polyhedron )
{
}
//-----------------------------------------------------------------------------
bool ScenePolyhedralZone::onAdd()
{
if( !Parent::onAdd() )
return false;
// Precompute polyhedron/AABB intersection data, if not
// a trivial box polyhedron.
if( !mIsBox )
{
_updateIntersector();
// Also need to update OBB.
_updateOrientedWorldBox();
}
return true;
}
//-----------------------------------------------------------------------------
void ScenePolyhedralZone::setTransform( const MatrixF& mat )
{
Parent::setTransform( mat );
// Recompute intersection data.
if( !mIsBox )
_updateIntersector();
}
//-----------------------------------------------------------------------------
void ScenePolyhedralZone::_updateOrientedWorldBox()
{
if( mIsBox )
Parent::_updateOrientedWorldBox();
else
mOrientedWorldBox.set( getTransform(), Point3F( mObjBox.len_x(), mObjBox.len_y(), mObjBox.len_z() ) );
}
//-----------------------------------------------------------------------------
bool ScenePolyhedralZone::getOverlappingZones( const Box3F& aabb, U32* outZones, U32& outNumZones )
{
// If a trivial box, let parent handle this.
if( mIsBox )
return Parent::getOverlappingZones( aabb, outZones, outNumZones );
// Otherwise, use the intersector.
OverlapTestResult overlap = mIntersector.test( aabb );
if( overlap == GeometryOutside )
{
outNumZones = 0;
return true;
}
outZones[ 0 ] = getZoneRangeStart();
outNumZones = 1;
return ( overlap != GeometryInside );
}

View file

@ -0,0 +1,81 @@
//-----------------------------------------------------------------------------
// 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 _SCENEPOLYHEDRALZONE_H_
#define _SCENEPOLYHEDRALZONE_H_
#ifndef _SCENESIMPLEZONE_H_
#include "scene/zones/sceneSimpleZone.h"
#endif
#ifndef _SCENEPOLYHEDRALOBJECT_H_
#include "scene/mixin/scenePolyhedralObject.h"
#endif
#ifndef _MINTERSECTOR_H_
#include "math/mIntersector.h"
#endif
/// A simple zone space that is described by a polyhedron.
///
/// By default, if no other polyhedron is assigned to a polyhedral zone, the
/// polyhedron is initialized from the zone's object box.
class ScenePolyhedralZone : public ScenePolyhedralObject< SceneSimpleZone >
{
public:
typedef ScenePolyhedralObject< SceneSimpleZone > Parent;
protected:
typedef PolyhedronBoxIntersector< PolyhedronType > IntersectorType;
/// Fast polyhedron/AABB intersector used for testing SceneObject AABBs
/// for overlap with the zone.
IntersectorType mIntersector;
/// Precompute polyhedron/AABB intersection data.
void _updateIntersector()
{
mIntersector = IntersectorType( getPolyhedron(), getTransform(), getScale(), getWorldBox() );
}
// SceneSimpleZone.
virtual void _updateOrientedWorldBox();
public:
ScenePolyhedralZone() {}
ScenePolyhedralZone( const PolyhedronType& polyhedron );
// SimObject.
virtual bool onAdd();
// SceneObject.
virtual void setTransform( const MatrixF& mat );
// SceneZoneSpace.
virtual bool getOverlappingZones( const Box3F& aabb, U32* outZones, U32& outNumZones );
};
#endif // !_SCENEPOLYHEDRALZONE_H_

View file

@ -0,0 +1,121 @@
//-----------------------------------------------------------------------------
// 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/zones/sceneRootZone.h"
#include "scene/sceneManager.h"
#include "scene/sceneRenderState.h"
//RD: for the SceneRootZone, it may be worthwhile to put an optimized path in place that
// does some spatially aware testing of portals rather than just blindly going through
// the list of connected zone managers
//-----------------------------------------------------------------------------
SceneRootZone::SceneRootZone()
{
setGlobalBounds();
resetWorldBox();
// Clear netflags we have inherited. We don't
// network scene roots.
mNetFlags.clear( Ghostable | ScopeAlways );
// Clear type flags we have inherited.
mTypeMask &= ~StaticObjectType;
}
//-----------------------------------------------------------------------------
String SceneRootZone::describeSelf() const
{
String str = Parent::describeSelf();
str += "|SceneRootZone";
return str;
}
//-----------------------------------------------------------------------------
bool SceneRootZone::onSceneAdd()
{
if( !Parent::onSceneAdd() )
return false;
AssertFatal( getZoneRangeStart() == SceneZoneSpaceManager::RootZoneId, "SceneRootZone::onSceneAdd - SceneRootZone must be first scene object zone manager!" );
return true;
}
//-----------------------------------------------------------------------------
void SceneRootZone::onSceneRemove()
{
AssertFatal( getZoneRangeStart() == SceneZoneSpaceManager::RootZoneId, "SceneRootZone::onSceneRemove - SceneRootZone must be first scene object zone manager!");
Parent::onSceneRemove();
}
//-----------------------------------------------------------------------------
bool SceneRootZone::onAdd()
{
AssertFatal( false, "SceneRootZone::onAdd - Must not register SceneRootZone!" );
return false;
}
//-----------------------------------------------------------------------------
void SceneRootZone::onRemove()
{
AssertFatal( false, "SceneRootZone::onRemove - Must not be called!" );
}
//-----------------------------------------------------------------------------
U32 SceneRootZone::getPointZone( const Point3F &p ) const
{
return 0;
}
//-----------------------------------------------------------------------------
bool SceneRootZone::getOverlappingZones( const Box3F& aabb, U32* outZones, U32& outNumZones )
{
// Everything overlaps the outside zone.
outZones[ 0 ] = SceneZoneSpaceManager::RootZoneId;
outNumZones = 1;
return false;
}
//-----------------------------------------------------------------------------
bool SceneRootZone::containsPoint( const Point3F &point ) const
{
return true;
}

View file

@ -0,0 +1,69 @@
//-----------------------------------------------------------------------------
// 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 _SCENEROOTZONE_H_
#define _SCENEROOTZONE_H_
#ifndef _SCENESIMPLEZONE_H_
#include "scene/zones/sceneSimpleZone.h"
#endif
/// Root zone in a scene.
///
/// The root zone is an infinite zone that contains all others zones and
/// objects in the scene.
///
/// This class is not declared as a ConsoleObject and is not visible
/// from TorqueScript. SceneRootZone objects are not registered with the
/// Sim system.
class SceneRootZone : public SceneSimpleZone
{
public:
typedef SceneSimpleZone Parent;
protected:
// SceneObject.
virtual bool onSceneAdd();
virtual void onSceneRemove();
public:
SceneRootZone();
// SimObject.
virtual bool onAdd();
virtual void onRemove();
virtual String describeSelf() const;
// SceneObject.
virtual bool containsPoint( const Point3F &point ) const;
// SceneZoneSpace.
virtual U32 getPointZone( const Point3F &p ) const;
virtual bool getOverlappingZones( const Box3F& aabb, U32* outZones, U32& outNumZones );
};
#endif //_SCENEROOTZONE_H_

View file

@ -0,0 +1,361 @@
//-----------------------------------------------------------------------------
// 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/zones/sceneSimpleZone.h"
#include "scene/sceneRenderState.h"
#include "scene/sceneManager.h"
#include "scene/zones/sceneTraversalState.h"
#include "scene/culling/sceneCullingVolume.h"
#include "core/stream/bitStream.h"
#include "console/engineAPI.h"
#include "platform/profiler.h"
extern bool gEditingMission;
//-----------------------------------------------------------------------------
SceneSimpleZone::SceneSimpleZone()
: mUseAmbientLightColor( false ),
mAmbientLightColor( 0.1f, 0.1f, 0.1f, 1.f ),
mIsRotated( false )
{
// Box zones are unit cubes that are scaled to fit.
mObjScale.set( 10, 10, 10 );
mObjBox.set(
Point3F( -0.5f, -0.5f, -0.5f ),
Point3F( 0.5f, 0.5f, 0.5f )
);
}
//-----------------------------------------------------------------------------
void SceneSimpleZone::initPersistFields()
{
addGroup( "Lighting" );
addProtectedField( "useAmbientLightColor", TypeBool, Offset( mUseAmbientLightColor, SceneSimpleZone ),
&_setUseAmbientLightColor, &defaultProtectedGetFn,
"Whether to use #ambientLightColor for ambient lighting in this zone or the global ambient color." );
addProtectedField( "ambientLightColor", TypeColorF, Offset( mAmbientLightColor, SceneSimpleZone ),
&_setAmbientLightColor, &defaultProtectedGetFn,
"Color of ambient lighting in this zone.\n\n"
"Only used if #useAmbientLightColor is true." );
endGroup( "Lighting" );
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
String SceneSimpleZone::describeSelf() const
{
String str = Parent::describeSelf();
str += String::ToString( "|zoneid: %i", getZoneRangeStart() );
return str;
}
//-----------------------------------------------------------------------------
bool SceneSimpleZone::onSceneAdd()
{
if( !Parent::onSceneAdd() )
return false;
// Register our zone.
SceneZoneSpaceManager* manager = getSceneManager()->getZoneManager();
if( manager )
manager->registerZones( this, 1 );
return true;
}
//-----------------------------------------------------------------------------
U32 SceneSimpleZone::packUpdate( NetConnection* connection, U32 mask, BitStream* stream )
{
U32 retMask = Parent::packUpdate( connection, mask, stream );
if( stream->writeFlag( mask & AmbientMask ) )
{
stream->writeFlag( mUseAmbientLightColor );
stream->writeFloat( mAmbientLightColor.red, 7 );
stream->writeFloat( mAmbientLightColor.green, 7 );
stream->writeFloat( mAmbientLightColor.blue, 7 );
stream->writeFloat( mAmbientLightColor.alpha, 7 );
}
return retMask;
}
//-----------------------------------------------------------------------------
void SceneSimpleZone::unpackUpdate( NetConnection* connection, BitStream* stream )
{
Parent::unpackUpdate( connection, stream );
if( stream->readFlag() ) // AmbientMask
{
mUseAmbientLightColor = stream->readFlag();
mAmbientLightColor.red = stream->readFloat( 7 );
mAmbientLightColor.green = stream->readFloat( 7 );
mAmbientLightColor.blue = stream->readFloat( 7 );
mAmbientLightColor.alpha = stream->readFloat( 7 );
}
}
//-----------------------------------------------------------------------------
void SceneSimpleZone::setUseAmbientLightColor( bool value )
{
if( mUseAmbientLightColor == value )
return;
mUseAmbientLightColor = value;
if( isServerObject() )
setMaskBits( AmbientMask );
}
//-----------------------------------------------------------------------------
void SceneSimpleZone::setAmbientLightColor( const ColorF& color )
{
mAmbientLightColor = color;
if( isServerObject() )
setMaskBits( AmbientMask );
}
//-----------------------------------------------------------------------------
bool SceneSimpleZone::getZoneAmbientLightColor( U32 zone, ColorF& outColor ) const
{
AssertFatal( zone == getZoneRangeStart(), "SceneSimpleZone::getZoneAmbientLightColor - Invalid zone ID!" );
if( !mUseAmbientLightColor )
return false;
outColor = mAmbientLightColor;
return true;
}
//-----------------------------------------------------------------------------
U32 SceneSimpleZone::getPointZone( const Point3F& p )
{
if( !containsPoint( p ) )
return SceneZoneSpaceManager::InvalidZoneId;
return getZoneRangeStart();
}
//-----------------------------------------------------------------------------
bool SceneSimpleZone::getOverlappingZones( const Box3F& aabb, U32* outZones, U32& outNumZones )
{
PROFILE_SCOPE( SceneBoxZone_getOverlappingZones );
bool isOverlapped = false;
bool isContained = false;
// If the zone has not been rotated, we can simply use straightforward
// AABB/AABB intersection based on the world boxes of the zone and the
// object.
//
// If, however, the zone has been rotated, then we must use the zone's
// OBB and test that against the object's AABB.
if( !mIsRotated )
{
isOverlapped = mWorldBox.isOverlapped( aabb );
isContained = isOverlapped && mWorldBox.isContained( aabb );
}
else
{
// Check if the zone's OBB intersects the object's AABB.
isOverlapped = aabb.collideOrientedBox(
getScale() / 2.f,
getTransform()
);
// If so, check whether the object's AABB is fully contained
// inside the zone's OBB.
if( isOverlapped )
{
isContained = true;
for( U32 i = 0; i < Box3F::NUM_POINTS; ++ i )
{
Point3F cornerPoint = aabb.computeVertex( i );
if( !mOrientedWorldBox.isContained( cornerPoint ) )
{
isContained = false;
break;
}
}
}
}
if( isOverlapped )
{
outNumZones = 1;
outZones[ 0 ] = getZoneRangeStart();
}
else
outNumZones = 0;
return !isContained;
}
//-----------------------------------------------------------------------------
void SceneSimpleZone::setTransform( const MatrixF& mat )
{
Parent::setTransform( mat );
// Find out whether the zone has been rotated.
EulerF rotation = getTransform().toEuler();
mIsRotated = !mIsZero( rotation.x ) ||
!mIsZero( rotation.y ) ||
!mIsZero( rotation.z );
// Update the OBB.
_updateOrientedWorldBox();
}
//-----------------------------------------------------------------------------
void SceneSimpleZone::prepRenderImage( SceneRenderState* state )
{
if( isRootZone() )
return;
Parent::prepRenderImage( state );
}
//-----------------------------------------------------------------------------
void SceneSimpleZone::traverseZones( SceneTraversalState* state )
{
traverseZones( state, getZoneRangeStart() );
}
//-----------------------------------------------------------------------------
void SceneSimpleZone::traverseZones( SceneTraversalState* state, U32 startZoneId )
{
PROFILE_SCOPE( SceneSimpleZone_traverseZones );
AssertFatal( startZoneId == getZoneRangeStart(), "SceneSimpleZone::traverseZones - Invalid start zone ID!" );
// If we aren't the root of the traversal, do a number of checks
// to see if we can early out of the traversal here. The primary reason
// we don't do the checks if we are the root is because of the frustum
// near plane. The start zone of a traversal is selected based on the
// current viewpoint. However, that point still has some space in between
// it and the near plane so if we end up with a case where that's all the
// space that is needed to cull away our starting zone, we won't see any
// traversal at all and get a blank scene back even if the starting zone
// would actually hand the traversal over to other zones and eventually
// discover visible space.
if( state->getTraversalDepth() > 0 )
{
// If we have already visited this zone in this traversal chain,
// exit out. This can happen when zones are grouped together.
// Note that this can also happen with the outdoor zone since it isn't
// convex. However, in that case, this acts as a nice side optimization
// we get since if the outdoor zone is already on the stack, our culling
// culling volume can only be smaller than the one we started with and thus
// by earlying out here, we prevent adding a pointless culling volume
// to the zone.
//TODO: would be nice to catch this via "visibility changed?" checks but
// that's non-trivial
if( state->haveVisitedZone( getZoneRangeStart() ) )
return;
// First check whether we even intersect the given frustum at all.
if( mIsRotated )
{
// Space has been rotated, so do a frustum/OBB check.
if( !state->getCurrentCullingVolume().test( _getOrientedWorldBox() ) )
return;
}
else
{
// Space has not been rotated, so we can do a faster frustum/ABB check.
if( !state->getCurrentCullingVolume().test( getWorldBox() ) )
return;
}
}
// Add the current culling volume to the culling state for this zone.
// If that doesn't result in new space becoming visible, we can terminate the traversal
// here.
if( !state->getCullingState()->addCullingVolumeToZone( startZoneId, state->getCurrentCullingVolume() ) )
return;
// Add our occluders to the rendering state.
_addOccludersToCullingState( state->getCullingState() );
// Merge the zone into the traversal area.
state->addToTraversedArea( getWorldBox() );
// Push our zone ID on the traversal stack and traverse into our
// connected zone managers.
state->pushZone( startZoneId );
_traverseConnectedZoneSpaces( state );
state->popZone();
}
//-----------------------------------------------------------------------------
bool SceneSimpleZone::_setUseAmbientLightColor( void* object, const char* index, const char* data )
{
SceneSimpleZone* zone = reinterpret_cast< SceneSimpleZone* >( object );
zone->setUseAmbientLightColor( EngineUnmarshallData< bool >()( data ) );
return false;
}
//-----------------------------------------------------------------------------
bool SceneSimpleZone::_setAmbientLightColor( void* object, const char* index, const char* data )
{
SceneSimpleZone* zone = reinterpret_cast< SceneSimpleZone* >( object );
zone->setAmbientLightColor( EngineUnmarshallData< ColorF >()( data ) );
return false;
}

View file

@ -0,0 +1,145 @@
//-----------------------------------------------------------------------------
// 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 _SCENESIMPLEZONE_H_
#define _SCENESIMPLEZONE_H_
#ifndef _SCENEZONESPACE_H_
#include "scene/zones/sceneZoneSpace.h"
#endif
#ifndef _MORIENTEDBOX_H_
#include "math/mOrientedBox.h"
#endif
class SceneRenderState;
class BaseMatInstance;
/// Abstract base class for a zone space that contains only a single zone.
///
/// Simple zones are required to be convex.
class SceneSimpleZone : public SceneZoneSpace
{
public:
typedef SceneZoneSpace Parent;
protected:
enum
{
AmbientMask = Parent::NextFreeMask << 0, ///< Ambient light color setting has changed.
NextFreeMask = Parent::NextFreeMask << 1,
};
/// @name Ambient Lighting
/// @{
/// If this is true, then the zone defines its own ambient
/// light color in #mAmbientColor.
bool mUseAmbientLightColor;
/// Ambient light color in this zone.
ColorF mAmbientLightColor;
/// @}
/// @name Transforms
/// @{
/// Whether the zone has been rotated. This is used to fasttrack overlap
/// tests to avoid doing an OBB/AABB intersection test when we can simply
/// use a much quicker AABB/AABB intersection test on the world boxes of
/// both objects.
bool mIsRotated;
/// The OBB for the zone.
OrientedBox3F mOrientedWorldBox;
/// @}
/// Return the oriented bounding box for the zone.
const OrientedBox3F& _getOrientedWorldBox() const { return mOrientedWorldBox; }
/// Update the OBB for the zone.
virtual void _updateOrientedWorldBox() { mOrientedWorldBox.set( getTransform(), getScale() ); }
// SceneObject.
virtual bool onSceneAdd();
public:
SceneSimpleZone();
/// @name Ambient Lighting
/// @{
/// Return true if the zone defines its own ambient light color.
bool useAmbientLightColor() const { return mUseAmbientLightColor; }
/// Set whether a custom ambient light color is active in this zone.
void setUseAmbientLightColor( bool value );
/// Return the ambient light color for this zone.
ColorF getAmbientLightColor() const { return mAmbientLightColor; }
/// Set the ambient light color for the zone.
/// @note This only takes effect if useAmbientLightColor() return true.
/// @see setUseAmbientLightColor
void setAmbientLightColor( const ColorF& color );
/// @}
/// @name Inherited
/// @{
// SimObject.
virtual String describeSelf() const;
static void initPersistFields();
// NetObject
virtual U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream );
virtual void unpackUpdate( NetConnection *conn, BitStream *stream );
// SceneObject
virtual void prepRenderImage( SceneRenderState* state );
virtual void setTransform( const MatrixF& mat );
// SceneZoneSpace.
virtual U32 getPointZone( const Point3F &p );
virtual bool getOverlappingZones( const Box3F& aabb, U32* outZones, U32& outNumZones );
virtual void traverseZones( SceneTraversalState* state );
virtual bool getZoneAmbientLightColor( U32 zone, ColorF& outColor ) const;
virtual void traverseZones( SceneTraversalState* state, U32 startZoneId );
/// @}
private:
static bool _setUseAmbientLightColor( void* object, const char* index, const char* data );
static bool _setAmbientLightColor( void* object, const char* index, const char* data );
};
#endif // !_SCENESIMPLEZONE_H_

View file

@ -0,0 +1,70 @@
//-----------------------------------------------------------------------------
// 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/zones/sceneTraversalState.h"
#include "scene/sceneManager.h"
#include "scene/culling/sceneCullingState.h"
#include "scene/culling/sceneCullingVolume.h"
#include "scene/zones/sceneZoneSpaceManager.h"
//-----------------------------------------------------------------------------
SceneTraversalState::SceneTraversalState( SceneCullingState* cullingState )
: mCullingState( cullingState ),
mTraversedArea( 0.f )
{
VECTOR_SET_ASSOCIATION( mZoneStack );
VECTOR_SET_ASSOCIATION( mCullingVolumeStack );
// Push the polyhedron of the root frustum onto the traversal
// stack as the current culling volume.
pushCullingVolume( cullingState->getRootVolume() );
}
//-----------------------------------------------------------------------------
SceneZoneSpace* SceneTraversalState::getZoneFromStack( U32 depth )
{
SceneZoneSpaceManager* zoneManager = getCullingState()->getSceneManager()->getZoneManager();
U32 zoneId = getZoneIdFromStack( depth );
return zoneManager->getZoneOwner( zoneId );
}
//-----------------------------------------------------------------------------
void SceneTraversalState::pushCullingVolume( const SceneCullingVolume& volume )
{
mCullingVolumeStack.push_back( volume );
}
//-----------------------------------------------------------------------------
void SceneTraversalState::popCullingVolume()
{
AssertFatal( mCullingVolumeStack.size() > 1, "SceneTraversalState::popCullingVolume - Must not pop root volume" );
mCullingVolumeStack.pop_back();
}

View file

@ -0,0 +1,119 @@
//-----------------------------------------------------------------------------
// 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 _SCENETRAVERSALSTATE_H_
#define _SCENETRAVERSALSTATE_H_
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _MBOX_H_
#include "math/mBox.h"
#endif
#ifndef _MPLANESET_H_
#include "math/mPlaneSet.h"
#endif
class SceneCullingState;
class SceneCullingVolume;
class SceneZoneSpace;
/// Temporary state for zone traversals. Keeps track of the zones
/// that were visited in the current traversal chain as well as of
/// the total area that so far has been visited in the traversal.
class SceneTraversalState
{
protected:
/// Scene culling state.
SceneCullingState* mCullingState;
/// Stack of zones visited in current traversal chain.
Vector< U32 > mZoneStack;
/// Stack of culling volumes. Topmost is current volume.
Vector< SceneCullingVolume > mCullingVolumeStack;
/// Area of scene visited in traversal.
Box3F mTraversedArea;
public:
/// Construct an empty scene traversal state.
/// @param cullingState Scene culling state.
SceneTraversalState( SceneCullingState* cullingState );
/// Return the scene culling state for this traversal.
SceneCullingState* getCullingState() const { return mCullingState; }
/// Get the scene area that has been visited so far in the traversal.
const Box3F& getTraversedArea() const { return mTraversedArea; }
/// Add an area to what has been visited so far in the traversal.
void addToTraversedArea( const Box3F& area ) { mTraversedArea.intersect( area ); }
/// @name Zone Stack
/// @{
/// Return the number of zones that are currently on the traversal stack.
U32 getTraversalDepth() const { return mZoneStack.size(); }
/// Push a zone onto the traversal stack.
void pushZone( U32 zoneId ) { mZoneStack.push_back( zoneId ); }
/// Pop the topmost zone from the traversal stack.
void popZone() { mZoneStack.pop_back(); }
/// Return true if the given zone has already been visited in the
/// current traversal chain, i.e. if it is currently on the traversal
/// stack.
bool haveVisitedZone( U32 zoneId ) const { return mZoneStack.contains( zoneId ); }
/// Return the zone ID of the topmost zone on the traversal stack.
U32 getZoneIdFromStack( U32 depth = 0 ) { return mZoneStack[ mZoneStack.size() - 1 - depth ]; }
/// Return the zone space that owns the topmost zone on the traversal stack.
SceneZoneSpace* getZoneFromStack( U32 depth = 0 );
/// @}
/// @name Culling Volume Stack
/// @{
/// Push a culling volume onto the stack so that it becomes the current culling
/// volume.
void pushCullingVolume( const SceneCullingVolume& volume );
/// Pop the current culling volume from the stack.
void popCullingVolume();
///
const SceneCullingVolume& getCurrentCullingVolume() const { return mCullingVolumeStack.last(); }
/// @}
};
#endif // !_SCENETRAVERSALSTATE_H_

View file

@ -0,0 +1,362 @@
//-----------------------------------------------------------------------------
// 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/zones/sceneZoneSpace.h"
#include "scene/zones/sceneTraversalState.h"
#include "scene/zones/sceneZoneSpaceManager.h"
#include "scene/sceneRenderState.h"
#include "sim/netConnection.h"
#include "core/stream/bitStream.h"
#include "console/engineAPI.h"
//#define DEBUG_SPEW
ClassChunker< SceneZoneSpace::ZoneSpaceRef > SceneZoneSpace::smZoneSpaceRefChunker;
//-----------------------------------------------------------------------------
SceneZoneSpace::SceneZoneSpace()
: mManager( NULL ),
mZoneGroup( InvalidZoneGroup ),
mZoneRangeStart( SceneZoneSpaceManager::InvalidZoneId ),
mZoneFlags( ZoneFlag_IsClosedOffSpace ),
mNumZones( 0 ),
mConnectedZoneSpaces( NULL )
{
VECTOR_SET_ASSOCIATION( mOccluders );
}
//-----------------------------------------------------------------------------
SceneZoneSpace::~SceneZoneSpace()
{
AssertFatal( mConnectedZoneSpaces == NULL, "SceneZoneSpace::~SceneZoneSpace - Still have connected zone spaces!" );
}
//-----------------------------------------------------------------------------
void SceneZoneSpace::onSceneRemove()
{
_disconnectAllZoneSpaces();
Parent::onSceneRemove();
}
//-----------------------------------------------------------------------------
void SceneZoneSpace::initPersistFields()
{
addGroup( "Zoning" );
addProtectedField( "zoneGroup", TypeS32, Offset( mZoneGroup, SceneZoneSpace ),
&_setZoneGroup, &defaultProtectedGetFn,
"ID of group the zone is part of." );
endGroup( "Zoning" );
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
bool SceneZoneSpace::writeField( StringTableEntry fieldName, const char* value )
{
// Don't write zoneGroup field if at default.
static StringTableEntry sZoneGroup = StringTable->insert( "zoneGroup" );
if( fieldName == sZoneGroup && getZoneGroup() == InvalidZoneGroup )
return false;
return Parent::writeField( fieldName, value );
}
//-----------------------------------------------------------------------------
void SceneZoneSpace::setZoneGroup( U32 group )
{
if( mZoneGroup == group )
return;
mZoneGroup = group;
setMaskBits( ZoneGroupMask );
// Rezone to establish new connectivity.
if( mManager )
mManager->notifyObjectChanged( this );
}
//-----------------------------------------------------------------------------
U32 SceneZoneSpace::packUpdate( NetConnection* connection, U32 mask, BitStream* stream )
{
U32 retMask = Parent::packUpdate( connection, mask, stream );
if( stream->writeFlag( mask & ZoneGroupMask ) )
stream->write( mZoneGroup );
return retMask;
}
//-----------------------------------------------------------------------------
void SceneZoneSpace::unpackUpdate( NetConnection* connection, BitStream* stream )
{
Parent::unpackUpdate( connection, stream );
if( stream->readFlag() ) // ZoneGroupMask
{
U32 zoneGroup;
stream->read( &zoneGroup );
setZoneGroup( zoneGroup );
}
}
//-----------------------------------------------------------------------------
bool SceneZoneSpace::getOverlappingZones( SceneObject* obj, U32* outZones, U32& outNumZones )
{
return getOverlappingZones( obj->getWorldBox(), outZones, outNumZones );
}
//-----------------------------------------------------------------------------
void SceneZoneSpace::_onZoneAddObject( SceneObject* object, const U32* zoneIDs, U32 numZones )
{
if( object->isVisualOccluder() )
_addOccluder( object );
// If this isn't the root zone and the object is zone space,
// see if we should automatically connect the two.
if( !isRootZone() && object->getTypeMask() & ZoneObjectType )
{
SceneZoneSpace* zoneSpace = dynamic_cast< SceneZoneSpace* >( object );
// Don't connect a zone space that has the same closed off status
// that we have except it is assigned to the same zone group.
if( zoneSpace &&
( zoneSpace->mZoneFlags.test( ZoneFlag_IsClosedOffSpace ) != mZoneFlags.test( ZoneFlag_IsClosedOffSpace ) ||
( zoneSpace->getZoneGroup() == getZoneGroup() &&
zoneSpace->getZoneGroup() != InvalidZoneGroup ) ) &&
_automaticallyConnectZoneSpace( zoneSpace ) )
{
connectZoneSpace( zoneSpace );
}
}
}
//-----------------------------------------------------------------------------
void SceneZoneSpace::_onZoneRemoveObject( SceneObject* object )
{
if( object->isVisualOccluder() )
_removeOccluder( object );
if( !isRootZone() && object->getTypeMask() & ZoneObjectType )
{
SceneZoneSpace* zoneSpace = dynamic_cast< SceneZoneSpace* >( object );
if( zoneSpace )
disconnectZoneSpace( zoneSpace );
}
}
//-----------------------------------------------------------------------------
bool SceneZoneSpace::_automaticallyConnectZoneSpace( SceneZoneSpace* zoneSpace ) const
{
//TODO: This is suboptimal. While it prevents the most blatantly wrong automatic connections,
// we need a true polyhedron/polyhedron intersection to accurately determine zone intersection
// when it comes to automatic connections.
U32 numZones = 0;
U32 zones[ SceneObject::MaxObjectZones ];
zoneSpace->getOverlappingZones( getWorldBox(), zones, numZones );
return ( numZones > 0 );
}
//-----------------------------------------------------------------------------
void SceneZoneSpace::connectZoneSpace( SceneZoneSpace* zoneSpace )
{
// If the zone space is already in the list, do nothing.
for( ZoneSpaceRef* ref = mConnectedZoneSpaces; ref != NULL; ref = ref->mNext )
if( ref->mZoneSpace == zoneSpace )
return;
// Link the zone space to the zone space refs.
ZoneSpaceRef* ref = smZoneSpaceRefChunker.alloc();
ref->mZoneSpace = zoneSpace;
ref->mNext = mConnectedZoneSpaces;
mConnectedZoneSpaces = ref;
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SceneZoneSpace] Connecting %i-%i to %i-%i",
getZoneRangeStart(), getZoneRangeStart() + getZoneRange(),
zoneSpace->getZoneRangeStart(), zoneSpace->getZoneRangeStart() + zoneSpace->getZoneRange()
);
#endif
}
//-----------------------------------------------------------------------------
void SceneZoneSpace::disconnectZoneSpace( SceneZoneSpace* zoneSpace )
{
ZoneSpaceRef* prev = NULL;
for( ZoneSpaceRef* ref = mConnectedZoneSpaces; ref != NULL; prev = ref, ref = ref->mNext )
if( ref->mZoneSpace == zoneSpace )
{
if( prev )
prev->mNext = ref->mNext;
else
mConnectedZoneSpaces = ref->mNext;
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SceneZoneSpace] Disconnecting %i-%i from %i-%i",
getZoneRangeStart(), getZoneRangeStart() + getZoneRange(),
zoneSpace->getZoneRangeStart(), zoneSpace->getZoneRangeStart() + zoneSpace->getZoneRange()
);
#endif
smZoneSpaceRefChunker.free( ref );
break;
}
}
//-----------------------------------------------------------------------------
void SceneZoneSpace::_disconnectAllZoneSpaces()
{
#ifdef DEBUG_SPEW
if( mConnectedZoneSpaces != NULL )
Platform::outputDebugString( "[SceneZoneSpace] Disconnecting all from %i-%i",
getZoneRangeStart(), getZoneRangeStart() + getZoneRange()
);
#endif
for( ZoneSpaceRef* ref = mConnectedZoneSpaces; ref != NULL; )
{
ZoneSpaceRef* next = ref->mNext;
smZoneSpaceRefChunker.free( ref );
ref = next;
}
mConnectedZoneSpaces = NULL;
}
//-----------------------------------------------------------------------------
void SceneZoneSpace::_addOccluder( SceneObject* object )
{
AssertFatal( !mOccluders.contains( object ), "SceneZoneSpace::_addOccluder - Occluder already added to this zone space!" );
mOccluders.push_back( object );
}
//-----------------------------------------------------------------------------
void SceneZoneSpace::_removeOccluder( SceneObject* object )
{
const U32 numOccluders = mOccluders.size();
for( U32 i = 0; i < numOccluders; ++ i )
if( mOccluders[ i ] == object )
{
mOccluders.erase_fast( i );
break;
}
AssertFatal( !mOccluders.contains( object ), "SceneZoneSpace::_removeOccluder - Occluder still added to this zone space!" );
}
//-----------------------------------------------------------------------------
void SceneZoneSpace::_addOccludersToCullingState( SceneCullingState* state ) const
{
const U32 numOccluders = mOccluders.size();
for( U32 i = 0; i < numOccluders; ++ i )
state->addOccluder( mOccluders[ i ] );
}
//-----------------------------------------------------------------------------
void SceneZoneSpace::_traverseConnectedZoneSpaces( SceneTraversalState* state )
{
// Hand the traversal over to all connected zone spaces.
for( ZoneSpaceRef* ref = mConnectedZoneSpaces; ref != NULL; ref = ref->mNext )
{
SceneZoneSpace* zoneSpace = ref->mZoneSpace;
zoneSpace->traverseZones( state );
}
}
//-----------------------------------------------------------------------------
void SceneZoneSpace::dumpZoneState( bool update )
{
// Nothing to dump if not registered.
if( !mManager )
return;
// If we should update, trigger rezoning for the space
// we occupy.
if( update )
mManager->_rezoneObjects( getWorldBox() );
Con::printf( "====== Zones in: %s =====", describeSelf().c_str() );
// Dump connections.
for( ZoneSpaceRef* ref = mConnectedZoneSpaces; ref != NULL; ref = ref->mNext )
Con::printf( "Connected to: %s", ref->mZoneSpace->describeSelf().c_str() );
// Dump objects.
for( U32 i = 0; i < getZoneRange(); ++ i )
{
U32 zoneId = getZoneRangeStart() + i;
Con::printf( "--- Zone %i", zoneId );
for( SceneZoneSpaceManager::ZoneContentIterator iter( mManager, zoneId, false ); iter.isValid(); ++ iter )
Con::printf( iter->describeSelf() );
}
}
//-----------------------------------------------------------------------------
bool SceneZoneSpace::_setZoneGroup( void* object, const char* index, const char* data )
{
SceneZoneSpace* zone = reinterpret_cast< SceneZoneSpace* >( object );
zone->setZoneGroup( EngineUnmarshallData< S32 >()( data ) );
return false;
}

View file

@ -0,0 +1,298 @@
//-----------------------------------------------------------------------------
// 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 _SCENEZONESPACE_H_
#define _SCENEZONESPACE_H_
#ifndef _SCENESPACE_H_
#include "scene/sceneSpace.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
class SceneZoneSpaceManager;
class SceneCullingState;
/// Abstract base class for an object that manages zones in a scene.
///
/// This class adds the ability to SceneSpace to define and manage zones within the object's
/// space. Zones are used to determine visibility in a scene.
///
/// Each zone space manages one or more zones in a scene. All the zones must be within the
/// AABB of the zone space but within that AABB, the zone space is free to distribute and
/// manage zones in arbitrary fashion.
///
/// For scene traversal, zone spaces are interconnected. By default, zone spaces get a chance
/// to connect to each other when being moved into each other's zones. An exception to this
/// is the root zone since it is both immobile and without position and (limited) extents. If
/// a zone space wants to connect to the root zone, it must do so manually (same goes for
/// disconnecting).
class SceneZoneSpace : public SceneSpace
{
public:
typedef SceneSpace Parent;
friend class SceneZoneSpaceManager;
friend class SceneRootZone; // mZoneFlags, connectZoneSpace, disconnectZoneSpace
enum
{
InvalidZoneGroup = 0
};
enum ZoneFlags
{
/// Whether this zoning space is "closed off" or not. When connections between
/// zoning spaces are established, by default only spaces that are closed off are
/// connected to those that are *not* and vice versa. This defines a natural progression
/// where spaces that explicitly want to propagate traversals are connected to those
/// that want to contain it.
///
/// This flag is set by default.
ZoneFlag_IsClosedOffSpace = BIT( 0 ),
};
protected:
enum
{
ZoneGroupMask = Parent::NextFreeMask << 0,
NextFreeMask = Parent::NextFreeMask << 1
};
/// The manager to which this zone space is registered.
SceneZoneSpaceManager* mManager;
/// ID of first zone defined by object.
U32 mZoneRangeStart;
/// Number of zones managed by #obj. IDs are consecutive
/// starting with #mZoneRangeStart.
U32 mNumZones;
/// Group which the zone is assigned to. Zone spaces that would normally not connect
/// do connect if they are assigned the same zone group. O to disable (default).
U32 mZoneGroup;
///
BitSet32 mZoneFlags;
/// @name Occluders
///
/// Zone spaces keep track of the occluders that get added to them so that during
/// traversal, they can be taken on board as early as possible. This allows traversals
/// themselves to take occlusion into account.
///
/// @{
/// Occluders in this space. Every object that is assigned to this space
/// and has the OccluderObjectType flag set, is added to this list.
Vector< SceneObject* > mOccluders;
/// Add the given object to the list of occluders in this zone space.
void _addOccluder( SceneObject* object );
/// Remove the given object from the list of occluders in this zone space.
void _removeOccluder( SceneObject* object );
/// Register the occluders in this zone with the given culling state.
void _addOccludersToCullingState( SceneCullingState* state ) const;
/// @}
/// @name Zone Space Connectivity
/// @{
//TODO: we should have both automatic and manual connections; only automatic connections
// should get reset when a zone space moves
/// Link to another zone space.
struct ZoneSpaceRef
{
// We could be storing zone IDs here to connect individual zones rather than just
// the managers but since no one requires this at the moment, the code doesn't do it.
// However, it's trivial to add.
SceneZoneSpace* mZoneSpace;
ZoneSpaceRef* mNext;
};
/// List of zone spaces that this space is connected to. This is used
/// for traversals.
ZoneSpaceRef* mConnectedZoneSpaces;
/// Allocator for ZoneSpaceRefs.
static ClassChunker< ZoneSpaceRef > smZoneSpaceRefChunker;
/// Disconnect all zone spaces currently connected to this space.
virtual void _disconnectAllZoneSpaces();
/// Hand the traversal over to connected zone spaces.
virtual void _traverseConnectedZoneSpaces( SceneTraversalState* state );
/// Return true if the given zone space, which has crossed into this space, should
/// be automatically connected. Note that event
virtual bool _automaticallyConnectZoneSpace( SceneZoneSpace* zoneSpace ) const;
/// @}
/// @name SceneManager Notifications
///
/// These methods are called when SceneManager assigns or removes objects to/from our zones.
///
/// @{
/// Called by the SceneManager when an object is added to one or more zones that
/// are managed by this object.
virtual void _onZoneAddObject( SceneObject* object, const U32* zoneIDs, U32 numZones );
/// Called by the SceneManager when an object that was previously added to one or more
/// zones managed by this object is now removed.
virtual void _onZoneRemoveObject( SceneObject* object );
/// @}
// SceneObject.
virtual void onSceneRemove();
public:
SceneZoneSpace();
virtual ~SceneZoneSpace();
/// Return true if this is the outdoor zone.
bool isRootZone() const { return ( getZoneRangeStart() == 0 ); }
/// Gets the index of the first zone this object manages in the collection of zones or 0xFFFFFFFF if the
/// object is not managing zones.
U32 getZoneRangeStart() const { return mZoneRangeStart; }
/// Return the number of zones that are managed by this object.
U32 getZoneRange() const { return mNumZones; }
/// Return the zone group that this zone space belongs to. 0 by default which means the
/// zone space is not allocated to a specific zone group.
U32 getZoneGroup() const { return mZoneGroup; }
/// Set the zone group of this zone space. Zone spaces in the same group will connect even if
/// not connecting by default. Set to 0 to deactivate.
void setZoneGroup( U32 group );
/// Dump a listing of all objects assigned to this zone space to the console as well
/// as a list of all connected spaces.
///
/// @param update Whether to update the zone contents before dumping. Since the zoning states of
/// SceneObjects are updated lazily, the contents of a zone can be outdated.
void dumpZoneState( bool update = true );
/// Get the ambient light color of the given zone in this space or return false if the
/// given zone does not have an ambient light color assigned to it.
virtual bool getZoneAmbientLightColor( U32 zone, ColorF& outColor ) const { return false; }
/// @name Containment Tests
/// @{
///
virtual bool getOverlappingZones( const Box3F& aabb, U32* zones, U32& numZones ) = 0;
/// Find the zones in this object that @a obj is part of.
///
/// @param obj Object in question.
/// @param outZones Indices of zones containing the object. Must have at least as many entries
/// as there as zones in this object or SceneObject::MaxObjectZones, whichever is smaller.
/// Note that implementations should never write more than SceneObject::MaxObjectZones entries.
/// @param outNumZones Number of elements in the returned array.
///
/// @return Return true if the world box of @a obj is fully contained within the zones of this object or
/// false if it is at least partially outside of them.
virtual bool getOverlappingZones( SceneObject* obj, U32* outZones, U32& outNumZones );
/// Returns the ID of the zone that are managed by this object that contains @a p.
/// @param p Point to test.
/// @return ID of the zone containing @a p or InvalidZoneId if none of the zones defined by this
/// object contain the point.
virtual U32 getPointZone( const Point3F& p ) = 0;
/// @}
/// @name Connectivity
/// @{
/// Connect this zone space to the given zone space.
///
/// @param zoneSpace A zone space.
///
/// @note Connectivity is reset when a zone space is moved!
virtual void connectZoneSpace( SceneZoneSpace* zoneSpace );
/// If the object is a zone space, then this method is called to instruct the object
/// to remove any zone connectivity to @a zoneSpace.
///
/// @param zoneSpace A zone space which had previously been passed to connectZoneSpace().
virtual void disconnectZoneSpace( SceneZoneSpace* zoneSpace );
/// @}
/// @name Traversals
/// @{
/// Traverse into the zones of this space. Set the render states of those zones and add the frustums
/// that lead into them.
///
/// The traversal stack is expected to not be empty and the topmost entry on the stack should be the
/// zone of another manager from which traversal is handed over to this manager.
///
/// @param state Scene traversal state.
///
/// @note If the zone's of a zone space are reached via different traversal paths, this method
/// will be called multiple times on the same space.
virtual void traverseZones( SceneTraversalState* state ) = 0;
/// Traverse the zones in this space starting with the given zone. Where appropriate, traversal
/// should be handed off to connected spaces.
///
/// @param state State which should be updated by the traversal.
/// @param startZoneId ID of zone in this manager in which to start traversal.
virtual void traverseZones( SceneTraversalState* state, U32 startZoneId ) = 0;
/// @}
// SimObject.
virtual bool writeField( StringTableEntry fieldName, const char* value );
static void initPersistFields();
// NetObject.
virtual U32 packUpdate( NetConnection* connection, U32 mask, BitStream* stream );
virtual void unpackUpdate( NetConnection* connection, BitStream* stream );
private:
static bool _setZoneGroup( void* object, const char* index, const char* data );
};
#endif // !_SCENEZONESPACE_H_

View file

@ -0,0 +1,923 @@
//-----------------------------------------------------------------------------
// 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/zones/sceneZoneSpaceManager.h"
#include "platform/profiler.h"
#include "platform/platformMemory.h"
#include "scene/sceneContainer.h"
#include "scene/zones/sceneRootZone.h"
#include "scene/zones/sceneZoneSpace.h"
// Uncomment to enable verification code for debugging. This slows the
// manager down significantly but will allow to find zoning state corruption
// much quicker.
//#define DEBUG_VERIFY
//#define DEBUG_SPEW
ClassChunker< SceneObject::ZoneRef > SceneZoneSpaceManager::smZoneRefChunker;
//-----------------------------------------------------------------------------
SceneZoneSpaceManager::SceneZoneSpaceManager( SceneContainer* container )
: mContainer( container ),
mRootZone( new SceneRootZone() ),
mNumTotalAllocatedZones( 0 ),
mNumActiveZones( 0 ),
mDirtyArea( Box3F::Invalid )
{
VECTOR_SET_ASSOCIATION( mZoneSpaces );
VECTOR_SET_ASSOCIATION( mZoneLists );
VECTOR_SET_ASSOCIATION( mZoneSpacesQueryList );
VECTOR_SET_ASSOCIATION( mDirtyObjects );
VECTOR_SET_ASSOCIATION( mDirtyZoneSpaces );
}
//-----------------------------------------------------------------------------
SceneZoneSpaceManager::~SceneZoneSpaceManager()
{
// Delete root zone.
SAFE_DELETE( mRootZone );
mNumTotalAllocatedZones = 0;
mNumActiveZones = 0;
}
//-----------------------------------------------------------------------------
void SceneZoneSpaceManager::registerZones( SceneZoneSpace* object, U32 numZones )
{
AssertFatal( _getZoneSpaceIndex( object ) == -1, "SceneZoneSpaceManager::registerZones - Object already registered" );
_compactZonesCheck();
const U32 zoneRangeStart = mNumTotalAllocatedZones;
mNumTotalAllocatedZones += numZones;
mNumActiveZones += numZones;
object->mNumZones = numZones;
object->mZoneRangeStart = zoneRangeStart;
// Allocate zone lists for all of the zones managed by the object.
// Add an entry to each list that points back to the zone space.
mZoneLists.increment( numZones );
for( U32 i = zoneRangeStart; i < mNumTotalAllocatedZones; ++ i )
{
SceneObject::ZoneRef* zoneRef = smZoneRefChunker.alloc();
zoneRef->object = object;
zoneRef->nextInBin = NULL;
zoneRef->prevInBin = NULL;
zoneRef->nextInObj = NULL;
zoneRef->zone = i;
mZoneLists[ i ] = zoneRef;
}
// Add space to list.
mZoneSpaces.push_back( object );
object->mManager = this;
// Set ZoneObjectType.
object->mTypeMask |= ZoneObjectType;
// Put the object on the dirty list.
if( !object->isRootZone() )
{
// Make sure the object gets on the zone space list even
// if it is already on the object dirty list.
object->mZoneRefDirty = false;
notifyObjectChanged( object );
}
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SceneZoneSpaceManager] Range %i-%i allocated to: %s",
zoneRangeStart, numZones, object->describeSelf().c_str() );
#endif
}
//-----------------------------------------------------------------------------
void SceneZoneSpaceManager::unregisterZones( SceneZoneSpace* object )
{
S32 zoneSpaceIndex = _getZoneSpaceIndex( object );
AssertFatal( zoneSpaceIndex != -1, "SceneZoneSpaceManager::unregisterZones - Object not registered as zone space" );
AssertFatal( mNumActiveZones >= object->mNumZones, "SceneZoneSpaceManager::unregisterZones - Too many zones removed");
const U32 zoneRangeStart = object->getZoneRangeStart();
const U32 numZones = object->getZoneRange();
// Destroy the zone lists for the zones registered
// by the object.
for( U32 j = zoneRangeStart; j < zoneRangeStart + numZones; j ++ )
{
// Delete all object links.
_clearZoneList( j );
// Delete the first link which refers to the zone itself.
smZoneRefChunker.free( mZoneLists[ j ] );
mZoneLists[ j ] = NULL;
}
// Destroy the connections the zone space has.
object->_disconnectAllZoneSpaces();
// Remove the zone manager entry.
mNumActiveZones -= numZones;
mZoneSpaces.erase( zoneSpaceIndex );
// Clear ZoneObjectType.
object->mTypeMask &= ~ZoneObjectType;
// Clear zone assignments.
object->mZoneRangeStart = InvalidZoneId;
object->mNumZones = 0;
object->mManager = NULL;
// Mark the zone space's area as dirty.
mDirtyArea.intersect( object->getWorldBox() );
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SceneZoneSpaceManager] Range %i-%i released from: %s",
zoneRangeStart, numZones, object->describeSelf().c_str() );
#endif
}
//-----------------------------------------------------------------------------
void SceneZoneSpaceManager::_rezoneObjects( const Box3F& area )
{
static Vector< SceneObject* > sObjects( __FILE__, __LINE__ );
// Find all objects in the area. We cannot use the callback
// version here and directly trigger rezoning since the rezoning
// itself does a container query.
sObjects.clear();
mContainer->findObjectList( area, 0xFFFFFFFF, &sObjects );
// Rezone the objects.
const U32 numObjects = sObjects.size();
for( U32 i = 0; i < numObjects; ++ i )
{
SceneObject* object = sObjects[ i ];
if( object != getRootZone() )
_rezoneObject( object );
}
}
//-----------------------------------------------------------------------------
void SceneZoneSpaceManager::_compactZonesCheck()
{
if( mNumActiveZones > ( mNumTotalAllocatedZones / 2 ) )
return;
// Redistribute the zone IDs among the current zone spaces
// so that the range of IDs is consecutive.
const U32 numZoneSpaces = mZoneSpaces.size();
U32 nextZoneId = 0;
Vector< SceneObject::ZoneRef* > newZoneLists;
newZoneLists.setSize( mNumActiveZones );
for( U32 i = 0; i < numZoneSpaces; ++ i )
{
SceneZoneSpace* space = mZoneSpaces[ i ];
const U32 oldZoneRangeStart = space->getZoneRangeStart();
const U32 newZoneRangeStart = nextZoneId;
const U32 numZones = space->getZoneRange();
// Assign the new zone range start.
space->mZoneRangeStart = newZoneRangeStart;
nextZoneId += numZones;
// Relocate the zone lists to match the new zone IDs and update
// the contents of the zone lists to match the new IDs.
for( U32 n = 0; n < numZones; ++ n )
{
const U32 newZoneId = newZoneRangeStart + n;
const U32 oldZoneId = oldZoneRangeStart + n;
// Relocate list.
newZoneLists[ newZoneId ] = mZoneLists[ oldZoneId ];
// Update entries.
for( SceneObject::ZoneRef* ref = newZoneLists[ newZoneId ]; ref != NULL; ref = ref->nextInBin )
ref->zone = newZoneId;
}
}
mNumTotalAllocatedZones = nextZoneId;
mZoneLists = newZoneLists;
AssertFatal( mNumTotalAllocatedZones == mNumActiveZones, "SceneZoneSpaceManager::_compactZonesCheck - Error during compact; mismatch between active and allocated zones" );
}
//-----------------------------------------------------------------------------
S32 SceneZoneSpaceManager::_getZoneSpaceIndex( SceneZoneSpace* object ) const
{
const U32 numZoneSpaces = getNumZoneSpaces();
for( U32 i = 0; i < numZoneSpaces; ++ i )
if( mZoneSpaces[ i ] == object )
return i;
return -1;
}
//-----------------------------------------------------------------------------
void SceneZoneSpaceManager::findZone( const Point3F& p, SceneZoneSpace*& owner, U32& zone ) const
{
AssertFatal( mNumActiveZones >= 1, "SceneZoneSpaceManager::findZone - Must have at least one active zone in scene (outdoor zone)" );
// If there are no zones in the level other than the outdoor
// zone, just return that.
if( mNumActiveZones == 1 )
{
owner = getRootZone();
zone = RootZoneId;
return;
}
PROFILE_SCOPE( SceneZoneSpaceManager_findZone );
// Query the scene container for zones with a query
// box that tightly fits around the point.
Box3F queryBox( p.x - 0.1f, p.y - 0.1f, p.z - 0.1f,
p.x + 0.1f, p.y + 0.1f, p.z + 0.1f );
_queryZoneSpaces( queryBox );
// Go through the zones and look for the first one that
// contains the given point.
const U32 numZones = mZoneSpacesQueryList.size();
for( U32 i = 0; i < numZones; ++ i )
{
SceneZoneSpace* zoneSpace = dynamic_cast< SceneZoneSpace* >( mZoneSpacesQueryList[ i ] );
if( !zoneSpace )
continue;
AssertFatal( zoneSpace != getRootZone(), "SceneZoneSpaceManager::findZone - SceneRootZone returned by zone manager query" );
// If the point is in one of the zones of this manager,
// then make this the result.
U32 inZone = zoneSpace->getPointZone( p );
if( inZone != InvalidZoneId )
{
owner = zoneSpace;
zone = inZone;
return;
}
}
// No other zone matched so return the outdoor zone.
owner = getRootZone();
zone = RootZoneId;
}
//-----------------------------------------------------------------------------
U32 SceneZoneSpaceManager::findZones( const Box3F& area, Vector< U32 >& outZones ) const
{
// Query all zone spaces in the area.
_queryZoneSpaces( area );
// Query each zone space for overlaps with the given
// area and add the zones to outZones.
bool outsideIncluded = false;
U32 numTotalZones = 0;
const U32 numZoneSpaces = mZoneSpacesQueryList.size();
for( U32 i = 0; i < numZoneSpaces; ++ i )
{
SceneZoneSpace* zoneSpace = dynamic_cast< SceneZoneSpace* >( mZoneSpacesQueryList[ i ] );
if( !zoneSpace )
continue;
AssertFatal( zoneSpace != getRootZone(), "SceneZoneSpaceManager::findZones - SceneRootZone returned by zone manager query" );
// Query manager.
U32 zones[ SceneObject::MaxObjectZones ];
U32 numZones = 0;
outsideIncluded |= zoneSpace->getOverlappingZones( area, zones, numZones );
// Add overlapped zones.
for( U32 n = 0; n < numZones; n ++ )
{
outZones.push_back( zones[ n ] );
numTotalZones ++;
}
}
// If the area box wasn't fully enclosed by the zones of the
// manager(s) or the query only returned the outside zone,
// add the outside zone to the list.
if( outsideIncluded || numTotalZones == 0 )
{
outZones.push_back( RootZoneId );
numTotalZones ++;
}
return numTotalZones;
}
//-----------------------------------------------------------------------------
void SceneZoneSpaceManager::_rezoneObject( SceneObject* object )
{
PROFILE_SCOPE( SceneZoneSpaceManager_rezoneObject );
AssertFatal( !dynamic_cast< SceneRootZone* >( object ), "SceneZoneSpaceManager::_rezoneObject - Cannot rezone the SceneRootZone!" );
// If the object is not yet assigned to zones,
// do so now and return.
if( !object->mNumCurrZones )
{
_zoneInsert( object );
return;
}
// If we have no zones in the scene other than the outdoor zone or if the
// object has global bounds on (and thus is always in the outdoor zone) or
// is an object that is restricted to the outdoor zone, leave the object's
// zoning state untouched.
if( mNumActiveZones == 1 || object->isGlobalBounds() || object->getTypeMask() & OUTDOOR_OBJECT_TYPEMASK )
{
object->mZoneRefDirty = false;
return;
}
// First, find out whether there's even a chance of the zoning to have changed
// for the object.
_queryZoneSpaces( object->getWorldBox() );
const U32 numZoneSpaces = mZoneSpacesQueryList.size();
if( !numZoneSpaces )
{
// There is no zone in the object's area. If it is already assigned to the
// root zone, then we don't need an update. Otherwise, we do.
if( object->mNumCurrZones == 1 &&
object->mZoneRefHead &&
object->mZoneRefHead->zone == RootZoneId )
{
object->mZoneRefDirty = false;
return;
}
}
// Update the object's zoning information by removing and recomputing
// its zoning information.
_zoneRemove( object );
_zoneInsert( object, true ); // Query already in place.
}
//-----------------------------------------------------------------------------
void SceneZoneSpaceManager::registerObject( SceneObject* object )
{
// Just put it on the dirty list.
notifyObjectChanged( object );
}
//-----------------------------------------------------------------------------
void SceneZoneSpaceManager::unregisterObject( SceneObject* object )
{
// Remove from dirty list.
mDirtyObjects.remove( object );
// Remove from zone lists.
_zoneRemove( object );
// If it's a zone space, unregister it.
if( object->getTypeMask() & ZoneObjectType && dynamic_cast< SceneZoneSpace* >( object ) )
{
SceneZoneSpace* zoneSpace = static_cast< SceneZoneSpace* >( object );
unregisterZones( zoneSpace );
mDirtyZoneSpaces.remove( zoneSpace );
}
}
//-----------------------------------------------------------------------------
void SceneZoneSpaceManager::updateObject( SceneObject* object )
{
// If no zone spaces have changed and the object's zoning
// state is clean, then there's nothing to do for this object.
if( mDirtyZoneSpaces.empty() && !object->mZoneRefDirty )
return;
// Otherwise update all the dirty zoning state.
updateZoningState();
}
//-----------------------------------------------------------------------------
void SceneZoneSpaceManager::notifyObjectChanged( SceneObject* object )
{
AssertFatal( object != getRootZone(), "SceneZoneSpaceManager::notifyObjectChanged - Cannot dirty root zone!" );
// Ignore if object is already on the dirty list.
if( object->mZoneRefDirty )
return;
// Put the object on the respective dirty list.
if( object->getTypeMask() & ZoneObjectType )
{
SceneZoneSpace* zoneSpace = dynamic_cast< SceneZoneSpace* >( object );
AssertFatal( zoneSpace != NULL, "SceneZoneSpaceManager::notifyObjectChanged - ZoneObjectType is not a SceneZoneSpace!" );
if( zoneSpace )
mDirtyZoneSpaces.push_back( zoneSpace );
}
else
{
mDirtyObjects.push_back( object );
}
// Mark object as dirty.
object->mZoneRefDirty = true;
}
//-----------------------------------------------------------------------------
void SceneZoneSpaceManager::updateZoningState()
{
// If there are no dirty objects, there's nothing to do.
if( mDirtyObjects.empty() &&
mDirtyZoneSpaces.empty() &&
mDirtyArea == Box3F::Invalid )
return;
// Otherwise, first update the zone spaces. Do this in two passes:
// first take all the dirty zone spaces out of the zoning state and
// then rezone the combined area of all dirty zone spaces.
//
// Note that this path here is pretty much only relevant during loading
// or editing and thus can be less performant than the path for individual
// objects below.
while( !mDirtyZoneSpaces.empty() )
{
SceneZoneSpace* zoneSpace = mDirtyZoneSpaces.last();
mDirtyZoneSpaces.decrement();
// Remove the zoning state of the object.
_zoneRemove( zoneSpace );
// Destroy all connections that this zone space has to
// other zone spaces.
zoneSpace->_disconnectAllZoneSpaces();
// Nuke its zone lists.
const U32 numZones = zoneSpace->getZoneRange();
for( U32 n = 0; n < numZones; ++ n )
_clearZoneList( zoneSpace->getZoneRangeStart() + n );
// Merge into dirty region.
mDirtyArea.intersect( zoneSpace->getWorldBox() );
}
if( mDirtyArea != Box3F::Invalid )
{
// Rezone everything in the dirty region.
_rezoneObjects( mDirtyArea );
mDirtyArea = Box3F::Invalid;
// Verify zoning state.
#ifdef DEBUG_VERIFY
verifyState();
#endif
// Fire the zoning changed signal to let interested parties
// know that the zoning setup of the scene has changed.
getZoningChangedSignal().trigger( this );
}
// And finally, update objects that have changed state.
while( !mDirtyObjects.empty() )
{
SceneObject* object = mDirtyObjects.last();
mDirtyObjects.decrement();
if( object->mZoneRefDirty )
_rezoneObject( object );
AssertFatal( !object->mZoneRefDirty, "SceneZoneSpaceManager::updateZoningState - Object still dirty!" );
}
AssertFatal( mDirtyObjects.empty(), "SceneZoneSpaceManager::updateZoningState - Still have dirty objects!" );
AssertFatal( mDirtyZoneSpaces.empty(), "SceneZoneSpaceManager::updateZoningState - Still have dirty zones!" );
}
//-----------------------------------------------------------------------------
void SceneZoneSpaceManager::_zoneInsert( SceneObject* object, bool queryListInitialized )
{
PROFILE_SCOPE( SceneZoneSpaceManager_zoneInsert );
AssertFatal( object->mNumCurrZones == 0, "SceneZoneSpaceManager::_zoneInsert - Object already in zone list" );
AssertFatal( object->getContainer() != NULL, "SceneZoneSpaceManager::_zoneInsert - Object must be in scene" );
AssertFatal( !dynamic_cast< SceneRootZone* >( object ), "SceneZoneSpaceManager::_zoneInsert - Must not be called on SceneRootZone" );
// If all we have is a single zone in the scene, then it must
// be the outdoor zone. Simply assign the object to it. Also do this
// if the object has global bounds on since we always assign these to
// just the outdoor zone. Finally, also do it for all object types that
// we want to restrict to the outdoor zone.
if( mNumActiveZones == 1 || object->isGlobalBounds() || object->getTypeMask() & OUTDOOR_OBJECT_TYPEMASK )
_addToOutdoorZone( object );
else
{
// Otherwise find all zones spaces that intersect with the object's
// world box.
if( !queryListInitialized )
_queryZoneSpaces( object->getWorldBox() );
// Go through the zone spaces and link all zones that the object
// overlaps.
bool outsideIncluded = true;
const U32 numZoneSpaces = mZoneSpacesQueryList.size();
for( U32 i = 0; i < numZoneSpaces; ++ i )
{
SceneZoneSpace* zoneSpace = dynamic_cast< SceneZoneSpace* >( mZoneSpacesQueryList[ i ] );
if( !zoneSpace )
continue;
AssertFatal( zoneSpace != getRootZone(), "SceneZoneSpaceManager::_zoneInsert - SceneRootZone returned by zone space query" );
// If we are inserting a zone space, then the query will turn up
// the object itself at some point. Skip it.
if( zoneSpace == object )
continue;
// Find the zones that the object overlaps within
// the zone space.
U32 numZones = 0;
U32 zones[ SceneObject::MaxObjectZones ];
bool overlapsOutside = zoneSpace->getOverlappingZones( object, zones, numZones );
AssertFatal( numZones != 0 || overlapsOutside,
"SceneZoneSpaceManager::_zoneInsert - Object must be fully contained in one or more zones or intersect the outside zone" );
outsideIncluded &= overlapsOutside; // Only include outside if *none* of the zones fully contains the object.
// Link the object to the zones.
for( U32 n = 0; n < numZones; ++ n )
_addToZoneList( zones[ n ], object );
// Let the zone manager know we have added objects to its
// zones.
if( numZones > 0 )
zoneSpace->_onZoneAddObject( object, zones, numZones );
}
// If the object crosses into the outside zone or hasn't been
// added to any zone above, add it to the outside zone.
if( outsideIncluded )
_addToOutdoorZone( object );
}
// Mark the zoning state of the object as current.
object->mZoneRefDirty = false;
}
//-----------------------------------------------------------------------------
void SceneZoneSpaceManager::_zoneRemove( SceneObject* obj )
{
PROFILE_SCOPE( SceneZoneSpaceManager_zoneRemove );
// Remove the object from the zone lists.
for( SceneObject::ZoneRef* walk = obj->mZoneRefHead; walk != NULL; )
{
// Let the zone owner know we are removing an object
// from its zones.
getZoneOwner( walk->zone )->_onZoneRemoveObject( walk->object );
// Now remove the ZoneRef link this object has in the
// zone list of the current zone.
SceneObject::ZoneRef* remove = walk;
walk = walk->nextInObj;
remove->prevInBin->nextInBin = remove->nextInBin;
if( remove->nextInBin )
remove->nextInBin->prevInBin = remove->prevInBin;
smZoneRefChunker.free( remove );
}
// Clear the object's zoning state.
obj->mZoneRefHead = NULL;
obj->mZoneRefDirty = false;
obj->mNumCurrZones = 0;
}
//-----------------------------------------------------------------------------
void SceneZoneSpaceManager::_addToZoneList( U32 zoneId, SceneObject* object )
{
SceneObject::ZoneRef* zoneList = mZoneLists[ zoneId ];
AssertFatal( zoneList != NULL, "SceneZoneSpaceManager::_addToZoneList - Zone list not initialized" );
AssertFatal( object != zoneList->object, "SCene::_addToZoneList - Cannot add zone to itself" );
SceneObject::ZoneRef* newRef = smZoneRefChunker.alloc();
// Add the object to the zone list.
newRef->zone = zoneId;
newRef->object = object;
newRef->nextInBin = zoneList->nextInBin;
newRef->prevInBin = zoneList;
if( zoneList->nextInBin )
zoneList->nextInBin->prevInBin = newRef;
zoneList->nextInBin = newRef;
// Add the zone to the object list.
newRef->nextInObj = object->mZoneRefHead;
object->mZoneRefHead = newRef;
object->mNumCurrZones ++;
}
//-----------------------------------------------------------------------------
void SceneZoneSpaceManager::_clearZoneList( U32 zoneId )
{
AssertFatal( zoneId < getNumZones(), "SceneZoneSpaceManager::_clearZoneList - Zone ID out of range" );
SceneObject::ZoneRef* list = mZoneLists[ zoneId ];
SceneZoneSpace* zoneSpace = getZoneOwner( zoneId );
// Go through the objects in the zone list and unlink and
// delete their zone entries.
for( SceneObject::ZoneRef* walk = list->nextInBin; walk != NULL; walk = walk->nextInBin )
{
SceneObject* object = walk->object;
AssertFatal( object != NULL, "SceneZoneSpaceManager::_clearZoneList - Object field not set on link" );
// The zone entry links on the objects are singly-linked lists
// linked through nextInObject so we need to find where in the
// objects zone entry list the node for the current zone is.
SceneObject::ZoneRef** ptrNext = &object->mZoneRefHead;
while( *ptrNext && *ptrNext != walk )
ptrNext = &( *ptrNext )->nextInObj;
AssertFatal( *ptrNext == walk, "SceneZoneSpaceManager::_clearZoneList - Zone entry not found on object in zone list!");
// Unlink and delete the entry.
*ptrNext = ( *ptrNext )->nextInObj;
smZoneRefChunker.free( walk );
object->mNumCurrZones --;
// If this is the only zone the object was in, mark
// its zoning state as dirty so it will get assigned
// to the outdoor zone on the next update.
if( !object->mZoneRefHead )
object->mZoneRefDirty = true;
// Let the zone know we have removed the object.
zoneSpace->_onZoneRemoveObject( object );
}
list->nextInBin = NULL;
}
//-----------------------------------------------------------------------------
SceneObject::ZoneRef* SceneZoneSpaceManager::_findInZoneList( U32 zoneId, SceneObject* object ) const
{
for( SceneObject::ZoneRef* ref = object->mZoneRefHead; ref != NULL; ref = ref->nextInObj )
if( ref->zone == zoneId )
return ref;
return NULL;
}
//-----------------------------------------------------------------------------
void SceneZoneSpaceManager::_addToOutdoorZone( SceneObject* object )
{
AssertFatal( !object->mZoneRefHead || !_findInZoneList( RootZoneId, object ),
"SceneZoneSpaceManager::_addToOutdoorZone - Object already added to outdoor zone" );
// Add the object to the outside's zone list. This method is always called
// *last* after the object has already been assigned to any other zone it
// intersects. Since we always prepend to the zoning lists, this means that
// the outdoor zone will always be *first* in the list of zones that an object
// is assigned to which generally is a good order.
_addToZoneList( RootZoneId, object );
// Let the zone know we added an object to it.
const U32 zoneId = RootZoneId;
static_cast< SceneZoneSpace* >( getRootZone() )->_onZoneAddObject( object, &zoneId, 1 );
}
//-----------------------------------------------------------------------------
void SceneZoneSpaceManager::_queryZoneSpaces( const Box3F& area ) const
{
mZoneSpacesQueryList.clear();
mContainer->findObjectList( area, ZoneObjectType, &mZoneSpacesQueryList );
}
//-----------------------------------------------------------------------------
void SceneZoneSpaceManager::dumpZoneStates( bool update )
{
if( update )
_rezoneObjects( getRootZone()->getWorldBox() );
const U32 numZoneSpaces = mZoneSpaces.size();
for( U32 i = 0; i < numZoneSpaces; ++ i )
mZoneSpaces[ i ]->dumpZoneState( false );
}
//-----------------------------------------------------------------------------
void SceneZoneSpaceManager::verifyState()
{
AssertFatal( mZoneSpaces.size() <= mNumActiveZones,
"SceneZoneSpaceManager::verifyState - More zone spaces than active zones!" );
AssertFatal( mNumTotalAllocatedZones >= mNumActiveZones,
"SceneZoneSpaceManager::verifyState - Fewer allocated than active zones!" );
AssertFatal( mRootZone->getZoneRangeStart() == 0,
"SceneZoneSpaceManager::verifyState - Invalid ID on root zone!" );
AssertFatal( mRootZone->getZoneRange() == 1,
"SceneZoneSpaceManager::verifyState - Invalid zone range on root zone!" );
// First validate the zone spaces themselves.
const U32 numZoneSpaces = mZoneSpaces.size();
for( U32 i = 0; i < numZoneSpaces; ++ i )
{
SceneZoneSpace* space = mZoneSpaces[ i ];
#ifndef TORQUE_DISABLE_MEMORY_MANAGER
Memory::checkPtr( space );
#endif
AssertFatal( space->getTypeMask() & ZoneObjectType, "SceneZoneSpaceManager::verifyState - Zone space is not a ZoneObjectType!" );
const U32 zoneRangeStart = space->getZoneRangeStart();
const U32 numZones = space->getZoneRange();
// Verify each of the allocated zones in this space.
for( U32 n = 0; n < numZones; ++ n )
{
const U32 zoneId = zoneRangeStart + n;
// Simple validation of zone ID.
AssertFatal( isValidZoneId( zoneId ), "SceneZoneSpaceManager::verifyState - Zone space is assigned in invalid zone ID!" );
AssertFatal( mZoneLists[ zoneId ] != NULL, "SceneZoneSpaceManager::verifyState - Zone list missing for zone!" );
AssertFatal( mZoneLists[ zoneId ]->object == space, "SceneZoneSpaceManager::verifyState - Zone list entry #0 is not referring back to zone!" );
for( SceneObject::ZoneRef* ref = mZoneLists[ zoneId ]; ref != NULL; ref = ref->nextInBin )
{
AssertFatal( ref->zone == zoneId, "SceneZoneSpaceManager::verifyState - Incorrect ID in zone list!" );
AssertFatal( ref->object != NULL, "SceneZoneSpaceManager::verifyState - Null object pointer in zone list!" );
#ifndef TORQUE_DISABLE_MEMORY_MANAGER
Memory::checkPtr( ref->object );
#endif
}
}
// Make sure no other zone space owns any of the same IDs.
for( U32 n = 0; n < numZoneSpaces; ++ n )
{
if( n == i )
continue;
SceneZoneSpace* otherSpace = mZoneSpaces[ n ];
AssertFatal( otherSpace->getZoneRangeStart() >= zoneRangeStart + numZones ||
otherSpace->getZoneRangeStart() + otherSpace->getZoneRange() <= zoneRangeStart,
"SceneZoneSpaceManager::verifyState - Overlap between zone ID ranges of zone spaces!" );
}
// Make sure that all zone connections appear to be valid.
for( SceneZoneSpace::ZoneSpaceRef* ref = space->mConnectedZoneSpaces; ref != NULL; ref = ref->mNext )
{
#ifndef TORQUE_DISABLE_MEMORY_MANAGER
Memory::checkPtr( ref->mZoneSpace );
#endif
AssertFatal( _getZoneSpaceIndex( ref->mZoneSpace ) != -1, "SceneZoneSpaceManager::verifyState - Zone connected to invalid zone!" );
AssertFatal( ref->mZoneSpace->getTypeMask() & ZoneObjectType, "SceneZoneSpaceManager::verifyState - Zone space is not a ZoneObjectType!" );
}
}
//TODO: can do a lot more validation here
}

View file

@ -0,0 +1,331 @@
//-----------------------------------------------------------------------------
// 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 _SCENEZONESPACEMANAGER_H_
#define _SCENEZONESPACEMANAGER_H_
#ifndef _SCENEOBJECT_H_
#include "scene/sceneObject.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _TSIGNAL_H_
#include "core/util/tSignal.h"
#endif
#ifndef _DATACHUNKER_H_
#include "core/dataChunker.h"
#endif
class SceneContainer;
class SceneRootZone;
class SceneZoneSpace;
/// Object that manages zone spaces in a scene.
class SceneZoneSpaceManager
{
public:
class ZoneContentIterator;
friend class SceneZoneSpace; // mZoneLists
friend class ZoneContentIterator; // mZoneLists
/// A signal used to notify that the zone setup of the scene has changed.
///
/// @note If you use this signal to maintain state that depends on the zoning
/// setup, it's best to not immediately update the sate in response to this
/// signal. The reason is that during loading and editing, the signal may
/// be fired a lot and continuously updating dependent data may waste a lot
/// of time.
typedef Signal< void( SceneZoneSpaceManager* ) > ZoningChangedSignal;
enum
{
/// Zone ID of the exterior zone.
RootZoneId = 0,
/// Constant to indicate an invalid zone ID.
InvalidZoneId = 0xFFFFFFFF,
};
/// Iterator for the contents of a given zone.
class ZoneContentIterator
{
public:
ZoneContentIterator( SceneZoneSpaceManager* manager, int zoneId, bool upToDate = true )
{
AssertFatal( zoneId < manager->getNumZones(), "SceneZoneSpaceManager::ZoneContentIterator - Zone ID out of range" );
if( upToDate )
{
// Since zoning is updated lazily, the zone contents may actually
// be out of date. Force an update by triggering rezoning on the
// zone object. This is brute-force but this iterator is not meant
// to be used for high-frequency code anyway.
//
// Use the area-based rezoning so that we can also properly iterate
// over the contents of SceneRootZone.
manager->_rezoneObjects( ( ( SceneObject* ) manager->getZoneOwner( zoneId ) )->getWorldBox() );
}
mCurrent = manager->mZoneLists[ zoneId ]->nextInBin; // Skip zone object entry.
}
bool isValid() const
{
return ( mCurrent != NULL );
}
bool operator !() const
{
return ( mCurrent == NULL );
}
ZoneContentIterator& operator ++()
{
if( mCurrent )
mCurrent = mCurrent->nextInBin;
return *this;
}
ZoneContentIterator& operator --()
{
if( mCurrent )
mCurrent = mCurrent->prevInBin;
return *this;
}
SceneObject* operator *() const
{
AssertFatal( mCurrent != NULL, "SceneManager::ZoneContentIterator::operator* - Invalid iterator" );
return mCurrent->object;
}
SceneObject* operator ->() const
{
AssertFatal( mCurrent != NULL, "SceneManager::ZoneContentIterator::operator-> - Invalid iterator" );
return mCurrent->object;
}
private:
SceneObject::ZoneRef* mCurrent;
};
protected:
/// The root and outdoor zone of the scene.
SceneRootZone* mRootZone;
/// Scene container that holds the zone spaces we are managing.
SceneContainer* mContainer;
/// Collection of objects that manage zones.
Vector< SceneZoneSpace* > mZoneSpaces;
/// Total number of zones that have been allocated in the scene.
U32 mNumTotalAllocatedZones;
/// Number of zone IDs that are in active use.
U32 mNumActiveZones;
/// Object list for each zone in the scene.
/// First entry in the list points back to the zone manager.
Vector< SceneObject::ZoneRef* > mZoneLists;
/// Vector used repeatedly for zone space queries on the container.
mutable Vector< SceneObject* > mZoneSpacesQueryList;
/// Allocator for ZoneRefs.
static ClassChunker< SceneObject::ZoneRef > smZoneRefChunker;
/// @name Dirty Lists
/// Updating the zoning state of a scene is done en block rather than
/// individually for each object as it changes transforms or size.
/// @{
/// Area of the scene that needs to be rezoned.
Box3F mDirtyArea;
/// List of zone spaces that have changed state and need updating.
Vector< SceneZoneSpace* > mDirtyZoneSpaces;
/// List of objects (non-zone spaces) that have changed state and need
/// updating.
Vector< SceneObject* > mDirtyObjects;
/// @}
/// Check to see if we have accumulated a lot of unallocate zone IDs and if so,
/// compact the zoning lists by reassigning IDs.
///
/// @warn This method may alter all zone IDs in the scene!
void _compactZonesCheck();
/// Return the index into #mZoneSpaces for the given object or -1 if
/// @object is not a zone manager.
S32 _getZoneSpaceIndex( SceneZoneSpace* object ) const;
/// Attach zoning state to the given object.
void _zoneInsert( SceneObject* object, bool queryListInitialized = false );
/// Detach zoning state from the given object.
void _zoneRemove( SceneObject* object );
/// Add to given object to the zone list of the given zone.
void _addToZoneList( U32 zoneId, SceneObject* object );
/// Clear all objects assigned to the given zone.
/// @note This does not remove the first link in the zone list which is the link
/// back to the zone manager.
void _clearZoneList( U32 zoneId );
/// Find the given object in the zone list of the given zone.
SceneObject::ZoneRef* _findInZoneList( U32 zoneId, SceneObject* object ) const;
/// Assign the given object to the outdoor zone.
void _addToOutdoorZone( SceneObject* object );
/// Rezone all objects in the given area.
void _rezoneObjects( const Box3F& area );
/// Update the zoning state of the given object.
void _rezoneObject( SceneObject* object );
/// Fill #mZoneSpacesQueryList with all ZoneObjectType objects in the given area.
void _queryZoneSpaces( const Box3F& area ) const;
public:
SceneZoneSpaceManager( SceneContainer* container );
~SceneZoneSpaceManager();
/// Bring the zoning state of the scene up to date. This will cause objects
/// that have moved or have been resized to be rezoned and will updated regions
/// of the scene that had their zoning setup changed.
///
/// @note This method depends on proper use of notifyObjectChanged().
void updateZoningState();
/// @name Objects
/// @{
/// Add zoning state to the given object.
void registerObject( SceneObject* object );
/// Remove the given object from the zoning state.
void unregisterObject( SceneObject* object );
/// Let the manager know that state relevant to zoning of the given
/// object has changed.
void notifyObjectChanged( SceneObject* object );
/// Update the zoning state of the given object.
void updateObject( SceneObject* object );
/// @}
/// @name Zones
/// @{
/// Return the root zone of the scene.
SceneRootZone* getRootZone() const { return mRootZone; }
/// Register a zone manager.
///
/// @param object SceneZoneSpace object that contains zones.
/// @param numZones Number of zones that @a object contains.
void registerZones( SceneZoneSpace* object, U32 numZones );
/// Unregister a zone manager.
///
/// @param object Object that contains zones.
void unregisterZones( SceneZoneSpace* object );
/// Return true if the given ID belongs to a currently registered zone.
bool isValidZoneId( const U32 zoneId ) const
{
return ( zoneId < mNumTotalAllocatedZones && mZoneLists[ zoneId ] );
}
/// Get the scene object that contains the zone with the given ID.
///
/// @param zoneId ID of the zone. Must be valid.
/// @return The zone space that has registered the given zone.
SceneZoneSpace* getZoneOwner( const U32 zoneId ) const
{
AssertFatal( isValidZoneId( zoneId ), "SceneManager::getZoneOwner - Invalid zone ID!");
return ( SceneZoneSpace* ) mZoneLists[ zoneId ]->object;
}
/// Return the total number of zones in the scene.
U32 getNumZones() const { return mNumTotalAllocatedZones; }
/// Return the effective amount of used zone IDs in the scene.
U32 getNumActiveZones() const { return mNumActiveZones; }
/// Return the total number of objects in the scene that manage zones.
U32 getNumZoneSpaces() const { return mZoneSpaces.size(); }
/// Find the zone that contains the given point.
///
/// Note that the result can be <em>any</em> zone containing the given
/// point.
void findZone( const Point3F& point, SceneZoneSpace*& outZoneSpace, U32& outZoneID ) const;
/// Collect the IDs of all zones that overlap the given area.
///
/// @param area AABB of scene space to query.
/// @param outZones IDs of all overlapped zones are added to this vector.
///
/// @return Number of zones that have been added to @a outZones. Always at least
/// 1 as at least the outdoor zone always overlaps the given area (though if another zone
/// manager fully contains @a area, the outdoor zone will not be added to the list).
U32 findZones( const Box3F& area, Vector< U32 >& outZones ) const;
static ZoningChangedSignal& getZoningChangedSignal()
{
static ZoningChangedSignal sSignal;
return sSignal;
}
/// @name Debugging
/// @{
/// Verify the current zoning state. This makes sure all the connectivity
/// information and all the zone assignments appear to be correct.
void verifyState();
/// Dump the current state of all zone spaces in the scene to the console.
/// @param update If true, zoning state states are updated first; if false, zoning is
/// dumped as is.
void dumpZoneStates( bool update = true );
/// @}
/// @}
};
#endif // !_SCENEZONESPACEMANAGER_H_