tge/engine/sceneGraph/sceneGraph.cc
2017-04-17 06:17:10 -06:00

1125 lines
37 KiB
C++
Executable File

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "sceneGraph/sceneGraph.h"
#include "sim/sceneObject.h"
#include "sceneGraph/sceneRoot.h"
#include "sceneGraph/sceneState.h"
#include "dgl/dgl.h"
#include "sim/netConnection.h"
#include "dgl/gBitmap.h"
#include "terrain/sky.h"
#include "terrain/terrData.h"
#include "terrain/terrRender.h"
#include "terrain/waterBlock.h"
#include "sim/decalManager.h"
#include "sceneGraph/detailManager.h"
#include "ts/tsShapeInstance.h"
#include "core/fileStream.h"
#include "platform/profiler.h"
#include "console/consoleTypes.h"
const U32 SceneGraph::csmMaxTraversalDepth = 4;
U32 SceneGraph::smStateKey = 0;
SceneGraph* gClientSceneGraph = NULL;
SceneGraph* gServerSceneGraph = NULL;
const U32 SceneGraph::csmRefPoolBlockSize = 4096;
F32 SceneGraph::smVisibleDistanceMod = 1.0;
F32 SceneGraph::mHazeArray[FogTextureDistSize];
U32 SceneGraph::mHazeArrayi[FogTextureDistSize];
F32 SceneGraph::mDistArray[FogTextureDistSize];
//------------------------------------------------------------------------------
//-------------------------------------- IMPLEMENTATION
SceneGraph::SceneGraph(bool isClient)
{
VECTOR_SET_ASSOCIATION(mRefPoolBlocks);
VECTOR_SET_ASSOCIATION(mZoneManagers);
VECTOR_SET_ASSOCIATION(mZoneLists);
mHazeArrayDirty = true;
mCurrZoneEnd = 0;
mNumActiveZones = 0;
mIsClient = isClient;
mNumFogVolumes = 0;
mFogDistance = 250;
mVisibleDistance = 500;
mFogColor.set(128, 128, 128);
mCurrSky = NULL;
mCurrTerrain = NULL;
mFreeRefPool = NULL;
addRefPoolBlock();
mCurrDecalManager = NULL;
}
SceneGraph::~SceneGraph()
{
mCurrZoneEnd = 0;
mNumActiveZones = 0;
for (U32 i = 0; i < mRefPoolBlocks.size(); i++)
{
SceneObjectRef* pool = mRefPoolBlocks[i];
for (U32 j = 0; j < csmRefPoolBlockSize; j++)
AssertFatal(pool[j].object == NULL, "Error, some object isn't properly out of the bins!");
delete [] pool;
}
mFreeRefPool = NULL;
}
void SceneGraph::addRefPoolBlock()
{
mRefPoolBlocks.push_back(new SceneObjectRef[csmRefPoolBlockSize]);
for (U32 i = 0; i < csmRefPoolBlockSize-1; i++)
{
mRefPoolBlocks.last()[i].object = NULL;
mRefPoolBlocks.last()[i].prevInBin = NULL;
mRefPoolBlocks.last()[i].nextInBin = NULL;
mRefPoolBlocks.last()[i].nextInObj = &(mRefPoolBlocks.last()[i+1]);
}
mRefPoolBlocks.last()[csmRefPoolBlockSize-1].object = NULL;
mRefPoolBlocks.last()[csmRefPoolBlockSize-1].prevInBin = NULL;
mRefPoolBlocks.last()[csmRefPoolBlockSize-1].nextInBin = NULL;
mRefPoolBlocks.last()[csmRefPoolBlockSize-1].nextInObj = mFreeRefPool;
mFreeRefPool = &(mRefPoolBlocks.last()[0]);
}
bool SceneGraph::useSpecial = false;
void SceneGraph::renderScene(const U32 objectMask)
{
PROFILE_START(SceneGraphRender);
if (smVisibleDistanceMod > 1.0f)
smVisibleDistanceMod = 1.0f;
else if (smVisibleDistanceMod < 0.5f)
smVisibleDistanceMod = 0.5f;
static bool skipFirstFog = TSShapeInstance::smSkipFirstFog;
if (skipFirstFog)
// HACK: This is the dumbest hack for 3Dfx yet:
// don't two-pass fog for the first frame
if (TSShapeInstance::smSkipFog)
{
TSShapeInstance::smSkipFog = false;
skipFirstFog = false;
}
else
TSShapeInstance::smSkipFog = true;
// Determine the camera position, and store off render state...
MatrixF modelview;
MatrixF mv;
Point3F cp;
dglClearPrimMetrics();
dglSetRenderPrimType(0);
bool multitex = dglDoesSupportARBMultitexture();
dglGetModelview(&modelview);
mv = modelview;
mv.inverse();
mv.getColumn(3, &cp);
setBaseCameraPosition(cp);
static bool prevEnvColor;
static bool prevMultiTex;
// Set up the base SceneState.
F64 left, right, top, bottom, nearPlane, farPlane;
RectI viewport;
dglGetFrustum(&left, &right, &bottom, &top, &nearPlane, &farPlane);
dglGetViewport(&viewport);
TextureHandle envMap = NULL;
if (getCurrentSky() != NULL)
envMap = getCurrentSky()->getEnvironmentMap();
SceneState* pBaseState = new SceneState(NULL,
mCurrZoneEnd,
left, right,
bottom, top,
nearPlane,
F64(getVisibleDistanceMod()),
viewport,
cp,
modelview,
getFogDistanceMod(),
getVisibleDistanceMod(),
mFogColor,
mNumFogVolumes,
mFogVolumes,
envMap,
smVisibleDistanceMod);
// build the fog texture
PROFILE_START(BuildFogTexture);
if(!useSpecial)
buildFogTexture( pBaseState );
else
buildFogTextureSpecial( pBaseState );
PROFILE_END();
// Find the start zone...
SceneObject* startObject;
U32 startZone;
findZone(cp, startObject, startZone);
PROFILE_START(BuildSceneTree);
DetailManager::beginPrepRender();
buildSceneTree(pBaseState, startObject, startZone, 1, objectMask);
DetailManager::endPrepRender();
PROFILE_END();
// grab the lights...
PROFILE_START(RegisterLights);
mLightManager.sgRegisterGlobalLights(false);
PROFILE_END();
PROFILE_START(TraverseScene);
traverseSceneTree(pBaseState);
PROFILE_END();
delete pBaseState;
PROFILE_END();
}
//---------------------------
static bool takeShot = true;
static void fogTextureShot(TextureHandle fogTexture)
{
GBitmap *fogBitmap = fogTexture.getBitmap();
FileStream fStream;
if(fStream.open("fogTextureTest.png", FileStream::Write) != NULL)
{
fogBitmap->writePNG(fStream);
}
takeShot = false;
}
void SceneGraph::buildFogTexture(SceneState *pState)
{
const Point3F &cp = pState->getCameraPosition();
if (!bool(mFogTexture))
{
mFogTexture = TextureHandle(NULL,new GBitmap(64, 64, false, GBitmap::RGBA), true);
mFogTextureIntensity = mFogTexture;
}
// build the fog texture
TerrainBlock *block = getCurrentTerrain();
if(block)
{
const GridSquare *sq = block->findSquare(TerrainBlock::BlockShift, Point2I(0,0));
GBitmap *fogBitmap = mFogTexture.getBitmap();
const F32 heightRange = getMax(fixedToFloat(sq->maxHeight - sq->minHeight), 5.f);
const F32 heightStep = heightRange / F32(fogBitmap->getHeight());
mHeightOffset = fixedToFloat(sq->minHeight);
mInvHeightRange = 1 / heightRange;
mInvVisibleDistance = 1 / getVisibleDistanceMod();
// inset distance half a texel (to the texel center)
F32 distStart = getVisibleDistanceMod() - (getVisibleDistanceMod() / (fogBitmap->getWidth() * 2));
ColorI fogColor(mFogColor);
if(mHazeArrayDirty)
{
F32 distStep = - getVisibleDistanceMod() / F32(fogBitmap->getWidth());
F32 dist = distStart;
for(U32 i = 0; i < FogTextureDistSize; i++)
{
mHazeArray[i] = pState->getHaze(dist);
F32 prevDist = dist;
dist += distStep;
mDistArray[i] = dist / prevDist;
}
mHazeArrayDirty = false;
}
F32 ht = mHeightOffset + (heightStep / 2) - cp.z;
U32 fc = fogColor.getRGBEndian();
for(U32 j = 0; j < fogBitmap->getHeight(); j++)
{
F32 dist = distStart;
U32 *ptr = (U32 *) fogBitmap->getAddress(0, j);
// fog texture goes from dist = visibleDistance at u = 0 to dist = 0 at u = 1
// makes the math on the texture computation cleaner
F32 fogStart = pState->getFog(dist, ht);
for(U32 i = 0; i < fogBitmap->getWidth(); i++)
{
U32 fog = (U32)((fogStart + mHazeArray[i]) * 255);
fogStart *= mDistArray[i];
if(fog > 255)
fog = 255;
// NOTE: the two platforms want their results in diff orders.
// Mac is typically endian flipped of PC ARGB format (i.e., BGRA).
// above getRGBEndian gets the RGB component in proper order, but
// we still need to mix-in the alpha component in the right location.
#if defined(TORQUE_BIG_ENDIAN) // mac code needs alpha low - don't ask me why. This isn't typical, but code is around PC-memory-order.
*ptr++ = (fc << 8) | (fog & 0xFF);
#else // intel wants alpha high
*ptr++ = fc | (fog << 24);
#endif
}
ht += heightStep;
}
mFogTexture.refresh();
}
// if(takeShot)
// fogTextureShot(mFogTexture);
}
// most of this is work in progress.
void SceneGraph::buildFogTextureSpecial(SceneState *pState)
{
const Point3F &cp = pState->getCameraPosition();
if (!bool(mFogTexture))
{
mFogTexture = TextureHandle(NULL,new GBitmap(64, 64, false, GBitmap::RGBA), true);
mFogTextureIntensity = mFogTexture;
}
// build the fog texture
TerrainBlock *block = getCurrentTerrain();
if(block)
{
GridSquare *sq = block->findSquare(TerrainBlock::BlockShift, Point2I(0,0));
F32 heightRange = fixedToFloat(sq->maxHeight - sq->minHeight);
mHeightOffset = fixedToFloat(sq->minHeight);
mInvHeightRange = 1 / heightRange;
mInvVisibleDistance = 1 / getVisibleDistanceMod();
GBitmap *fogBitmap = mFogTexture.getBitmap();
GBitmap *fogBitmapInten = mFogTextureIntensity.getBitmap();
F32 heightStep = heightRange / F32(fogBitmap->getHeight());
// inset distance half a texel (to the texel center)
F32 distStart = getVisibleDistance() - (getVisibleDistanceMod() / (fogBitmap->getWidth() * 2) );
ColorI fogColor(mFogColor);
ColorF ffogColor = mFogColor;
ColorF array[3];
ffogColor.red *= 255;
ffogColor.green *= 255;
ffogColor.blue *= 255;
U32 numFogs = mNumFogVolumes;
F32 distStep = - getVisibleDistanceMod() / F32(fogBitmap->getWidth());
if(mHazeArrayDirty)
{
F32 dist = distStart;
for(U32 i = 0; i < FogTextureDistSize; i++)
{
mHazeArray[i] = pState->getHaze(dist);
mHazeArrayi[i] = (U32)(mHazeArray[i] * 255);
F32 prevDist = dist;
dist += distStep;
mDistArray[i] = dist / prevDist;
}
mHazeArrayDirty = false;
}
F32 ht = mHeightOffset + (heightStep / 2) - cp.z;
U32 fc = *((U32 *) &fogColor) & 0x00FFFFFF;
for(U32 j = 0; j < fogBitmap->getHeight(); j++)
{
F32 dist = distStart;
U32 *ptr = (U32 *) fogBitmap->getAddress(0, j);
// fog texture goes from dist = visibleDistance at u = 0 to dist = 0 at u = 1
// makes the math on the texture computation cleaner
pState->getFogs(dist, ht, array, numFogs);
switch( numFogs ) { // Changed this if, else if, else if to a switch statement - KB
case 0:
{
for(U32 i = 0; i < fogBitmap->getWidth(); i++)
*ptr++ = fc | (mHazeArrayi[i] << 24);
}
break;
case 1:
{
for(U32 i = 0; i < fogBitmap->getWidth(); i++)
{
F32 bandPct = array[0].alpha;
F32 hazePct = mHazeArray[i];
if(bandPct > 1)
bandPct = 1;
if(bandPct + hazePct > 1)
hazePct = 1 - bandPct;
ColorI c((S32)(hazePct * ffogColor.red + bandPct * (array[0].red * 255)),
(S32)(hazePct * ffogColor.green + bandPct * (array[0].green * 255)),
(S32)(hazePct * ffogColor.blue + bandPct * (array[0].blue * 255)),
(S32)((hazePct + bandPct) * 255));
*ptr++ = c.getARGBEndian();
array[0].alpha *= mDistArray[i];
}
}
break;
case 2:
{
for(U32 i = 0; i < fogBitmap->getWidth(); i++)
{
F32 hazePct = mHazeArray[i];
F32 bandPct0 = array[0].alpha;
F32 bandPct1 = array[1].alpha;
if(bandPct0 > 1)
bandPct0 = 1;
if(bandPct0 + bandPct1 > 1)
bandPct1 = 1 - bandPct0;
if(bandPct1 + bandPct0 + hazePct > 1)
hazePct = 1 - bandPct1 - bandPct0;
ColorI c((S32)(hazePct * ffogColor.red + bandPct0 * array[0].red + bandPct1 * array[1].red),
(S32)(hazePct * ffogColor.green + bandPct0 * array[0].green + bandPct1 * array[1].green),
(S32)(hazePct * ffogColor.blue + bandPct0 * array[0].blue + bandPct1 * array[1].blue),
(S32)((hazePct + bandPct0 + bandPct1) * 255));
*ptr++ = c.getARGBEndian();
array[0].alpha *= mDistArray[i];
}
}
break;
}
ht += heightStep;
}
mFogTexture.refresh();
}
if(takeShot)
fogTextureShot(mFogTexture);
}
//--------------------------------------------------------------------------
void SceneGraph::traverseSceneTree(SceneState* pState)
{
// DMM FIX: only handles trees one deep for now
if (pState->mSubsidiaries.size() != 0) {
for (U32 i = 0; i < pState->mSubsidiaries.size(); i++)
traverseSceneTree(pState->mSubsidiaries[i]);
}
if (pState->mParent != NULL) {
// Comes from a transform portal. Let's see if we need to flip the cull
// Now, the index gives the TransformPortal index in the Parent...
SceneObject* pPortalOwner = pState->mPortalOwner;
U32 portalIndex = pState->mPortalIndex;
AssertFatal(pPortalOwner != NULL && portalIndex != 0xFFFFFFFF,
"Hm, this should never happen. We should always have an owner and an index here");
// Snag our current viewport
RectI viewport;
dglGetViewport(&viewport);
// Save our current projection matrix
glMatrixMode(GL_PROJECTION);
glPushMatrix();
// Ok, open the portal. Opening and closing the portals is a tricky bit of
// work, since we have to get the z values just right. We're going to toss
// the responsibility onto the shoulders of the object that owns the portal.
pPortalOwner->openPortal(portalIndex, pState, pState->mParent);
if (pState->mFlipCull)
glCullFace(GL_FRONT);
// Render the objects in this subsidiary...
pState->renderCurrentImages();
if (pState->mFlipCull)
glCullFace(GL_BACK);
// close the portal
pPortalOwner->closePortal(portalIndex, pState, pState->mParent);
// Restore our original viewport
dglSetViewport(viewport);
// Restore our original projection matrix
glMatrixMode(GL_PROJECTION);
glPopMatrix();
}
else
{
pState->renderCurrentImages();
}
}
//----------------------------------------------------------------------------
struct ScopingInfo {
Point3F scopePoint;
F32 scopeDist;
F32 scopeDistSquared;
const bool* zoneScopeStates;
NetConnection* connection;
};
inline void scopeCallback(SceneObject* obj, ScopingInfo* pInfo)
{
NetConnection* ptr = pInfo->connection;
if (obj->isScopeable()) {
F32 difSq = (obj->getWorldSphere().center - pInfo->scopePoint).lenSquared();
if (difSq < pInfo->scopeDistSquared) {
// Not even close, it's in...
ptr->objectInScope(obj);
} else {
// Check a little more closely...
F32 realDif = mSqrt(difSq);
if (realDif - obj->getWorldSphere().radius < pInfo->scopeDist) {
ptr->objectInScope(obj);
}
}
}
}
void SceneGraph::scopeScene(const Point3F& scopePosition,
const F32 scopeDistance,
NetConnection* netConnection)
{
// Find the start zone...
SceneObject* startObject;
U32 startZone;
findZone(scopePosition, startObject, startZone);
// Search proceeds from the baseObject, and starts in the baseZone.
// General Outline:
// - Traverse up the tree, stopping at either the root, or the last zone manager
// that prevents traversal outside
// - This will set up the array of zone states, either scoped or unscoped.
// loop through all the objects, placing them in scope if they are in
// a scoped zone.
// Objects (in particular, those managers that are part of the initial up
// traversal) keep track of whether or not they have done their scope traversal
// by a key which is the same key used for renderState determination
SceneObject* pTraversalRoot = startObject;
U32 rootZone = startZone;
bool* zoneScopeState = new bool[mCurrZoneEnd];
dMemset(zoneScopeState, 0, sizeof(bool) * mCurrZoneEnd);
smStateKey++;
while (true) {
// Anything that we encounter in our up traversal is scoped
if (pTraversalRoot->isScopeable())
netConnection->objectInScope(pTraversalRoot);
pTraversalRoot->mLastStateKey = smStateKey;
if (pTraversalRoot->scopeObject(scopePosition, scopeDistance,
zoneScopeState)) {
// Continue upwards
if (pTraversalRoot->getNumCurrZones() != 1) {
Con::errorf(ConsoleLogEntry::General,
"Error, must have one and only one zone to be a traversal root. %s has %d",
pTraversalRoot->getName(), pTraversalRoot->getNumCurrZones());
}
rootZone = pTraversalRoot->getCurrZone(0);
pTraversalRoot = getZoneOwner(rootZone);
} else {
// Terminate. This is the traveral root...
break;
}
}
S32 i;
// Note that we start at 1 here rather than 0, since if the root was going to be
// scoped, it would have been scoped in the up traversal rather than at this stage.
// Also, it doesn't have a CurrZone(0), so that's bad... :)
for (i = 1; i < mZoneManagers.size(); i++)
{
if (mZoneManagers[i].obj->mLastStateKey != smStateKey &&
zoneScopeState[mZoneManagers[i].obj->getCurrZone(0)] == true)
{
// Scope the zones in this manager...
mZoneManagers[i].obj->scopeObject(scopePosition, scopeDistance, zoneScopeState);
}
}
ScopingInfo info;
info.scopePoint = scopePosition;
info.scopeDist = scopeDistance;
info.scopeDistSquared = scopeDistance * scopeDistance;
info.zoneScopeStates = zoneScopeState;
info.connection = netConnection;
for (i = 0; i < mCurrZoneEnd; i++) {
// Zip through the zone lists...
if (zoneScopeState[i] == true) {
// Scope zone i...
SceneObjectRef* pList = mZoneLists[i];
SceneObjectRef* pWalk = pList->nextInBin;
while (pWalk != NULL) {
SceneObject* pObject = pWalk->object;
if (pObject->mLastStateKey != smStateKey) {
pObject->mLastStateKey = smStateKey;
scopeCallback(pObject, &info);
}
pWalk = pWalk->nextInBin;
}
}
}
delete [] zoneScopeState;
zoneScopeState = NULL;
}
//------------------------------------------------------------------------------
bool SceneGraph::addObjectToScene(SceneObject* obj)
{
if (obj->getType() & TerrainObjectType) {
// Double check
AssertFatal(dynamic_cast<TerrainBlock*>(obj) != NULL, "Not a terrain, but a terrain type?");
mCurrTerrain = static_cast<TerrainBlock*>(obj);
}
if (obj->getType() & EnvironmentObjectType) {
if (dynamic_cast<Sky*>(obj) != NULL) {
mCurrSky = static_cast<Sky*>(obj);
}
}
if (obj->getType() & DecalManagerObjectType) {
if (dynamic_cast<DecalManager*>(obj) != NULL) {
mCurrDecalManager = static_cast<DecalManager*>(obj);
}
}
if (obj->getType() & WaterObjectType)
{
addToWaterList(obj);
}
return obj->onSceneAdd(this);
}
//------------------------------------------------------------------------------
void SceneGraph::removeObjectFromScene(SceneObject* obj)
{
if (obj->mSceneManager != NULL) {
AssertFatal(obj->mSceneManager == this, "Error, removing from the wrong sceneGraph!");
if (obj->getType() & TerrainObjectType) {
// Double check
AssertFatal(dynamic_cast<TerrainBlock*>(obj) != NULL, "Not a terrain, but a terrain type?");
if (mCurrTerrain == static_cast<TerrainBlock*>(obj))
mCurrTerrain = NULL;
}
if (obj->getType() & EnvironmentObjectType) {
if (dynamic_cast<Sky*>(obj) != NULL && mCurrSky == static_cast<Sky*>(obj))
mCurrSky = NULL;
}
if (obj->getType() & DecalManagerObjectType) {
if (dynamic_cast<DecalManager*>(obj) != NULL && mCurrDecalManager == static_cast<DecalManager*>(obj))
mCurrDecalManager = NULL;
}
if (obj->getType() & WaterObjectType)
{
removeFromWaterList(obj);
}
obj->onSceneRemove();
}
}
//------------------------------------------------------------------------------
void SceneGraph::registerZones(SceneObject* obj, U32 numZones)
{
AssertFatal(alreadyManagingZones(obj) == false, "Error, added zones twice!");
compactZonesCheck();
U32 i;
U32 retVal = mCurrZoneEnd;
mCurrZoneEnd += numZones;
mNumActiveZones += numZones;
mZoneLists.increment(numZones);
for (i = mCurrZoneEnd - numZones; i < mCurrZoneEnd; i++)
{
mZoneLists[i] = new SceneObjectRef;
mZoneLists[i]->object = obj;
mZoneLists[i]->nextInBin = NULL;
mZoneLists[i]->prevInBin = NULL;
mZoneLists[i]->nextInObj = NULL;
}
ZoneManager newEntry;
newEntry.obj = obj;
newEntry.numZones = numZones;
newEntry.zoneRangeStart = retVal;
mZoneManagers.push_back(newEntry);
obj->mZoneRangeStart = retVal;
// Since we now have new zones in this space, we need to rezone any intersecting
// objects. Query the container database to find all intersecting/contained
// objects, and rezone them
Container* pQueryContainer = mIsClient ? &gClientContainer :
&gServerContainer;
// query
SimpleQueryList list;
pQueryContainer->findObjects(obj->mWorldBox, 0xFFFFFFFF, SimpleQueryList::insertionCallback, &list);
// DMM: Horrendously inefficient. We should do the rejection test against
// obj here
for (i = 0; i < list.mList.size(); i++)
{
SceneObject* rezoneObj = list.mList[i];
// Make sure this is actually a SceneObject, is not the zone manager,
// and is added to the scene manager.
if (rezoneObj != NULL && rezoneObj != obj &&
rezoneObj->mSceneManager != NULL)
rezoneObject(rezoneObj);
}
}
//------------------------------------------------------------------------------
void SceneGraph::unregisterZones(SceneObject* obj)
{
AssertFatal(alreadyManagingZones(obj) == true, "Error, not managing any zones!");
// First, let's nuke the lists associated with this object. We can leave the
// horizontal references in the objects in place, they'll be freed before too
// long.
for (U32 i = 0; i < mZoneManagers.size(); i++)
{
if (obj == mZoneManagers[i].obj)
{
AssertFatal(mNumActiveZones >= mZoneManagers[i].numZones, "Too many zones removed");
for (U32 j = mZoneManagers[i].zoneRangeStart;
j < mZoneManagers[i].zoneRangeStart + mZoneManagers[i].numZones; j++)
{
SceneObjectRef* pList = mZoneLists[j];
SceneObjectRef* pWalk = pList->nextInBin;
// We have to tree pList a little differently, since it's not a traditional
// link. We can just delete it at the end...
pList->object = NULL;
delete pList;
mZoneLists[j] = NULL;
while (pWalk)
{
AssertFatal(pWalk->object != NULL, "Error, must have an object!");
SceneObjectRef* pTrash = pWalk;
pWalk = pWalk->nextInBin;
pTrash->nextInBin = pTrash;
pTrash->prevInBin = pTrash;
// Ok, now we need to zip through the list in the object to find
// this and remove it since we aren't doubly linked...
SceneObjectRef** ppRef = &pTrash->object->mZoneRefHead;
bool found = false;
while (*ppRef)
{
if (*ppRef == pTrash)
{
// Remove it
*ppRef = (*ppRef)->nextInObj;
found = true;
pTrash->object = NULL;
pTrash->nextInBin = NULL;
pTrash->prevInBin = NULL;
pTrash->nextInObj = NULL;
pTrash->zone = 0xFFFFFFFF;
freeObjectRef(pTrash);
break;
}
ppRef = &(*ppRef)->nextInObj;
}
AssertFatal(found == true, "Error, should have found that reference!");
}
}
mNumActiveZones -= mZoneManagers[i].numZones;
mZoneManagers.erase(i);
obj->mZoneRangeStart = 0xFFFFFFFF;
// query
if ((mIsClient == true && obj != gClientSceneRoot) ||
(mIsClient == false && obj != gServerSceneRoot))
{
Container* pQueryContainer = mIsClient ? &gClientContainer : &gServerContainer;
SimpleQueryList list;
pQueryContainer->findObjects(obj->mWorldBox, 0xFFFFFFFF, SimpleQueryList::insertionCallback, &list);
for (i = 0; i < list.mList.size(); i++) {
SceneObject* rezoneObj = list.mList[i];
if (rezoneObj != NULL && rezoneObj != obj && rezoneObj->mSceneManager != NULL)
rezoneObject(rezoneObj);
}
}
return;
}
}
compactZonesCheck();
// Other assert already ensured we will terminate properly...
AssertFatal(false, "Error, impossible condition reached!");
}
//------------------------------------------------------------------------------
void SceneGraph::compactZonesCheck()
{
if (mNumActiveZones > (mCurrZoneEnd / 2))
return;
// DMMTODO: Compact zones...
//
}
//------------------------------------------------------------------------------
bool SceneGraph::alreadyManagingZones(SceneObject* obj) const
{
for (U32 i = 0; i < mZoneManagers.size(); i++)
if (obj == mZoneManagers[i].obj)
return true;
return false;
}
//------------------------------------------------------------------------------
void SceneGraph::findZone(const Point3F& p, SceneObject*& owner, U32& zone)
{
// Since there is no zone information maintained by the sceneGraph
// any more, this is quite brain-dead. Maybe fix this? DMM
//
U32 currZone = 0;
SceneObject* currOwner = mZoneManagers[0].obj;
while (true)
{
bool cont = false;
// Loop, but don't consider the root...
for (U32 i = 1; i < mZoneManagers.size(); i++)
{
AssertWarn(mZoneManagers[i].obj->getNumCurrZones() == 1 || (i == 0 && mZoneManagers[i].obj->getNumCurrZones() == 0),
"ZoneManagers are only allowed to belong to one and only one zone!");
if (mZoneManagers[i].obj->getCurrZone(0) == currZone)
{
// Test to see if the point is inside
U32 testZone = mZoneManagers[i].obj->getPointZone(p);
if (testZone != 0)
{
// Point is in this manager, reset, and descend
cont = true;
currZone = testZone;
currOwner = mZoneManagers[i].obj;
break;
}
}
}
// Have we gone as far as we can?
if (cont == false)
break;
}
zone = currZone;
owner = currOwner;
}
//------------------------------------------------------------------------------
void SceneGraph::rezoneObject(SceneObject* obj)
{
PROFILE_START(SceneGraph_rezoneObject);
AssertFatal(obj->mSceneManager != NULL && obj->mSceneManager == this, "Error, bad or no scenemanager here!");
if (obj->mZoneRefHead != NULL)
{
// Remove the object from the zone lists...
SceneObjectRef* walk = obj->mZoneRefHead;
while (walk)
{
SceneObjectRef* remove = walk;
walk = walk->nextInObj;
remove->prevInBin->nextInBin = remove->nextInBin;
if (remove->nextInBin)
remove->nextInBin->prevInBin = remove->prevInBin;
remove->nextInObj = NULL;
remove->nextInBin = NULL;
remove->prevInBin = NULL;
remove->object = NULL;
remove->zone = U32(-1);
freeObjectRef(remove);
}
obj->mZoneRefHead = NULL;
}
U32 numMasterZones = 0;
SceneObject* masterZoneOwners[SceneObject::MaxObjectZones];
U32 masterZoneBuffer[SceneObject::MaxObjectZones];
S32 i;
for (i = S32(mZoneManagers.size()) - 1; i >= 0; i--)
{
// Careful, zone managers are in the list at this point...
if (obj == mZoneManagers[i].obj)
continue;
// Skip if we don't even overlap the world box of the manager.
// And this is not the root zone - we always consider the SceneRoot.
if (!(dynamic_cast<SceneRoot*>(mZoneManagers[i].obj)) && mZoneManagers[i].obj->getWorldBox().isOverlapped(obj->getWorldBox()) == false)
continue;
// We have several possible outcomes here
// 1: Object completely contained in zoneManager
// 2: object overlaps manager. (outside zone is included)
// 3: Object completely contains manager (outside zone not included)
// In case 3, we ignore the possibility that the object resides in
// zones managed by the manager, and we can continue
// In case 1 and 2, we need to query the manager for zones.
// In case 1, we break out of the loop, unless the object is not a
// part of the managers interior zones.
// In case 2, we need to continue querying the database until we
// stop due to one of the above conditions (guaranteed to happen
// when we reach the sceneRoot. (Zone 0)
//
if (!(dynamic_cast<SceneRoot*>(mZoneManagers[i].obj)) && obj->getWorldBox().isContained(mZoneManagers[i].obj->getWorldBox()))
{
// case 3
continue;
}
// Query the zones...
U32 numZones = 0;
U32 zoneBuffer[SceneObject::MaxObjectZones];
bool outsideIncluded = mZoneManagers[i].obj->getOverlappingZones(obj, zoneBuffer, &numZones);
AssertFatal(numZones != 0 || outsideIncluded == true, "Hm, no zones, but not in the outside zone? Impossible!");
// Copy the included zones out
if (numMasterZones + numZones > SceneObject::MaxObjectZones)
Con::errorf(ConsoleLogEntry::General, "Zone Overflow! Object will NOT render correctly. Copying out as many as possible");
numZones = getMin(numZones, SceneObject::MaxObjectZones - numMasterZones);
for (U32 j = 0; j < numZones; j++)
{
masterZoneBuffer[numMasterZones] = zoneBuffer[j];
masterZoneOwners[numMasterZones++] = mZoneManagers[i].obj;
}
if (outsideIncluded == false)
{
// case 3. We can stop the search at this point...
break;
}
else
{
// Case 2. We need to continue searching...
// ...
}
}
// Copy the found zones into the buffer...
AssertFatal(numMasterZones != 0, "Error, no zones found? Should always find root at least.");
obj->mNumCurrZones = numMasterZones;
for (i = 0; i < numMasterZones; i++)
{
// Insert into zone masterZoneBuffer[i]
SceneObjectRef* zoneList = mZoneLists[masterZoneBuffer[i]];
AssertFatal(zoneList != NULL, "Error, no list for this zone!");
SceneObjectRef* newRef = allocateObjectRef();
// Get it into the list
newRef->zone = masterZoneBuffer[i];
newRef->object = obj;
newRef->nextInBin = zoneList->nextInBin;
newRef->prevInBin = zoneList;
if (zoneList->nextInBin)
zoneList->nextInBin->prevInBin = newRef;
zoneList->nextInBin = newRef;
// Now get it into the objects chain...
newRef->nextInObj = obj->mZoneRefHead;
obj->mZoneRefHead = newRef;
}
PROFILE_END();
}
void SceneGraph::zoneInsert(SceneObject* obj)
{
AssertFatal(obj->mNumCurrZones == 0, "Error, already entered into zone list...");
rezoneObject(obj);
if (obj->isManagingZones())
{
// Query the container database to find all intersecting/contained
// objects, and rezone them
SimpleQueryList list;
Container* pQueryContainer = mIsClient ? &gClientContainer : &gServerContainer;
pQueryContainer->findObjects(obj->mWorldBox, 0xFFFFFFFF, SimpleQueryList::insertionCallback, &list);
// DMM: Horrendously inefficient. We should do the rejection test against
// obj here, but the zoneManagers are so infrequently inserted and removed that
// it really doesn't matter...
for (U32 i = 0; i < list.mList.size(); i++)
{
SceneObject* rezoneObj = list.mList[i];
// Make sure this is actually a SceneObject, is not the zone manager,
// and is added to the scene manager.
if (rezoneObj != NULL && rezoneObj != obj &&
rezoneObj->mSceneManager == this)
rezoneObject(rezoneObj);
}
}
}
//------------------------------------------------------------------------------
void SceneGraph::zoneRemove(SceneObject* obj)
{
obj->mNumCurrZones = 0;
// Remove the object from the zone lists...
SceneObjectRef* walk = obj->mZoneRefHead;
while (walk)
{
SceneObjectRef* remove = walk;
walk = walk->nextInObj;
remove->prevInBin->nextInBin = remove->nextInBin;
if (remove->nextInBin)
remove->nextInBin->prevInBin = remove->prevInBin;
remove->nextInObj = NULL;
remove->nextInBin = NULL;
remove->prevInBin = NULL;
remove->object = NULL;
remove->zone = U32(-1);
freeObjectRef(remove);
}
obj->mZoneRefHead = NULL;
}
void SceneGraph::setFogColor(ColorF color)
{
mFogColor = color;
}
void SceneGraph::setVisibleDistance(F32 dist)
{
mHazeArrayDirty = true;
mVisibleDistance = dist;
}
void SceneGraph::setFogDistance(F32 dist)
{
mHazeArrayDirty = true;
mFogDistance = dist;
}
void SceneGraph::setFogVolumes(U32 numFogVolumes, FogVolume *fogVolumes)
{
mNumFogVolumes = getMin(numFogVolumes, U32(MaxFogVolumes));
for(U32 i = 0; i < mNumFogVolumes; i++)
mFogVolumes[i] = fogVolumes[i];
}
void SceneGraph::getWaterObjectList(SimpleQueryList& sql)
{
sql.mList.setSize(mWaterList.size());
for (S32 i = 0; i < mWaterList.size(); i++)
sql.mList[i] = mWaterList[i];
}
void SceneGraph::addToWaterList(SceneObject* obj)
{
#if defined(TORQUE_DEBUG)
for (S32 i = 0; i < mWaterList.size(); i++)
{
AssertFatal(mWaterList[i] != obj, "Error, object already on water list!");
}
#endif
mWaterList.push_back(obj);
}
void SceneGraph::removeFromWaterList(SceneObject* obj)
{
#if defined(TORQUE_DEBUG)
bool found = false;
for (S32 i = 0; i < mWaterList.size(); i++)
{
if (obj == mWaterList[i])
found = true;
}
AssertFatal(found, "Error, object not on water list!");
#endif
for (S32 i = 0; i < mWaterList.size(); i++)
{
if (mWaterList[i] == obj)
{
mWaterList.erase(i);
return;
}
}
}