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