/*------------------------------------------------------------------------------------- Copyright (c) 2006 John Judnich This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. -------------------------------------------------------------------------------------*/ //PagedGeometry.h //Main source file for the PagedGeometry engine. //------------------------------------------------------------------------------------- #include "PagedGeometry.h" #include "StaticBillboardSet.h" #include #include #include #include using namespace Ogre; using namespace std; namespace Forests { //------------------------------------------------------------------------------------- PagedGeometry::PagedGeometry(Camera* cam, const Real pageSize, Ogre::RenderQueueGroupID queue) : mRenderQueue(queue) { //Setup camera, scene manager, and scene node if (cam) { sceneCam = cam; sceneMgr = sceneCam->getSceneManager(); oldCamPos = sceneCam->getDerivedPosition(); #ifdef PAGEDGEOMETRY_ALTERNATE_COORDSYSTEM rootNode = sceneMgr->getRootSceneNode()->createChildSceneNode(); //Create PagedGeometry's root node #else rootNode = sceneMgr->getRootSceneNode(); #endif } else { sceneCam = NULL; sceneMgr = NULL; rootNode = NULL; oldCamPos = Vector3::ZERO; } lastSceneCam = NULL; lastOldCamPos = Vector3::ZERO; #ifdef PAGEDGEOMETRY_ALTERNATE_COORDSYSTEM //Setup default coordinate system coordinateSystemQuat = Quaternion::IDENTITY; #endif //Init. timer timer.reset(); lastTime = 0; //Setup page size / bounds PagedGeometry::pageSize = pageSize; m_bounds = TBounds(0, 0, 0, 0); //Misc. pageLoader = NULL; geometryAllowedVisible = true; tempdir=""; // empty for current working directory shadersEnabled = true; // enable shaders by default } PagedGeometry::~PagedGeometry() { #ifdef PAGEDGEOMETRY_ALTERNATE_COORDSYSTEM //Remove PagedGeometry's coordinate system node if (rootNode) sceneMgr->destroySceneNode(rootNode); #endif //Remove all page managers and the geometry associated with them removeDetailLevels(); } void PagedGeometry::setTempDir(Ogre::String dir) { tempdir = dir; } void PagedGeometry::setPageLoader(PageLoader *loader) { pageLoader = loader; } void PagedGeometry::setCamera(Camera *cam) { if (cam == NULL){ //Simply set camera to null sceneCam = NULL; } else { if (sceneMgr && cam->getSceneManager() != sceneMgr) OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, "The specified camera is from the wrong SceneManager", "PagedGeometry::setCamera()"); if (cam == lastSceneCam){ //If the cache values for this camera are preserved, use them std::swap(oldCamPos, lastOldCamPos); std::swap(sceneCam, lastSceneCam); } else { lastSceneCam = sceneCam; lastOldCamPos = oldCamPos; sceneCam = cam; } //If sceneMgre is NULL (this only occurs the first time a camera is set), //then set the scene manager (it won't change after this point). if (sceneMgr == NULL) sceneMgr = sceneCam->getSceneManager(); //If rootNode is NULL (this also only occurs the first time a camera is set), //the create a scene node (it won't change after this point) for the coordinate //system translations. if (rootNode == NULL){ #ifdef PAGEDGEOMETRY_ALTERNATE_COORDSYSTEM rootNode = sceneMgr->getRootSceneNode()->createChildSceneNode(); rootNode->setOrientation(coordinateSystemQuat); #else rootNode = sceneMgr->getRootSceneNode(); #endif } } } #ifdef PAGEDGEOMETRY_ALTERNATE_COORDSYSTEM void PagedGeometry::setCoordinateSystem(Vector3 up, Vector3 right) { up.z = -up.z; Vector3 forward = right.crossProduct(up); coordinateSystemQuat = Quaternion(right, up, forward); if (rootNode) rootNode->setOrientation(coordinateSystemQuat); } #endif #ifdef PAGEDGEOMETRY_ALTERNATE_COORDSYSTEM Vector3 PagedGeometry::_convertToLocal(const Vector3 &globalVec) const { assert(getSceneNode()); //Convert from the given global position to the local coordinate system of PagedGeometry's root scene node. return (getSceneNode()->getOrientation().Inverse() * globalVec); } #else //Default coordinate system - no conversion Vector3 PagedGeometry::_convertToLocal(const Vector3 &globalVec) const { return globalVec; } #endif void PagedGeometry::setPageSize(Real size) { if (!managerList.empty()) OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, "PagedGeometry::setPageSize() cannot be called after detail levels have been added. Call removeDetailLevels() first.", "PagedGeometry::setPageSize()"); pageSize = size; } void PagedGeometry::setInfinite() { if (!managerList.empty()) OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, "PagedGeometry::setInfinite() cannot be called after detail levels have been added. Call removeDetailLevels() first.", "PagedGeometry::setInfinite()"); m_bounds = TBounds(0, 0, 0, 0); } void PagedGeometry::setBounds(TBounds bounds) { if (!managerList.empty()) OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, "PagedGeometry::setBounds() cannot be called after detail levels have been added. Call removeDetailLevels() first.", "PagedGeometry::setBounds()"); if (!Math::RealEqual(bounds.width(), bounds.height(), 0.01f)) OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, "Bounds must be square", "PagedGeometry::setBounds()"); if (bounds.width() <= 0 || bounds.height() <=0) OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, "Bounds must have positive width and height", "PagedGeometry::setBounds()"); m_bounds = bounds; } TBounds PagedGeometry::convertAABToTBounds( const Ogre::AxisAlignedBox & aab ) const { Vector3 minimum = _convertToLocal(aab.getMinimum()); Vector3 maximum = _convertToLocal(aab.getMaximum()); return TBounds (minimum.x, minimum.z, maximum.x, maximum.z); } void PagedGeometry::removeDetailLevels() { std::list::iterator it; //Delete all the page managers for (it = managerList.begin(); it != managerList.end(); ++it){ GeometryPageManager *mgr = *it; delete mgr; } //Clear the page manager list managerList.clear(); } void PagedGeometry::update() { //If no camera has been set, then return without doing anything if (sceneCam == NULL) return; //Calculate time since last update unsigned long deltaTime, tmp; tmp = timer.getMilliseconds(); deltaTime = tmp - lastTime; lastTime = tmp; //Get camera position and speed Vector3 camPos = _convertToLocal(sceneCam->getDerivedPosition()); Vector3 camSpeed; //Speed in units-per-millisecond if (deltaTime == 0){ camSpeed.x = 0; camSpeed.y = 0; camSpeed.z = 0; } else { camSpeed = (camPos - oldCamPos) / deltaTime; } oldCamPos = camPos; if (pageLoader != 0){ //Update the PageLoader pageLoader->frameUpdate(); //Update all the page managers bool enableCache = true; std::list::iterator it; GeometryPageManager *prevMgr = NULL; for (it = managerList.begin(); it != managerList.end(); ++it){ GeometryPageManager *mgr = *it; mgr->update(deltaTime, camPos, camSpeed, enableCache, prevMgr); prevMgr = mgr; } } //Update misc. subsystems StaticBillboardSet::updateAll(_convertToLocal(getCamera()->getDerivedDirection())); } void PagedGeometry::reloadGeometry() { assert(pageLoader); std::list::iterator it; for (it = managerList.begin(); it != managerList.end(); ++it){ GeometryPageManager *mgr = *it; mgr->reloadGeometry(); } } void PagedGeometry::reloadGeometryPage(const Vector3 &point, bool forceLoadImmediately) { if (!pageLoader) return; #ifdef PAGEDGEOMETRY_ALTERNATE_COORDSYSTEM point = _convertToLocal(point); #endif std::list::iterator it; for (it = managerList.begin(); it != managerList.end(); ++it) { GeometryPageManager *mgr = *it; mgr->reloadGeometryPage( #ifdef PAGEDGEOMETRY_ALTERNATE_COORDSYSTEM _convertToLocal(point) #else point #endif , forceLoadImmediately ); } } void PagedGeometry::reloadGeometryPages(const Ogre::Vector3 ¢er, Real radius) { if (!pageLoader) return; std::list::iterator it; for (it = managerList.begin(); it != managerList.end(); ++it) { GeometryPageManager *mgr = *it; mgr->reloadGeometryPages( #ifdef PAGEDGEOMETRY_ALTERNATE_COORDSYSTEM _convertToLocal(center) #else center #endif , radius); } } void PagedGeometry::reloadGeometryPages(const TBounds & area) { if (!pageLoader) return; TBounds localArea = area; if (localArea.left > localArea.right) std::swap(localArea.left, localArea.right); if (localArea.top > localArea.bottom) std::swap(localArea.top, localArea.bottom); std::list::iterator it; for (it = managerList.begin(); it != managerList.end(); ++it){ GeometryPageManager *mgr = *it; mgr->reloadGeometryPages(localArea); } } void PagedGeometry::preloadGeometry(const TBounds & area) { if (!pageLoader) return; TBounds localArea = area; if (localArea.left > localArea.right) std::swap(localArea.left, localArea.right); if (localArea.top > localArea.bottom) std::swap(localArea.top, localArea.bottom); std::list::iterator it; for (it = managerList.begin(); it != managerList.end(); ++it){ GeometryPageManager *mgr = *it; mgr->preloadGeometry(localArea); } } void PagedGeometry::resetPreloadedGeometry() { if (!pageLoader) return; std::list::iterator it; for (it = managerList.begin(); it != managerList.end(); ++it){ GeometryPageManager *mgr = *it; mgr->resetPreloadedGeometry(); } } void PagedGeometry::_addDetailLevel(GeometryPageManager *mgr, Real maxRange, Real transitionLength) { //Calculate the near range Real minRange = 0; if (!managerList.empty()){ GeometryPageManager *lastMgr = managerList.back(); minRange = lastMgr->getFarRange(); } //Error check if (maxRange <= minRange){ OGRE_EXCEPT(Exception::ERR_INVALID_STATE, "Closer detail levels must be added before farther ones", "PagedGeometry::addDetailLevel()"); } //Setup the new manager mgr->setNearRange(minRange); mgr->setFarRange(maxRange); mgr->setTransition(transitionLength); managerList.push_back(mgr); } void PagedGeometry::setCustomParam(string entity, string paramName, float paramValue) { setCustomParam(entity + "." + paramName, paramValue); } void PagedGeometry::setCustomParam(string paramName, float paramValue) { customParam[paramName] = paramValue; } float PagedGeometry::getCustomParam(string entity, string paramName, float defaultParamValue) const { return getCustomParam(entity + "." + paramName, defaultParamValue); } float PagedGeometry::getCustomParam(string paramName, float defaultParamValue) const { std::map::const_iterator it; it = customParam.find(paramName); if (it != customParam.end()) { float x = it->second; return x; } else return defaultParamValue; } Ogre::RenderQueueGroupID PagedGeometry::getRenderQueue() const { return mRenderQueue; } //------------------------------------------------------------------------------------- GeometryPageManager::GeometryPageManager(PagedGeometry *mainGeom) : mainGeom(mainGeom) , geomGrid(NULL) , scrollBuffer(NULL) , geomGridX(0) , geomGridZ(0) , cacheTimer(0) // Reset the cache timer { //Use default cache speeds setCacheSpeed(); //No transition default setTransition(0); } GeometryPageManager::~GeometryPageManager() { //Delete GeometryPage's for (int x = 0; x < geomGridX; ++x) for (int z = 0; z < geomGridZ; ++z) delete _getGridPage(x, z); //Deallocate arrays if(geomGrid) delete[] geomGrid; if(scrollBuffer) delete[] scrollBuffer; } void GeometryPageManager::update(unsigned long deltaTime, Vector3 &camPos, Vector3 &camSpeed, bool &enableCache, GeometryPageManager *prevManager) { //-- Cache new geometry pages -- //Cache 1 page ahead of the view ranges const Real cacheDist = farTransDist + mainGeom->getPageSize(); const Real cacheDistSq = cacheDist * cacheDist; //First calculate the general area where the pages will be processed // 0,0 is the left top corner of the bounding box int x1 = Math::Floor(((camPos.x - cacheDist) - gridBounds.left) / mainGeom->getPageSize()); int x2 = Math::Floor(((camPos.x + cacheDist) - gridBounds.left) / mainGeom->getPageSize()); int z1 = Math::Floor(((camPos.z - cacheDist) - gridBounds.top) / mainGeom->getPageSize()); int z2 = Math::Floor(((camPos.z + cacheDist) - gridBounds.top) / mainGeom->getPageSize()); if(scrollBuffer) { //Check if the page grid needs to be scrolled int shiftX = 0, shiftZ = 0; if (x1 < 0) shiftX = x1; else if (x2 >= geomGridX-1) shiftX = x2 - (geomGridX-1); if (z1 < 0) shiftZ = z1; else if (z2 >= geomGridZ-1) shiftZ = z2 - (geomGridZ-1); if (shiftX != 0 || shiftZ != 0) { //Scroll grid _scrollGridPages(shiftX, shiftZ); //Update grid bounds and processing area gridBounds.left += shiftX * mainGeom->getPageSize(); gridBounds.right += shiftX * mainGeom->getPageSize(); gridBounds.top += shiftZ * mainGeom->getPageSize(); gridBounds.bottom += shiftZ * mainGeom->getPageSize(); x1 -= shiftX; x2 -= shiftX; z1 -= shiftZ; z2 -= shiftZ; } } else { // make sure that values are inbounds if(x2 >= geomGridX) x2 = geomGridX - 1; if(z2 >= geomGridZ) z2 = geomGridZ - 1; if (x1 < 0) x1 = 0; if (z1 < 0) z1 = 0; } //Now, in that general area, find what pages are within the cacheDist radius //Pages within the cacheDist radius will be added to the pending block list //to be loaded later, and pages within farDist will be loaded immediately. for (int x = x1; x <= x2; ++x){ for (int z = z1; z <= z2; ++z){ GeometryPage *blk = _getGridPage(x, z); Real dx = camPos.x - blk->_centerPoint.x; Real dz = camPos.z - blk->_centerPoint.z; Real distSq = dx * dx + dz * dz; //If the page is in the cache radius... if (distSq <= cacheDistSq){ //If the block hasn't been loaded yet, it should be if (blk->_loaded == false){ //Test if the block's distance is between nearDist and farDist if (distSq >= nearDistSq && distSq < farTransDistSq){ //If so, load the geometry immediately _loadPage(blk); loadedList.insert(blk); //And remove it from the pending list if necessary if (blk->_pending){ pendingList.erase(blk); blk->_pending = false; } } else { //Otherwise, add it to the pending geometry list (if not already) //Pages in then pending list will be loaded later (see below) if (!blk->_pending){ pendingList.insert(blk); blk->_pending = true; } } } else { //Set the inactive time to 0 (since the page is active). This //must be done in order to keep it from expiring (and deleted). //This way, blocks not in the cache radius won't have their //inactivity clock reset, and they will expire in a few seconds. blk->_inactiveTime = 0; } } } } //Calculate cache speeds based on camera speed. This is important to keep the cache //process running smooth, because if the cache can't keep up with the camera, the //immediately visible pages will be forced to load instantly, which can cause //noticeable and sudden stuttering. The cache system results in smoother performance //because it smooths the loading tasks out across multiple frames. For example, //instead of loading 10+ blocks every 2 seconds, the cache would load 1 block every //200 milliseconds. Real speed = Math::Sqrt(camSpeed.x * camSpeed.x + camSpeed.z * camSpeed.z); unsigned long cacheInterval; if (speed == 0) cacheInterval = maxCacheInterval; else { cacheInterval = (mainGeom->getPageSize() * 0.8f) / (speed * pendingList.size()); if (cacheInterval > maxCacheInterval) cacheInterval = maxCacheInterval; } TPGeometryPages::iterator i1, i2; //Now load a single geometry page periodically, based on the cacheInterval cacheTimer += deltaTime; if (cacheTimer >= cacheInterval && enableCache){ //Find a block to be loaded from the pending list i1 = pendingList.begin(); i2 = pendingList.end(); while (i1 != i2) { GeometryPage *blk = *i1; //Remove it from the pending list i1 = pendingList.erase(i1); blk->_pending = false; //If it's within the geometry cache radius, load it and break out of the loop Real dx = camPos.x - blk->_centerPoint.x; Real dz = camPos.z - blk->_centerPoint.z; Real distSq = dx * dx + dz * dz; if (distSq <= cacheDistSq){ _loadPage(blk); loadedList.insert(blk); enableCache = false; break; } //Otherwise this will keep looping until an unloaded page is found } //Reset the cache timer cacheTimer = 0; } //-- Update existing geometry and impostors -- //Loop through each loaded geometry block i1 = loadedList.begin(); i2 = loadedList.end(); //Real fadeBeginDistSq = farDistSq - Math::Sqr(mainGeom->getPageSize() * 1.4142f); Real halfPageSize = mainGeom->getPageSize() * 0.5f; while (i1 != i2) { GeometryPage *blk = *i1; //If the geometry has expired... if (blk->_inactiveTime >= inactivePageLife){ if (!blk->_keepLoaded) { //Unload it _unloadPage(blk); i1 = loadedList.erase(i1); } else { //This page needs to be kept loaded indefinitely, so don't unload it blk->_inactiveTime = 0; ++i1; } } else { //Update it's visibility/fade status based on it's distance from the camera bool visible = false; Real dx = camPos.x - blk->_centerPoint.x; Real dz = camPos.z - blk->_centerPoint.z; Real distSq = dx * dx + dz * dz; Real overlap = 0, tmp; tmp = blk->_trueBounds.getMaximum().x - halfPageSize; if (tmp > overlap) overlap = tmp; tmp = blk->_trueBounds.getMaximum().z - halfPageSize; if (tmp > overlap) overlap = tmp; tmp = blk->_trueBounds.getMinimum().x + halfPageSize; if (tmp > overlap) overlap = tmp; tmp = blk->_trueBounds.getMinimum().z + halfPageSize; if (tmp > overlap) overlap = tmp; Real pageLengthSq = Math::Sqr((mainGeom->getPageSize() + overlap) * 1.41421356f); if (distSq + pageLengthSq >= nearDistSq && distSq - pageLengthSq < farTransDistSq){ //Fade the page when transitioning bool enable = false; Real fadeNear = 0; Real fadeFar = 0; if (fadeEnabled && distSq + pageLengthSq >= farDistSq){ //Fade in visible = true; enable = true; fadeNear = farDist; fadeFar = farTransDist; } else if (prevManager && prevManager->fadeEnabled && (distSq - pageLengthSq < prevManager->farTransDistSq)){ //Fade out visible = true; enable = true; fadeNear = prevManager->farDist + (prevManager->farTransDist - prevManager->farDist) * 0.5f; //This causes geometry to fade out faster than it fades in, avoiding a state where a transition appears semitransparent fadeFar = prevManager->farDist; } //Apply fade settings if (enable != blk->_fadeEnable){ blk->setFade(enable, fadeNear, fadeFar); blk->_fadeEnable = enable; } } //Non-fade visibility if (distSq >= nearDistSq && distSq < farDistSq) visible = true; //Hide all? if (!mainGeom->getVisible()) visible = false; //Update visibility if (visible){ //Show the page if it isn't visible if (blk->_visible != true){ blk->setVisible(true); blk->_visible = true; } } else { //Hide the page if it's not already if (blk->_visible != false){ blk->setVisible(false); blk->_visible = false; } } //And update it blk->update(); ++i1; } //Increment the inactivity timer for the geometry blk->_inactiveTime += deltaTime; } } //Clears all GeometryPage's void GeometryPageManager::reloadGeometry() { TPGeometryPages::iterator it; for (it = loadedList.begin(); it != loadedList.end(); ++it) { GeometryPage *page = *it; _unloadPageDelayed(page); page->_pending = true; pendingList.insert(page); } loadedList.clear(); } //Clears a single page (which contains the given point) void GeometryPageManager::reloadGeometryPage(const Vector3 &point, bool forceLoadImmediately) { //Determine which grid block contains the given points const int x = Math::Floor(geomGridX * (point.x - gridBounds.left) / gridBounds.width()); const int z = Math::Floor(geomGridZ * (point.z - gridBounds.top) / gridBounds.height()); //Unload the grid block if it's in the grid area, and is loaded if (x >= 0 && z >= 0 && x < geomGridX && z < geomGridZ){ GeometryPage *page = _getGridPage(x, z); if (page->_loaded){ _unloadPageDelayed(page); page->_pending = true; pendingList.insert(page); } if (forceLoadImmediately) { //Load the geometry immediately _loadPage(page); loadedList.insert(page); //And remove it from the pending list if necessary if (page->_pending){ pendingList.erase(page); page->_pending = false; } } } } //Clears pages within the radius void GeometryPageManager::reloadGeometryPages(const Vector3 ¢er, Real radius) { //First calculate a square boundary to eliminate the search space TBounds area(center.x - radius, center.z - radius, center.x + radius, center.z + radius); //Determine which grid block contains the top-left corner int x1 = Math::Floor(geomGridX * (area.left - gridBounds.left) / gridBounds.width()); int z1 = Math::Floor(geomGridZ * (area.top - gridBounds.top) / gridBounds.height()); if (x1 < 0) x1 = 0; else if (x1 > geomGridX-1) x1 = geomGridX-1; if (z1 < 0) z1 = 0; else if (z1 > geomGridZ-1) z1 = geomGridZ-1; //...and the bottom right int x2 = Math::Floor(geomGridX * (area.right - gridBounds.left) / gridBounds.width()); int z2 = Math::Floor(geomGridZ * (area.bottom - gridBounds.top) / gridBounds.height()); if (x2 < 0) x2 = 0; else if (x2 > geomGridX-1) x2 = geomGridX-1; if (z2 < 0) z2 = 0; else if (z2 > geomGridZ-1) z2 = geomGridZ-1; //Scan all the grid blocks in the region //Real radiusSq = radius * radius; for (int x = x1; x <= x2; ++x) { for (int z = z1; z <= z2; ++z) { GeometryPage *page = _getGridPage(x, z); if (page->_loaded){ Vector3 pos = page->getCenterPoint(); Real distX = (pos.x - center.x), distZ = (pos.z - center.z); Real distSq = distX * distX + distZ * distZ; if (distSq <= radius) { _unloadPageDelayed(page); page->_pending = true; pendingList.insert(page); } } } } } //Clears pages within the bounds void GeometryPageManager::reloadGeometryPages(const TBounds & area) { //Determine which grid block contains the top-left corner int x1 = Math::Floor(geomGridX * (area.left - gridBounds.left) / gridBounds.width()); int z1 = Math::Floor(geomGridZ * (area.top - gridBounds.top) / gridBounds.height()); if (x1 < 0) x1 = 0; else if (x1 > geomGridX-1) x1 = geomGridX-1; if (z1 < 0) z1 = 0; else if (z1 > geomGridZ-1) z1 = geomGridZ-1; //...and the bottom right int x2 = Math::Floor(geomGridX * (area.right - gridBounds.left) / gridBounds.width()); int z2 = Math::Floor(geomGridZ * (area.bottom - gridBounds.top) / gridBounds.height()); if (x2 < 0) x2 = 0; else if (x2 > geomGridX-1) x2 = geomGridX-1; if (z2 < 0) z2 = 0; else if (z2 > geomGridZ-1) z2 = geomGridZ-1; //Unload the grid blocks for (int x = x1; x <= x2; ++x) { for (int z = z1; z <= z2; ++z) { GeometryPage *page = _getGridPage(x, z); if (page->_loaded){ _unloadPageDelayed(page); page->_pending = true; pendingList.insert(page); } } } } //Loads pages within viewing range of the bounds and keeps them loaded void GeometryPageManager::preloadGeometry(const TBounds & area) { TBounds loadarea; loadarea.left = area.left - farDist; loadarea.right = area.right + farDist; loadarea.top = area.top - farDist; loadarea.bottom = area.bottom + farDist; //Determine which grid block contains the top-left corner int x1 = Math::Floor(geomGridX * (loadarea.left - gridBounds.left) / gridBounds.width()); int z1 = Math::Floor(geomGridZ * (loadarea.top - gridBounds.top) / gridBounds.height()); if (x1 < 0) x1 = 0; else if (x1 > geomGridX-1) x1 = geomGridX-1; if (z1 < 0) z1 = 0; else if (z1 > geomGridZ-1) z1 = geomGridZ-1; //...and the bottom right int x2 = Math::Floor(geomGridX * (loadarea.right - gridBounds.left) / gridBounds.width()); int z2 = Math::Floor(geomGridZ * (loadarea.bottom - gridBounds.top) / gridBounds.height()); if (x2 < 0) x2 = 0; else if (x2 > geomGridX-1) x2 = geomGridX-1; if (z2 < 0) z2 = 0; else if (z2 > geomGridZ-1) z2 = geomGridZ-1; //Preload the grid blocks for (int x = x1; x <= x2; ++x) { for (int z = z1; z <= z2; ++z) { GeometryPage *page = _getGridPage(x, z); //If the page isn't loaded if (!page->_loaded){ //Load the geometry immediately _loadPage(page); loadedList.insert(page); //And remove it from the pending list if necessary if (page->_pending){ pendingList.erase(page); page->_pending = false; } } //Flag the page so it won't expire and be deleted in a few seconds if //it's currently not in the viewing range. page->_keepLoaded = true; } } } void GeometryPageManager::resetPreloadedGeometry() { //Set all grid blocks' _keepLoaded flag to false for (int x = 0; x < geomGridX; ++x) { for (int z = 0; z < geomGridZ; ++z) { GeometryPage *page = _getGridPage(x, z); page->_keepLoaded = true; } } } //Loads the given page of geometry immediately //Note: _loadPage() does add the page to loadedList, so that will have to be done manually void GeometryPageManager::_loadPage(GeometryPage *page) { //Calculate page info PageInfo info; Real halfPageSize = mainGeom->getPageSize() * 0.5f; info.bounds.left = page->_centerPoint.x - halfPageSize; info.bounds.right = page->_centerPoint.x + halfPageSize; info.bounds.top = page->_centerPoint.z - halfPageSize; info.bounds.bottom = page->_centerPoint.z + halfPageSize; info.centerPoint = page->_centerPoint; info.xIndex = page->_xIndex; info.zIndex = page->_zIndex; info.userData = page->_userData; //Check if the page is prepared. If not, should stop now (and rely on the page being loaded again when the background data has been prepared). if (!mainGeom->getPageLoader()->preparePage(info)) { page->_inactiveTime = 0; page->_loaded = true; return; } //Check if page needs unloading (if a delayed unload has been issued) if (page->_needsUnload){ page->removeEntities(); mainGeom->getPageLoader()->unloadPage(info); page->_userData = 0; page->_needsUnload = false; page->clearBoundingBox(); } //Load the page page->setRegion(info.bounds.left, info.bounds.top, info.bounds.right, info.bounds.bottom); mainGeom->getPageLoader()->geomPage = page; mainGeom->getPageLoader()->loadPage(info); page->_userData = info.userData; page->build(); page->setVisible(page->_visible); page->_inactiveTime = 0; page->_loaded = true; page->_fadeEnable = false; } //Unloads the given page of geometry immediately //Note: _unloadPage() does not remove the page from loadedList, so that will have to be done manually void GeometryPageManager::_unloadPage(GeometryPage *page) { //Calculate boundaries to unload PageInfo info; Real halfPageSize = mainGeom->getPageSize() * 0.5f; info.bounds.left = page->_centerPoint.x - halfPageSize; info.bounds.right = page->_centerPoint.x + halfPageSize; info.bounds.top = page->_centerPoint.z - halfPageSize; info.bounds.bottom = page->_centerPoint.z + halfPageSize; info.centerPoint = page->_centerPoint; info.xIndex = page->_xIndex; info.zIndex = page->_zIndex; info.userData = page->_userData; //Unload the page page->removeEntities(); mainGeom->getPageLoader()->unloadPage(info); page->_userData = 0; page->_needsUnload = false; page->clearBoundingBox(); page->_inactiveTime = 0; page->_loaded = false; page->_fadeEnable = false; } //"Virtually" unloads the given page of geometry. In reality it is unloaded during the next load. //Note: _unloadPageDelayed() does not remove the page from loadedList, so that will have to be done manually void GeometryPageManager::_unloadPageDelayed(GeometryPage *page) { page->_needsUnload = true; page->_loaded = false; } //Scrolls pages in the grid by the given amount void GeometryPageManager::_scrollGridPages(int shiftX, int shiftZ) { //Check if the camera moved completely out of the grid if (shiftX > geomGridX || shiftX < -geomGridX || shiftZ > geomGridZ || shiftZ < -geomGridZ){ //If so, just reload all the tiles (reloading is accomplished by unloading - loading is automatic) for (int x = 0; x < geomGridX; ++x){ for (int z = 0; z < geomGridZ; ++z){ GeometryPage *page = _getGridPage(x, z); if (page->_loaded){ page->_keepLoaded = false; _unloadPage(page); loadedList.erase(page); } page->_centerPoint.x += shiftX * mainGeom->getPageSize(); page->_centerPoint.z += shiftZ * mainGeom->getPageSize(); page->_xIndex += shiftX; page->_zIndex += shiftZ; } } } else { //If not, scroll the grid by the X and Y values //Scroll horizontally (X) if (shiftX > 0){ for (int z = 0; z < geomGridZ; ++z){ //Temporarily store off-shifted pages first for (int x = 0; x < shiftX; ++x){ GeometryPage *page = _getGridPage(x, z); if (page->_loaded){ page->_keepLoaded = false; _unloadPageDelayed(page); loadedList.erase(page); } scrollBuffer[x] = page; } //Shift left int shiftedMidpoint = geomGridX - shiftX; for (int x = 0; x < shiftedMidpoint; ++x) _setGridPage(x, z, _getGridPage(x + shiftX, z)); //Rotate temporary pages around to other side of grid for (int x = 0; x < shiftX; ++x){ scrollBuffer[x]->_centerPoint.x += geomGridX * mainGeom->getPageSize(); scrollBuffer[x]->_xIndex += geomGridX; _setGridPage(x + shiftedMidpoint, z, scrollBuffer[x]); } } } else if (shiftX < 0) { for (int z = 0; z < geomGridZ; ++z){ //Temporarily store off-shifted pages first int initialMidpoint = geomGridX + shiftX; for (int x = initialMidpoint; x < geomGridX; ++x){ GeometryPage *page = _getGridPage(x, z); if (page->_loaded){ page->_keepLoaded = false; _unloadPageDelayed(page); loadedList.erase(page); } scrollBuffer[x - initialMidpoint] = page; } //Shift right for (int x = geomGridX-1; x >= -shiftX; x--) _setGridPage(x, z, _getGridPage(x + shiftX, z)); //Rotate temporary pages around to other side of grid for (int x = 0; x < -shiftX; ++x){ scrollBuffer[x]->_centerPoint.x -= geomGridX * mainGeom->getPageSize(); scrollBuffer[x]->_xIndex -= geomGridX; _setGridPage(x, z, scrollBuffer[x]); } } } //Scroll vertically (Z) if (shiftZ > 0){ for (int x = 0; x < geomGridX; ++x){ //Temporarily store off-shifted pages first for (int z = 0; z < shiftZ; ++z){ GeometryPage *page = _getGridPage(x, z); if (page->_loaded){ page->_keepLoaded = false; _unloadPageDelayed(page); loadedList.erase(page); } scrollBuffer[z] = page; } //Shift left int shiftedMidpoint = geomGridZ - shiftZ; for (int z = 0; z < shiftedMidpoint; ++z) _setGridPage(x, z, _getGridPage(x, z + shiftZ)); //Rotate temporary pages around to other side of grid for (int z = 0; z < shiftZ; ++z){ scrollBuffer[z]->_centerPoint.z += geomGridZ * mainGeom->getPageSize(); scrollBuffer[z]->_zIndex += geomGridZ; _setGridPage(x, z + shiftedMidpoint, scrollBuffer[z]); } } } else if (shiftZ < 0) { for (int x = 0; x < geomGridX; ++x){ //Temporarily store off-shifted pages first int initialMidpoint = geomGridZ + shiftZ; for (int z = initialMidpoint; z < geomGridZ; ++z){ GeometryPage *page = _getGridPage(x, z); if (page->_loaded){ page->_keepLoaded = false; _unloadPageDelayed(page); loadedList.erase(page); } scrollBuffer[z - initialMidpoint] = page; } //Shift right for (int z = geomGridZ-1; z >= -shiftZ; z--) _setGridPage(x, z, _getGridPage(x, z + shiftZ)); //Rotate temporary pages around to other side of grid for (int z = 0; z < -shiftZ; ++z){ scrollBuffer[z]->_centerPoint.z -= geomGridZ * mainGeom->getPageSize(); scrollBuffer[z]->_zIndex -= geomGridZ; _setGridPage(x, z, scrollBuffer[z]); } } } } } GeometryPage::GeometryPage() { _visible = _fadeEnable = _pending = _loaded = _needsUnload = _keepLoaded = false; _trueBoundsUndefined = true; _inactiveTime = 0; _xIndex = _zIndex = 0; _centerPoint = Ogre::Vector3::ZERO; _userData = NULL; mHasQueryFlag = false; mQueryFlag = 0; } void GeometryPage::addEntityToBoundingBox(Ogre::MovableObject *ent, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation, const Ogre::Vector3 &scale) { //Update bounding box Ogre::Matrix4 mat(rotation); mat.setScale(scale); Ogre::AxisAlignedBox entBounds = ent->getBoundingBox(); entBounds.transform(mat); Ogre::Vector3 relPosition = position - _centerPoint; if (_trueBoundsUndefined){ _trueBounds.setMinimum(entBounds.getMinimum() + relPosition); _trueBounds.setMaximum(entBounds.getMaximum() + relPosition); _trueBoundsUndefined = false; } else { Ogre::Vector3 min = _trueBounds.getMinimum(); Ogre::Vector3 max = _trueBounds.getMaximum(); min.makeFloor(entBounds.getMinimum() + relPosition); max.makeCeil(entBounds.getMaximum() + relPosition); _trueBounds.setMinimum(min); _trueBounds.setMaximum(max); } } const AxisAlignedBox &GeometryPage::getBoundingBox() { return _trueBounds; } void GeometryPage::clearBoundingBox() { _trueBounds = AxisAlignedBox(0, 0, 0, 0, 0, 0); _trueBoundsUndefined = true; } }