//----------------------------------------------------------------------------- // 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(obj) != NULL, "Not a terrain, but a terrain type?"); mCurrTerrain = static_cast(obj); } if (obj->getType() & EnvironmentObjectType) { if (dynamic_cast(obj) != NULL) { mCurrSky = static_cast(obj); } } if (obj->getType() & DecalManagerObjectType) { if (dynamic_cast(obj) != NULL) { mCurrDecalManager = static_cast(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(obj) != NULL, "Not a terrain, but a terrain type?"); if (mCurrTerrain == static_cast(obj)) mCurrTerrain = NULL; } if (obj->getType() & EnvironmentObjectType) { if (dynamic_cast(obj) != NULL && mCurrSky == static_cast(obj)) mCurrSky = NULL; } if (obj->getType() & DecalManagerObjectType) { if (dynamic_cast(obj) != NULL && mCurrDecalManager == static_cast(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(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(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; } } }