diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 31ac64e9ec..a9f3d24e29 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -21,7 +21,7 @@ add_openmw_dir (mwrender actors objects renderingmanager animation rotatecontroller sky npcanimation vismask creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation - renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager + renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging ) add_openmw_dir (mwinput diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp new file mode 100644 index 0000000000..6453889bcf --- /dev/null +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -0,0 +1,302 @@ +#include "objectpaging.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "apps/openmw/mwworld/esmstore.hpp" +#include "apps/openmw/mwbase/environment.hpp" +#include "apps/openmw/mwbase/world.hpp" + +#include "vismask.hpp" + +namespace MWRender +{ + + bool typeFilter(int type, bool far) + { + switch (type) + { + case ESM::REC_STAT: + case ESM::REC_ACTI: + case ESM::REC_DOOR: + return true; + case ESM::REC_CONT: + return far ? false : true; + default: + return false; + } + } + + const std::string& getModel(int type, const std::string& id, const MWWorld::ESMStore& store) + { + switch (type) + { + case ESM::REC_STAT: + return store.get().searchStatic(id)->mModel; + case ESM::REC_ACTI: + return store.get().searchStatic(id)->mModel; + case ESM::REC_DOOR: + return store.get().searchStatic(id)->mModel; + case ESM::REC_CONT: + return store.get().searchStatic(id)->mModel; + default: throw std::exception(); + } + } + + osg::ref_ptr ObjectPaging::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint) + { + if (!far)return nullptr; + ChunkId id = std::make_tuple(center, size); + + osg::ref_ptr obj = mCache->getRefFromObjectCache(id); + if (obj) + return obj->asNode(); + else + { + osg::ref_ptr node = createChunk(size, center, viewPoint); + mCache->addEntryToObjectCache(id, node.get()); + return node; + } + } + + class CanOptimizeCallback : public SceneUtil::Optimizer::IsOperationPermissibleForObjectCallback + { + public: + virtual bool isOperationPermissibleForObjectImplementation(const SceneUtil::Optimizer* optimizer, const osg::Drawable* node,unsigned int option) const + { + return true; + } + virtual bool isOperationPermissibleForObjectImplementation(const SceneUtil::Optimizer* optimizer, const osg::Node* node,unsigned int option) const + { + return true; + } + }; + + class CopyOp : public osg::CopyOp + { + public: + CopyOp() : mDistance(0.f) { + setCopyFlags(osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES + #if OSG_MIN_VERSION_REQUIRED(3,5,6) + |osg::CopyOp::DEEP_COPY_ARRAYS|osg::CopyOp::DEEP_COPY_PRIMITIVES // damned vbogarbage racing + #endif + ); + } + + float mDistance; + + virtual osg::Node* operator() (const osg::Node* node) const + { + if (dynamic_cast(node)) + return nullptr; + if (dynamic_cast(node)) + return nullptr; + + if (const osg::Switch* sw = node->asSwitch()) + { + osg::Group* n = new osg::Group; + for (unsigned int i=0; igetNumChildren(); ++i) + if (sw->getValue(i)) + n->addChild(operator()(sw->getChild(i))); + n->setDataVariance(osg::Object::STATIC); + return n; + } + if (const osg::LOD* lod = dynamic_cast(node)) + { + osg::Group* n = new osg::Group; + for (unsigned int i=0; igetNumChildren(); ++i) + if (lod->getMinRange(i) <= mDistance && mDistance < lod->getMaxRange(i)) + n->addChild(operator()(lod->getChild(i))); + n->setDataVariance(osg::Object::STATIC); + return n; + } + + osg::Node* n = osg::CopyOp::operator()(node); + if (n) { + n->setDataVariance(osg::Object::STATIC); + n->setUserDataContainer(nullptr); + n->setName(""); + } + return n; + } + virtual osg::Drawable* operator() (const osg::Drawable* drawable) const + { + if (dynamic_cast(drawable)) + return nullptr; + + if (const SceneUtil::RigGeometry* rig = dynamic_cast(drawable)) + return osg::CopyOp::operator()(rig->getSourceGeometry()); + if (const SceneUtil::MorphGeometry* morph = dynamic_cast(drawable)) + return osg::CopyOp::operator()(morph->getSourceGeometry()); + + return osg::CopyOp::operator()(drawable); + } + virtual osg::Callback* operator() (const osg::Callback* callback) const + { + return nullptr; + } + }; + + ObjectPaging::ObjectPaging(Resource::SceneManager* sceneManager) + : GenericResourceManager(nullptr) + , mSceneManager(sceneManager) + { + mMergeGeometry = Settings::Manager::getBool("object paging merge geometry", "Terrain"); + mMinSize = Settings::Manager::getFloat("object paging min size", "Terrain"); + } + + osg::ref_ptr ObjectPaging::createChunk(float size, const osg::Vec2f& center, const osg::Vec3f& viewPoint) + { + osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size/2.f), std::floor(center.y() - size/2.f)); + + osg::ref_ptr group = new osg::Group; + + osg::Vec3f worldCenter = osg::Vec3f(center.x(), center.y(), 0)*ESM::Land::REAL_SIZE; + osg::Vec3f relativeViewPoint = viewPoint - worldCenter; + + std::vector refs; + std::vector esm; + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + + for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX) + { + for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY) + { + const ESM::Cell* cell = store.get().searchStatic(cellX, cellY); + if (!cell) continue; + for (size_t i=0; imContextList.size(); ++i) + { + try + { + unsigned int index = cell->mContextList.at(i).index; + if (esm.size()<=index) + esm.resize(index+1); + cell->restore(esm[index], i); + ESM::CellRef ref; + ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; + bool deleted = false; + while(cell->getNextRef(esm[index], ref, deleted)) + { + if (std::find(cell->mMovedRefs.begin(), cell->mMovedRefs.end(), ref.mRefNum) != cell->mMovedRefs.end()) continue; + int type = store.findStatic(Misc::StringUtils::lowerCase(ref.mRefID)); + if (!typeFilter(type,size>=2)) continue; + if (deleted) continue; + refs.push_back(ref); + } + } + catch (std::exception& e) + { + continue; + } + } + for (ESM::CellRefTracker::const_iterator it = cell->mLeasedRefs.begin(); it != cell->mLeasedRefs.end(); ++it) + { + ESM::CellRef ref = it->first; + bool deleted = it->second; + if (deleted) continue; + int type = store.findStatic(Misc::StringUtils::lowerCase(ref.mRefID)); + if (!typeFilter(type,size>=2)) continue; + refs.push_back(ref); + } + } + } + + osg::Vec2f minBound = (center - osg::Vec2f(size/2.f, size/2.f)); + osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f)); + for (const ESM::CellRef& ref : refs) + { + std::string id = Misc::StringUtils::lowerCase(ref.mRefID); + if (id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker") + continue; // marker objects that have a hardcoded function in the game logic, should be hidden from the player + + int type = store.findStatic(id); + std::string model = "meshes/" + getModel(type, id, store); +/* + bool useAnim = type != ESM::REC_STAT; + if (useAnim) + model = Misc::ResourceHelpers::correctActorModelPath(model, mSceneManager->getVFS()); +*/ + if (model.empty()) continue; + + osg::Vec3f pos = ref.mPos.asVec3(); + if (size < 1.f) + { + osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE; + cellPos.x() = std::max(cellPos.x(), std::floor(minBound.x())); + cellPos.x() = std::min(cellPos.x(), std::ceil(maxBound.x())); + cellPos.y() = std::max(cellPos.y(), std::floor(minBound.y())); + cellPos.y() = std::min(cellPos.y(), std::ceil(maxBound.y())); + if (cellPos.x() < minBound.x() || cellPos.x() > maxBound.x() || cellPos.y() < minBound.y() || cellPos.y() > maxBound.y()) + continue; + } + + osg::ref_ptr cnode = mSceneManager->getTemplate(model, false); + + float d = (viewPoint - pos).length(); + + if (cnode->getBound().radius() * ref.mScale < d*mMinSize) + continue; + + CopyOp co = CopyOp(); + co.mDistance = d; + osg::ref_ptr node = osg::clone(cnode.get(), co); + node->setUserDataContainer(nullptr); + + osg::Matrixf matrix; + matrix.preMultTranslate(pos - worldCenter); + matrix.preMultRotate( osg::Quat(ref.mPos.rot[2], osg::Vec3f(0,0,-1)) * + osg::Quat(ref.mPos.rot[1], osg::Vec3f(0,-1,0)) * + osg::Quat(ref.mPos.rot[0], osg::Vec3f(-1,0,0)) ); + matrix.preMultScale(osg::Vec3f(ref.mScale, ref.mScale, ref.mScale)); + osg::ref_ptr trans = new osg::MatrixTransform(matrix); + trans->addChild(node); + trans->setDataVariance(osg::Object::STATIC); + + group->addChild(trans); + } + + if (mMergeGeometry) + { + SceneUtil::Optimizer optimizer; + if ((relativeViewPoint - group->getBound().center()).length2() > group->getBound().radius2()) + { + optimizer.setViewPoint(relativeViewPoint); + optimizer.setMergeAlphaBlending(true); + } + optimizer.setIsOperationPermissibleForObjectCallback(new CanOptimizeCallback); + unsigned int options = SceneUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS|SceneUtil::Optimizer::REMOVE_REDUNDANT_NODES|SceneUtil::Optimizer::MERGE_GEOMETRY; + optimizer.optimize(group, options); + } + + auto ico = mSceneManager->getIncrementalCompileOperation(); + if (ico) ico->add(group); + else group->getBound(); + + group->setNodeMask(Mask_Static); + + return group; + } + + unsigned int ObjectPaging::getNodeMask() + { + return Mask_Static; + } + +} diff --git a/apps/openmw/mwrender/objectpaging.hpp b/apps/openmw/mwrender/objectpaging.hpp new file mode 100644 index 0000000000..6d66d078df --- /dev/null +++ b/apps/openmw/mwrender/objectpaging.hpp @@ -0,0 +1,44 @@ +#ifndef OPENMW_COMPONENTS_ESMPAGING_CHUNKMANAGER_H +#define OPENMW_COMPONENTS_ESMPAGING_CHUNKMANAGER_H + +#include +#include +#include + +namespace Resource +{ + class SceneManager; +} +namespace MWWorld +{ + class ESMStore; +} + +namespace MWRender +{ + + typedef std::tuple ChunkId; // Center, Size + + class ObjectPaging : public Resource::GenericResourceManager, public Terrain::QuadTreeWorld::ChunkManager + { + public: + ObjectPaging(Resource::SceneManager* sceneManager); + ~ObjectPaging() = default; + + osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint) override; + + osg::ref_ptr createChunk(float size, const osg::Vec2f& center, const osg::Vec3f& viewPoint); + + virtual void setExpiryDelay(double expiryDelay) override { mExpiryDelay = 0.5f; } + + virtual unsigned int getNodeMask() override; + + private: + Resource::SceneManager* mSceneManager; + bool mMergeGeometry; + float mMinSize; + }; + +} + +#endif diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 462f9fbb6e..9c92da767a 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -70,6 +70,8 @@ #include "actorspaths.hpp" #include "recastmesh.hpp" #include "fogmanager.hpp" +#include "objectpaging.hpp" + namespace MWRender { @@ -286,6 +288,12 @@ namespace MWRender mTerrain.reset(new Terrain::QuadTreeWorld( sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug, compMapResolution, compMapLevel, lodFactor, vertexLodMod, maxCompGeometrySize)); + if (Settings::Manager::getBool("object paging", "Terrain")) + { + mObjectPaging.reset(new ObjectPaging(mResourceSystem->getSceneManager())); + static_cast(mTerrain.get())->addChunkManager(mObjectPaging.get()); + mResourceSystem->addResourceManager(mObjectPaging.get()); + } } else mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug)); @@ -1467,4 +1475,9 @@ namespace MWRender mRecastMesh->update(mNavigator.getRecastMeshTiles(), mNavigator.getSettings()); } + + void RenderingManager::setActiveGrid(const osg::Vec4i &grid) + { + mTerrain->setActiveGrid(grid); + } } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 13f09b359e..dff76f95ec 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -84,6 +84,7 @@ namespace MWRender class NavMesh; class ActorsPaths; class RecastMesh; + class ObjectPaging; class RenderingManager : public MWRender::RenderingInterface { @@ -237,6 +238,8 @@ namespace MWRender void setNavMeshNumber(const std::size_t value); + void setActiveGrid(const osg::Vec4i &grid); + private: void updateProjectionMatrix(); void updateTextureFiltering(); @@ -275,6 +278,7 @@ namespace MWRender std::unique_ptr mWater; std::unique_ptr mTerrain; TerrainStorage* mTerrainStorage; + std::unique_ptr mObjectPaging; std::unique_ptr mSky; std::unique_ptr mFog; std::unique_ptr mEffectManager; diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 9a96e98060..60b9a3220e 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -172,7 +172,7 @@ namespace MWWorld class TerrainPreloadItem : public SceneUtil::WorkItem { public: - TerrainPreloadItem(const std::vector >& views, Terrain::World* world, const std::vector& preloadPositions) + TerrainPreloadItem(const std::vector >& views, Terrain::World* world, const std::vector& preloadPositions) : mAbort(false) , mTerrainViews(views) , mWorld(world) @@ -191,7 +191,7 @@ namespace MWWorld for (unsigned int i=0; ireset(); - mWorld->preload(mTerrainViews[i], mPreloadPositions[i], mAbort); + mWorld->preload(mTerrainViews[i], mPreloadPositions[i].first, mPreloadPositions[i].second, mAbort); } } @@ -204,7 +204,7 @@ namespace MWWorld std::atomic mAbort; std::vector > mTerrainViews; Terrain::World* mWorld; - std::vector mPreloadPositions; + std::vector mPreloadPositions; }; /// Worker thread item: update the resource system's cache, effectively deleting unused entries. @@ -415,7 +415,7 @@ namespace MWWorld mUnrefQueue = unrefQueue; } - void CellPreloader::setTerrainPreloadPositions(const std::vector &positions) + void CellPreloader::setTerrainPreloadPositions(const std::vector &positions) { if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) return; diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index 0501a4f9be..ef28170190 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include namespace Resource @@ -68,7 +69,8 @@ namespace MWWorld void setUnrefQueue(SceneUtil::UnrefQueue* unrefQueue); - void setTerrainPreloadPositions(const std::vector& positions); + typedef std::pair PositionCellGrid; + void setTerrainPreloadPositions(const std::vector& positions); private: Resource::ResourceSystem* mResourceSystem; @@ -105,7 +107,7 @@ namespace MWWorld PreloadMap mPreloadCells; std::vector > mTerrainViews; - std::vector mTerrainPreloadPositions; + std::vector mTerrainPreloadPositions; osg::ref_ptr mTerrainPreloadItem; osg::ref_ptr mUpdateCacheItem; }; diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index f862266404..278b8532e9 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -136,6 +136,10 @@ void ESMStore::setUp(bool validateRecords) mIds[*record] = storeIt->first; } } + + if (mStaticIds.empty()) + mStaticIds = mIds; + mSkills.setUp(); mMagicEffects.setUp(); mAttributes.setUp(); diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index fe1cfc7087..24364bfb13 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -68,6 +68,8 @@ namespace MWWorld // Lookup of all IDs. Makes looking up references faster. Just // maps the id name to the record type. std::map mIds; + std::map mStaticIds; + std::map mStores; ESM::NPC mPlayerTemplate; @@ -99,6 +101,14 @@ namespace MWWorld } return it->second; } + int findStatic(const std::string &id) const + { + std::map::const_iterator it = mStaticIds.find(id); + if (it == mStaticIds.end()) { + return 0; + } + return it->second; + } ESMStore() : mDynamicCount(0) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 1e1d39bec9..127b56c94f 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -286,28 +286,6 @@ namespace MWWorld ::updateObjectScale(ptr, *mPhysics, mRendering); } - void Scene::getGridCenter(int &cellX, int &cellY) - { - int maxX = std::numeric_limits::min(); - int maxY = std::numeric_limits::min(); - int minX = std::numeric_limits::max(); - int minY = std::numeric_limits::max(); - CellStoreCollection::iterator iter = mActiveCells.begin(); - while (iter!=mActiveCells.end()) - { - assert ((*iter)->getCell()->isExterior()); - int x = (*iter)->getCell()->getGridX(); - int y = (*iter)->getCell()->getGridY(); - maxX = std::max(x, maxX); - maxY = std::max(y, maxY); - minX = std::min(x, minX); - minY = std::min(y, minY); - ++iter; - } - cellX = (minX + maxX) / 2; - cellY = (minY + maxY) / 2; - } - void Scene::update (float duration, bool paused) { mPreloadTimer += duration; @@ -488,6 +466,27 @@ namespace MWWorld mPreloader->clear(); } + osg::Vec4i Scene::gridCenterToBounds(const osg::Vec2i& centerCell) const + { + return osg::Vec4i(centerCell.x()-mHalfGridSize,centerCell.y()-mHalfGridSize,centerCell.x()+mHalfGridSize+1,centerCell.y()+mHalfGridSize+1); + } + + osg::Vec2i Scene::getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i* currentGridCenter) const + { + if (currentGridCenter) + { + float centerX, centerY; + MWBase::Environment::get().getWorld()->indexToPosition(currentGridCenter->x(), currentGridCenter->y(), centerX, centerY, true); + float distance = std::max(std::abs(centerY-pos.x()), std::abs(centerY-pos.y())); + const float maxDistance = Constants::CellSizeInUnits / 2 + mCellLoadingThreshold; // 1/2 cell size + threshold + if (distance <= maxDistance) + return *currentGridCenter; + } + osg::Vec2i newCenter; + MWBase::Environment::get().getWorld()->positionToIndex(pos.x(), pos.y(), newCenter.x(), newCenter.y()); + return newCenter; + } + void Scene::playerMoved(const osg::Vec3f &pos) { const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); @@ -497,19 +496,9 @@ namespace MWWorld if (!mCurrentCell || !mCurrentCell->isExterior()) return; - // figure out the center of the current cell grid (*not* necessarily mCurrentCell, which is the cell the player is in) - int cellX, cellY; - getGridCenter(cellX, cellY); - float centerX, centerY; - MWBase::Environment::get().getWorld()->indexToPosition(cellX, cellY, centerX, centerY, true); - const float maxDistance = Constants::CellSizeInUnits / 2 + mCellLoadingThreshold; // 1/2 cell size + threshold - float distance = std::max(std::abs(centerX-pos.x()), std::abs(centerY-pos.y())); - if (distance > maxDistance) - { - int newX, newY; - MWBase::Environment::get().getWorld()->positionToIndex(pos.x(), pos.y(), newX, newY); - changeCellGrid(newX, newY); - } + osg::Vec2i newCell = getNewGridCenter(pos, &mCurrentGridCenter); + if (newCell != mCurrentGridCenter) + changeCellGrid(newCell.x(), newCell.y()); } void Scene::changeCellGrid (int playerCellX, int playerCellY, bool changeEvent) @@ -612,6 +601,9 @@ namespace MWWorld CellStore* current = MWBase::Environment::get().getWorld()->getExterior(playerCellX, playerCellY); MWBase::Environment::get().getWindowManager()->changeCell(current); + mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY); + mRendering.setActiveGrid(gridCenterToBounds(mCurrentGridCenter)); + if (changeEvent) mCellChanged = true; } @@ -983,7 +975,7 @@ namespace MWWorld void Scene::preloadCells(float dt) { - std::vector exteriorPositions; + std::vector exteriorPositions; const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); @@ -991,7 +983,7 @@ namespace MWWorld osg::Vec3f predictedPos = playerPos + moved / dt * mPredictionTime; if (mCurrentCell->isExterior()) - exteriorPositions.push_back(predictedPos); + exteriorPositions.emplace_back(predictedPos, gridCenterToBounds(getNewGridCenter(predictedPos, &mCurrentGridCenter))); mLastPlayerPos = playerPos; @@ -1008,7 +1000,7 @@ namespace MWWorld mPreloader->setTerrainPreloadPositions(exteriorPositions); } - void Scene::preloadTeleportDoorDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector& exteriorPositions) + void Scene::preloadTeleportDoorDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector& exteriorPositions) { std::vector teleportDoors; for (const MWWorld::CellStore* cellStore : mActiveCells) @@ -1042,7 +1034,7 @@ namespace MWWorld int x,y; MWBase::Environment::get().getWorld()->positionToIndex (pos.x(), pos.y(), x, y); preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true); - exteriorPositions.push_back(pos); + exteriorPositions.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos))); } } catch (std::exception& e) @@ -1062,7 +1054,7 @@ namespace MWWorld int cellX,cellY; - getGridCenter(cellX,cellY); + cellX = mCurrentGridCenter.x(); cellY = mCurrentGridCenter.y(); float centerX, centerY; MWBase::Environment::get().getWorld()->indexToPosition(cellX, cellY, centerX, centerY, true); @@ -1110,8 +1102,8 @@ namespace MWWorld void Scene::preloadTerrain(const osg::Vec3f &pos) { - std::vector vec; - vec.push_back(pos); + std::vector vec; + vec.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos))); mPreloader->setTerrainPreloadPositions(vec); } @@ -1145,7 +1137,7 @@ namespace MWWorld std::vector mList; }; - void Scene::preloadFastTravelDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& /*predictedPos*/, std::vector& exteriorPositions) // ignore predictedPos here since opening dialogue with travel service takes extra time + void Scene::preloadFastTravelDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& /*predictedPos*/, std::vector& exteriorPositions) // ignore predictedPos here since opening dialogue with travel service takes extra time { const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); ListFastTravelDestinationsVisitor listVisitor(mPreloadDistance, player.getRefData().getPosition().asVec3()); @@ -1166,7 +1158,7 @@ namespace MWWorld int x,y; MWBase::Environment::get().getWorld()->positionToIndex( pos.x(), pos.y(), x, y); preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true); - exteriorPositions.push_back(pos); + exteriorPositions.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos))); } } } diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index da795f84b7..9b8403c389 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -1,6 +1,9 @@ #ifndef GAME_MWWORLD_SCENE_H #define GAME_MWWORLD_SCENE_H +#include +#include + #include "ptr.hpp" #include "globals.hpp" @@ -86,16 +89,20 @@ namespace MWWorld osg::Vec3f mLastPlayerPos; void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test = false); + osg::Vec2i mCurrentGridCenter; // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center void changeCellGrid (int playerCellX, int playerCellY, bool changeEvent = true); - void getGridCenter(int& cellX, int& cellY); + typedef std::pair PositionCellGrid; void preloadCells(float dt); - void preloadTeleportDoorDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector& exteriorPositions); + void preloadTeleportDoorDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector& exteriorPositions); void preloadExteriorGrid(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos); - void preloadFastTravelDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector& exteriorPositions); + void preloadFastTravelDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector& exteriorPositions); + + osg::Vec4i gridCenterToBounds(const osg::Vec2i ¢erCell) const; + osg::Vec2i getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i *currentGridCenter = nullptr) const; public: diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index a414585efe..93f11d4fd9 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -151,6 +151,19 @@ namespace MWWorld return 0; } + template + const T *Store::searchStatic(const std::string &id) const + { + std::string idLower = Misc::StringUtils::lowerCase(id); + typename std::map::const_iterator it = mStatic.find(idLower); + + if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { + return &(it->second); + } + + return 0; + } + template bool Store::isDynamic(const std::string &id) const { @@ -582,6 +595,18 @@ namespace MWWorld return 0; } + const ESM::Cell *Store::searchStatic(int x, int y) const + { + ESM::Cell cell; + cell.mData.mX = x, cell.mData.mY = y; + + std::pair key(x, y); + DynamicExt::const_iterator it = mExt.find(key); + if (it != mExt.end()) { + return &(it->second); + } + return 0; + } const ESM::Cell *Store::searchOrCreate(int x, int y) { std::pair key(x, y); diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 64c01589e3..f119fa928a 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -167,6 +167,7 @@ namespace MWWorld void setUp(); const T *search(const std::string &id) const; + const T *searchStatic(const std::string &id) const; /** * Does the record with this ID come from the dynamic store? @@ -297,6 +298,7 @@ namespace MWWorld const ESM::Cell *search(const std::string &id) const; const ESM::Cell *search(int x, int y) const; + const ESM::Cell *searchStatic(int x, int y) const; const ESM::Cell *searchOrCreate(int x, int y); const ESM::Cell *find(const std::string &id) const; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 61a40ee4b3..1709a3d149 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -466,7 +466,7 @@ namespace Resource return options; } - osg::ref_ptr SceneManager::getTemplate(const std::string &name) + osg::ref_ptr SceneManager::getTemplate(const std::string &name, bool compile) { std::string normalized = name; mVFS->normalizeFilename(normalized); @@ -529,7 +529,7 @@ namespace Resource optimizer.optimize(loaded, options); } - if (mIncrementalCompileOperation) + if (compile && mIncrementalCompileOperation) mIncrementalCompileOperation->add(loaded); else loaded->getBound(); @@ -713,6 +713,13 @@ namespace Resource mSharedStateMutex.lock(); mSharedStateManager->prune(); mSharedStateMutex.unlock(); + + if (mIncrementalCompileOperation) + { + OpenThreads::ScopedLock lock(*mIncrementalCompileOperation->getToCompiledMutex()); + while (mIncrementalCompileOperation->getToCompile().size() > 1000) + mIncrementalCompileOperation->getToCompile().pop_front(); + } } void SceneManager::clearCache() diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 1c1c60a58d..bbe88c9a0e 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -81,7 +81,7 @@ namespace Resource /// @note If the given filename does not exist or fails to load, an error marker mesh will be used instead. /// If even the error marker mesh can not be found, an exception is thrown. /// @note Thread safe. - osg::ref_ptr getTemplate(const std::string& name); + osg::ref_ptr getTemplate(const std::string& name, bool compile=true); /// Create an instance of the given scene template and cache it for later use, so that future calls to getInstance() can simply /// return this cached object instead of creating a new one. diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index 487126627c..7bcccdbe33 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -103,7 +103,9 @@ void Optimizer::optimize(osg::Node* node, unsigned int options) osg::Timer_t startTick = osg::Timer::instance()->tick(); MergeGeometryVisitor mgv(this); - mgv.setTargetMaximumNumberOfVertices(10000); + mgv.setTargetMaximumNumberOfVertices(1000000); + mgv.setMergeAlphaBlending(_mergeAlphaBlending); + mgv.setViewPoint(_viewPoint); node->accept(mgv); osg::Timer_t endTick = osg::Timer::instance()->tick(); @@ -988,6 +990,17 @@ struct LessGeometry } }; +struct LessGeometryViewPoint +{ + osg::Vec3f _viewPoint; + bool operator() (const osg::ref_ptr& lhs,const osg::ref_ptr& rhs) const + { + float len1 = (lhs->getBoundingBox().center() - _viewPoint).length2(); + float len2 = (rhs->getBoundingBox().center() - _viewPoint).length2(); + return len2 < len1; + } +}; + struct LessGeometryPrimitiveType { bool operator() (const osg::ref_ptr& lhs,const osg::ref_ptr& rhs) const @@ -1055,16 +1068,16 @@ bool isAbleToMerge(const osg::Geometry& g1, const osg::Geometry& g2) void Optimizer::MergeGeometryVisitor::pushStateSet(osg::StateSet *stateSet) { _stateSetStack.push_back(stateSet); - checkAllowedToMerge(); + checkAlphaBlendingActive(); } void Optimizer::MergeGeometryVisitor::popStateSet() { _stateSetStack.pop_back(); - checkAllowedToMerge(); + checkAlphaBlendingActive(); } -void Optimizer::MergeGeometryVisitor::checkAllowedToMerge() +void Optimizer::MergeGeometryVisitor::checkAlphaBlendingActive() { int renderingHint = 0; bool override = false; @@ -1080,7 +1093,7 @@ void Optimizer::MergeGeometryVisitor::checkAllowedToMerge() override = true; } // Can't merge Geometry that are using a transparent sorting bin as that would cause the sorting to break. - _allowedToMerge = renderingHint != osg::StateSet::TRANSPARENT_BIN; + _alphaBlendingActive = renderingHint == osg::StateSet::TRANSPARENT_BIN; } void Optimizer::MergeGeometryVisitor::apply(osg::Group &group) @@ -1088,7 +1101,7 @@ void Optimizer::MergeGeometryVisitor::apply(osg::Group &group) if (group.getStateSet()) pushStateSet(group.getStateSet()); - if (_allowedToMerge) + if (!_alphaBlendingActive || _mergeAlphaBlending) mergeGroup(group); traverse(group); @@ -1097,6 +1110,14 @@ void Optimizer::MergeGeometryVisitor::apply(osg::Group &group) popStateSet(); } +osg::PrimitiveSet* clonePrimitive(osg::PrimitiveSet* ps) +{ + if (ps->referenceCount() <= 1) + return ps; + ps = dynamic_cast(ps->clone(osg::CopyOp::DEEP_COPY_ALL)); + return ps; +} + bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) { if (!isOperationPermissibleForObject(&group)) return false; @@ -1120,7 +1141,7 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) osg::Geometry* geom = child->asGeometry(); if (geom) { - if (!geometryContainsSharedArrays(*geom) && + if ( geom->getDataVariance()!=osg::Object::DYNAMIC && isOperationPermissibleForObject(geom)) { @@ -1254,6 +1275,12 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) DuplicateList& duplicateList = *mitr; if (!duplicateList.empty()) { + if (_alphaBlendingActive) + { + LessGeometryViewPoint lgvp; + lgvp._viewPoint = _viewPoint; + std::sort(duplicateList.begin(), duplicateList.end(), lgvp); + } DuplicateList::iterator ditr = duplicateList.begin(); osg::ref_ptr lhs = *ditr++; group.addChild(lhs.get()); @@ -1290,10 +1317,12 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) { if (prim->getNumIndices()==3) { + prim = clonePrimitive(prim); (*itr) = prim; prim->setMode(osg::PrimitiveSet::TRIANGLES); } else if (prim->getNumIndices()==4) { + prim = clonePrimitive(prim); (*itr) = prim; prim->setMode(osg::PrimitiveSet::QUADS); } } @@ -1320,6 +1349,8 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) #if 1 bool doneCombine = false; + std::set toremove; + osg::Geometry::PrimitiveSetList& primitives = geom->getPrimitiveSetList(); unsigned int lhsNo=0; unsigned int rhsNo=1; @@ -1348,6 +1379,8 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) if (combine) { + lhs = clonePrimitive(lhs); + primitives[lhsNo] = lhs; switch(lhs->getType()) { @@ -1375,7 +1408,7 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) if (combine) { // make this primitive set as invalid and needing cleaning up. - rhs->setMode(0xffffff); + toremove.insert(rhs); doneCombine = true; ++rhsNo; } @@ -1390,7 +1423,6 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) if (doneCombine) { // now need to clean up primitiveset so it no longer contains the rhs combined primitives. - // first swap with a empty primitiveSet to empty it completely. osg::Geometry::PrimitiveSetList oldPrimitives; primitives.swap(oldPrimitives); @@ -1400,7 +1432,7 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) pitr != oldPrimitives.end(); ++pitr) { - if ((*pitr)->getMode()!=0xffffff) primitives.push_back(*pitr); + if (!toremove.count(*pitr)) primitives.push_back(*pitr); } } #endif @@ -1479,34 +1511,6 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) return false; } -bool Optimizer::MergeGeometryVisitor::geometryContainsSharedArrays(osg::Geometry& geom) -{ - if (geom.getVertexArray() && geom.getVertexArray()->referenceCount()>1) return true; - if (geom.getNormalArray() && geom.getNormalArray()->referenceCount()>1) return true; - if (geom.getColorArray() && geom.getColorArray()->referenceCount()>1) return true; - if (geom.getSecondaryColorArray() && geom.getSecondaryColorArray()->referenceCount()>1) return true; - if (geom.getFogCoordArray() && geom.getFogCoordArray()->referenceCount()>1) return true; - - - for(unsigned int unit=0;unitreferenceCount()>1) return true; - } - - // shift the indices of the incoming primitives to account for the pre existing geometry. - for(osg::Geometry::PrimitiveSetList::iterator primItr=geom.getPrimitiveSetList().begin(); - primItr!=geom.getPrimitiveSetList().end(); - ++primItr) - { - if ((*primItr)->referenceCount()>1) return true; - } - - - return false; -} - - class MergeArrayVisitor : public osg::ArrayVisitor { protected: @@ -1574,6 +1578,8 @@ class MergeArrayVisitor : public osg::ArrayVisitor bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geometry& rhs) { + if (lhs.containsSharedArrays()) + lhs.duplicateSharedArrays(); MergeArrayVisitor merger; @@ -1661,7 +1667,6 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom } } - // shift the indices of the incoming primitives to account for the pre existing geometry. osg::Geometry::PrimitiveSetList::iterator primItr; for(primItr=rhs.getPrimitiveSetList().begin(); primItr!=rhs.getPrimitiveSetList().end(); ++primItr) @@ -1697,7 +1702,8 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom } else { - primitive->offsetIndices(base); + (*primItr) = clonePrimitive(primitive); + (*primItr)->offsetIndices(base); } } break; @@ -1722,7 +1728,8 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom } else { - primitive->offsetIndices(base); + (*primItr) = clonePrimitive(primitive); + (*primItr)->offsetIndices(base); } } break; @@ -1731,7 +1738,8 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom case(osg::PrimitiveSet::DrawArrayLengthsPrimitiveType): case(osg::PrimitiveSet::DrawElementsUIntPrimitiveType): default: - primitive->offsetIndices(base); + (*primItr) = clonePrimitive(primitive); + (*primItr)->offsetIndices(base); break; } } diff --git a/components/sceneutil/optimizer.hpp b/components/sceneutil/optimizer.hpp index 6dd4394d18..d7c83e898b 100644 --- a/components/sceneutil/optimizer.hpp +++ b/components/sceneutil/optimizer.hpp @@ -65,7 +65,7 @@ class Optimizer public: - Optimizer() {} + Optimizer() : _mergeAlphaBlending(false) {} virtual ~Optimizer() {} enum OptimizationOptions @@ -118,6 +118,9 @@ class Optimizer STATIC_OBJECT_DETECTION }; + void setMergeAlphaBlending(bool merge) { _mergeAlphaBlending = merge; } + void setViewPoint(const osg::Vec3f& viewPoint) { _viewPoint = viewPoint; } + /** Reset internal data to initial state - the getPermissibleOptionsMap is cleared.*/ void reset(); @@ -252,6 +255,9 @@ class Optimizer typedef std::map PermissibleOptimizationsMap; PermissibleOptimizationsMap _permissibleOptimizationsMap; + osg::Vec3f _viewPoint; + bool _mergeAlphaBlending; + public: /** Flatten Static Transform nodes by applying their transform to the @@ -371,7 +377,16 @@ class Optimizer /// default to traversing all children. MergeGeometryVisitor(Optimizer* optimizer=0) : BaseOptimizerVisitor(optimizer, MERGE_GEOMETRY), - _targetMaximumNumberOfVertices(10000), _allowedToMerge(true) {} + _targetMaximumNumberOfVertices(10000), _alphaBlendingActive(false), _mergeAlphaBlending(false) {} + + void setMergeAlphaBlending(bool merge) + { + _mergeAlphaBlending = merge; + } + void setViewPoint(const osg::Vec3f& viewPoint) + { + _viewPoint = viewPoint; + } void setTargetMaximumNumberOfVertices(unsigned int num) { @@ -385,15 +400,13 @@ class Optimizer void pushStateSet(osg::StateSet* stateSet); void popStateSet(); - void checkAllowedToMerge(); + void checkAlphaBlendingActive(); virtual void apply(osg::Group& group); virtual void apply(osg::Billboard&) { /* don't do anything*/ } bool mergeGroup(osg::Group& group); - static bool geometryContainsSharedArrays(osg::Geometry& geom); - static bool mergeGeometry(osg::Geometry& lhs,osg::Geometry& rhs); static bool mergePrimitive(osg::DrawArrays& lhs,osg::DrawArrays& rhs); @@ -406,7 +419,9 @@ class Optimizer unsigned int _targetMaximumNumberOfVertices; std::vector _stateSetStack; - bool _allowedToMerge; + bool _alphaBlendingActive; + bool _mergeAlphaBlending; + osg::Vec3f _viewPoint; }; }; diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index 97c8e1d391..627353b996 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -106,7 +106,7 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr sourceGeometry) } } -osg::ref_ptr RigGeometry::getSourceGeometry() +osg::ref_ptr RigGeometry::getSourceGeometry() const { return mSourceGeometry; } diff --git a/components/sceneutil/riggeometry.hpp b/components/sceneutil/riggeometry.hpp index a393530ec2..801c172b36 100644 --- a/components/sceneutil/riggeometry.hpp +++ b/components/sceneutil/riggeometry.hpp @@ -44,7 +44,7 @@ namespace SceneUtil /// @note The source geometry will not be modified. void setSourceGeometry(osg::ref_ptr sourceGeom); - osg::ref_ptr getSourceGeometry(); + osg::ref_ptr getSourceGeometry() const; virtual void accept(osg::NodeVisitor &nv); virtual bool supports(const osg::PrimitiveFunctor&) const { return true; } diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 95c1ca4914..1dc62cfb80 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -10,7 +10,6 @@ #include #include -#include #include #include "terraindrawable.hpp" @@ -35,7 +34,7 @@ ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, T } -osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f ¢er, unsigned char lod, unsigned int lodFlags) +osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f ¢er, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint) { ChunkId id = std::make_tuple(center, lod, lodFlags); osg::ref_ptr obj = mCache->getRefFromObjectCache(id); @@ -163,10 +162,6 @@ std::vector > ChunkManager::createPasses(float chunk osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Vec2f &chunkCenter, unsigned char lod, unsigned int lodFlags) { - osg::Vec2f worldCenter = chunkCenter*mStorage->getCellWorldSize(); - osg::ref_ptr transform (new SceneUtil::PositionAttitudeTransform); - transform->setPosition(osg::Vec3f(worldCenter.x(), worldCenter.y(), 0.f)); - osg::ref_ptr positions (new osg::Vec3Array); osg::ref_ptr normals (new osg::Vec3Array); osg::ref_ptr colors (new osg::Vec4ubArray); @@ -224,16 +219,15 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve geometry->setPasses(createPasses(chunkSize, chunkCenter, false)); } - transform->addChild(geometry); - transform->getBound(); - geometry->setupWaterBoundingBox(-1, chunkSize * mStorage->getCellWorldSize() / numVerts); if (mSceneManager->getIncrementalCompileOperation()) { mSceneManager->getIncrementalCompileOperation()->add(geometry); } - return transform; + geometry->setNodeMask(mNodeMask); + + return geometry; } } diff --git a/components/terrain/chunkmanager.hpp b/components/terrain/chunkmanager.hpp index be83e158cb..d6f4dd98ec 100644 --- a/components/terrain/chunkmanager.hpp +++ b/components/terrain/chunkmanager.hpp @@ -6,6 +6,7 @@ #include #include "buffercache.hpp" +#include "quadtreeworld.hpp" namespace osg { @@ -29,23 +30,28 @@ namespace Terrain typedef std::tuple ChunkId; // Center, Lod, Lod Flags /// @brief Handles loading and caching of terrain chunks - class ChunkManager : public Resource::GenericResourceManager + class ChunkManager : public Resource::GenericResourceManager, public QuadTreeWorld::ChunkManager { public: ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager, CompositeMapRenderer* renderer); - osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags); + osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint); void setCompositeMapSize(unsigned int size) { mCompositeMapSize = size; } void setCompositeMapLevel(float level) { mCompositeMapLevel = level; } void setMaxCompositeGeometrySize(float maxCompGeometrySize) { mMaxCompGeometrySize = maxCompGeometrySize; } + void setNodeMask(unsigned int mask) { mNodeMask = mask; } + virtual unsigned int getNodeMask() override { return mNodeMask; } + void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; void clearCache() override; void releaseGLObjects(osg::State* state) override; + virtual void setExpiryDelay(double expiryDelay) override { mExpiryDelay = 0.5f; } + private: osg::ref_ptr createChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags); @@ -61,6 +67,8 @@ namespace Terrain CompositeMapRenderer* mCompositeMapRenderer; BufferCache mBufferCache; + unsigned int mNodeMask; + unsigned int mCompositeMapSize; float mCompositeMapLevel; float mMaxCompGeometrySize; diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index c43a9a21b9..842dced208 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -9,6 +9,7 @@ #include #include +#include #include "quadtreenode.hpp" #include "storage.hpp" @@ -63,6 +64,12 @@ public: int nativeLodLevel = Log2(static_cast(node->getSize()/mMinSize)); int lodLevel = Log2(static_cast(dist/(Constants::CellSizeInUnits*mMinSize*mFactor))); + if (node->getSize()>1 && dist < (8192+1024)*1.41421356237) + { + // to prevent making chunks who will cross the activegrid border + return false; + } + return nativeLodLevel <= lodLevel; } @@ -231,6 +238,7 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour mChunkManager->setCompositeMapSize(compMapResolution); mChunkManager->setCompositeMapLevel(compMapLevel); mChunkManager->setMaxCompositeGeometrySize(maxCompGeometrySize); + mChunkManagers.push_back(mChunkManager.get()); } QuadTreeWorld::~QuadTreeWorld() @@ -289,7 +297,7 @@ unsigned int getLodFlags(QuadTreeNode* node, int ourLod, int vertexLodMod, ViewD return lodFlags; } -void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, ChunkManager* chunkManager) +void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, float cellWorldSize, const osg::Vec4i &gridbounds, const std::vector& chunkManagers) { if (!vd->hasChanged() && entry.mRenderingNode) return; @@ -308,7 +316,20 @@ void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, C } if (!entry.mRenderingNode) - entry.mRenderingNode = chunkManager->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, entry.mLodFlags); + { + auto pat = new SceneUtil::PositionAttitudeTransform; + pat->setPosition(osg::Vec3f(entry.mNode->getCenter().x()*cellWorldSize, entry.mNode->getCenter().y()*cellWorldSize, 0.f)); + + const osg::Vec2f& center = entry.mNode->getCenter(); + bool far = (center.x() <= gridbounds.x() || center.y() <= gridbounds.y() || center.x() >= gridbounds.z() || center.y() >= gridbounds.w()); + + for (QuadTreeWorld::ChunkManager* m : chunkManagers) + { + osg::Node* n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, entry.mLodFlags, far, vd->getViewPoint()); + if (n) pat->addChild(n); + } + entry.mRenderingNode = pat; + } } void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil::CullVisitor* cv, float cellworldsize, bool outofworld) @@ -385,7 +406,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) bool needsUpdate = true; ViewData* vd = nullptr; if (isCullVisitor) - vd = mViewDataMap->getViewData(static_cast(&nv)->getCurrentCamera(), nv.getViewPoint(), needsUpdate); + vd = mViewDataMap->getViewData(static_cast(&nv)->getCurrentCamera(), nv.getViewPoint(), mActiveGrid, needsUpdate); else { static ViewData sIntersectionViewData; @@ -432,12 +453,12 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) } } + const float cellWorldSize = mStorage->getCellWorldSize(); + for (unsigned int i=0; igetNumEntries(); ++i) { ViewData::Entry& entry = vd->getEntry(i); - - loadRenderingNode(entry, vd, mVertexLodMod, mChunkManager.get()); - + loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, mActiveGrid, mChunkManagers); entry.mRenderingNode->accept(nv); } @@ -490,13 +511,17 @@ void QuadTreeWorld::enable(bool enabled) void QuadTreeWorld::cacheCell(View *view, int x, int y) { ensureQuadTreeBuilt(); + osg::Vec4i grid (x,y,x+1,y+1); ViewData* vd = static_cast(view); + vd->setActiveGrid(grid); mRootNode->traverseTo(vd, 1, osg::Vec2f(x+0.5f,y+0.5f)); + const float cellWorldSize = mStorage->getCellWorldSize(); + for (unsigned int i=0; igetNumEntries(); ++i) { ViewData::Entry& entry = vd->getEntry(i); - loadRenderingNode(entry, vd, mVertexLodMod, mChunkManager.get()); + loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, grid, mChunkManagers); } } @@ -505,18 +530,21 @@ View* QuadTreeWorld::createView() return new ViewData; } -void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, std::atomic &abort) +void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg::Vec4i &grid, std::atomic &abort) { ensureQuadTreeBuilt(); ViewData* vd = static_cast(view); vd->setViewPoint(viewPoint); + vd->setActiveGrid(grid); mRootNode->traverseNodes(vd, viewPoint, mLodCallback, mViewDistance); + const float cellWorldSize = mStorage->getCellWorldSize(); + for (unsigned int i=0; igetNumEntries() && !abort; ++i) { ViewData::Entry& entry = vd->getEntry(i); - loadRenderingNode(entry, vd, mVertexLodMod, mChunkManager.get()); + loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, grid, mChunkManagers); } vd->markUnchanged(); } @@ -526,7 +554,7 @@ void QuadTreeWorld::storeView(const View* view, double referenceTime) osg::ref_ptr dummy = new osg::DummyObject; const ViewData* vd = static_cast(view); bool needsUpdate = false; - ViewData* stored = mViewDataMap->getViewData(dummy, vd->getViewPoint(), needsUpdate); + ViewData* stored = mViewDataMap->getViewData(dummy, vd->getViewPoint(), vd->getActiveGrid(), needsUpdate); stored->copyFrom(*vd); stored->setLastUsageTimeStamp(referenceTime); } @@ -556,5 +584,10 @@ void QuadTreeWorld::unloadCell(int x, int y) World::unloadCell(x,y); } +void QuadTreeWorld::addChunkManager(QuadTreeWorld::ChunkManager* m) +{ + mChunkManagers.push_back(m); + mTerrainRoot->setNodeMask(mTerrainRoot->getNodeMask()|m->getNodeMask()); +} } diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index bcb671ee13..7634ea868f 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -38,11 +38,20 @@ namespace Terrain virtual void unloadCell(int x, int y); View* createView(); - void preload(View* view, const osg::Vec3f& eyePoint, std::atomic& abort); + void preload(View* view, const osg::Vec3f& eyePoint, const osg::Vec4i &cellgrid, std::atomic& abort); void storeView(const View* view, double referenceTime); void reportStats(unsigned int frameNumber, osg::Stats* stats); + class ChunkManager + { + public: + virtual ~ChunkManager(){} + virtual osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint) = 0; + virtual unsigned int getNodeMask() { return 0; } + }; + void addChunkManager(ChunkManager*); + private: void ensureQuadTreeBuilt(); @@ -51,6 +60,8 @@ namespace Terrain osg::ref_ptr mViewDataMap; osg::ref_ptr mLodCallback; + std::vector mChunkManagers; + OpenThreads::Mutex mQuadTreeMutex; bool mQuadTreeBuilt; float mLodFactor; diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index a0e5e4718b..4398d1a143 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -5,9 +5,10 @@ #include #include +#include #include "chunkmanager.hpp" #include "compositemaprenderer.hpp" - +#include "storage.hpp" namespace Terrain { @@ -57,12 +58,17 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu } else { - osg::ref_ptr node = mChunkManager->getChunk(chunkSize, chunkCenter, 0, 0); + osg::ref_ptr node = mChunkManager->getChunk(chunkSize, chunkCenter, 0, 0, false, osg::Vec3f()); if (!node) return nullptr; + + const float cellWorldSize = mStorage->getCellWorldSize(); + osg::ref_ptr pat = new SceneUtil::PositionAttitudeTransform; + pat->setPosition(osg::Vec3f(chunkCenter.x()*cellWorldSize, chunkCenter.y()*cellWorldSize, 0.f)); + pat->addChild(node); if (parent) - parent->addChild(node); - return node; + parent->addChild(pat); + return pat; } } diff --git a/components/terrain/viewdata.cpp b/components/terrain/viewdata.cpp index d07a0e356a..6399425a40 100644 --- a/components/terrain/viewdata.cpp +++ b/components/terrain/viewdata.cpp @@ -24,6 +24,7 @@ void ViewData::copyFrom(const ViewData& other) mChanged = other.mChanged; mHasViewPoint = other.mHasViewPoint; mViewPoint = other.mViewPoint; + mActiveGrid = other.mActiveGrid; } void ViewData::add(QuadTreeNode *node) @@ -118,12 +119,12 @@ bool ViewData::Entry::set(QuadTreeNode *node) } } -bool suitable(ViewData* vd, const osg::Vec3f& viewPoint, float& maxDist) +bool suitable(ViewData* vd, const osg::Vec3f& viewPoint, float& maxDist, const osg::Vec4i& activeGrid) { - return vd->hasViewPoint() && (vd->getViewPoint() - viewPoint).length2() < maxDist*maxDist; + return vd->hasViewPoint() && (vd->getViewPoint() - viewPoint).length2() < maxDist*maxDist && vd->getActiveGrid() == activeGrid; } -ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPoint, bool& needsUpdate) +ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPoint, const osg::Vec4i &activeGrid, bool& needsUpdate) { Map::const_iterator found = mViews.find(viewer); ViewData* vd = nullptr; @@ -135,11 +136,11 @@ ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPo else vd = found->second; - if (!suitable(vd, viewPoint, mReuseDistance)) + if (!suitable(vd, viewPoint, mReuseDistance, activeGrid)) { for (Map::const_iterator other = mViews.begin(); other != mViews.end(); ++other) { - if (suitable(other->second, viewPoint, mReuseDistance) && other->second->getNumEntries()) + if (suitable(other->second, viewPoint, mReuseDistance, activeGrid) && other->second->getNumEntries()) { vd->copyFrom(*other->second); needsUpdate = false; @@ -147,6 +148,7 @@ ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPo } } vd->setViewPoint(viewPoint); + vd->setActiveGrid(activeGrid); needsUpdate = true; } else diff --git a/components/terrain/viewdata.hpp b/components/terrain/viewdata.hpp index 7f9f14af55..87b45f57b1 100644 --- a/components/terrain/viewdata.hpp +++ b/components/terrain/viewdata.hpp @@ -57,6 +57,9 @@ namespace Terrain void setViewPoint(const osg::Vec3f& viewPoint); const osg::Vec3f& getViewPoint() const; + void setActiveGrid(const osg::Vec4i &grid) { if (grid != mActiveGrid) {mActiveGrid = grid;mEntries.clear();mNumEntries=0;} } + const osg::Vec4i &getActiveGrid() const { return mActiveGrid;} + private: std::vector mEntries; unsigned int mNumEntries; @@ -64,6 +67,7 @@ namespace Terrain bool mChanged; osg::Vec3f mViewPoint; bool mHasViewPoint; + osg::Vec4i mActiveGrid; }; class ViewDataMap : public osg::Referenced @@ -75,7 +79,7 @@ namespace Terrain , mExpiryDelay(1.f) {} - ViewData* getViewData(osg::Object* viewer, const osg::Vec3f& viewPoint, bool& needsUpdate); + ViewData* getViewData(osg::Object* viewer, const osg::Vec3f& viewPoint, const osg::Vec4i &activeGrid, bool& needsUpdate); ViewData* createOrReuseView(); diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 2d53f40908..b57fa1cef8 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -23,7 +23,6 @@ World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSyst { mTerrainRoot = new osg::Group; mTerrainRoot->setNodeMask(nodeMask); - mTerrainRoot->getOrCreateStateSet()->setRenderingHint(osg::StateSet::OPAQUE_BIN); osg::ref_ptr material (new osg::Material); material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); mTerrainRoot->getOrCreateStateSet()->setAttributeAndModes(material, osg::StateAttribute::ON); @@ -48,6 +47,7 @@ World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSyst mTextureManager.reset(new TextureManager(mResourceSystem->getSceneManager())); mChunkManager.reset(new ChunkManager(mStorage, mResourceSystem->getSceneManager(), mTextureManager.get(), mCompositeMapRenderer)); + mChunkManager->setNodeMask(nodeMask); mCellBorder.reset(new CellBorder(this,mTerrainRoot.get(),borderMask)); mResourceSystem->addResourceManager(mChunkManager.get()); diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 618095a605..e559321693 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -147,7 +147,7 @@ namespace Terrain /// @note Thread safe, as long as you do not attempt to load into the same view from multiple threads. - virtual void preload(View* view, const osg::Vec3f& viewPoint, std::atomic& abort) {} + virtual void preload(View* view, const osg::Vec3f& viewPoint, const osg::Vec4i &cellgrid, std::atomic& abort) {} /// Store a preloaded view into the cache with the intent that the next rendering traversal can use it. /// @note Not thread safe. @@ -161,6 +161,8 @@ namespace Terrain osg::Callback* getHeightCullCallback(float highz, unsigned int mask); + void setActiveGrid(const osg::Vec4i &grid) { mActiveGrid = grid; } + protected: Storage* mStorage; @@ -181,6 +183,8 @@ namespace Terrain std::set> mLoadedCells; osg::ref_ptr mHeightCullCallback; + + osg::Vec4i mActiveGrid; }; } diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 5b587776c3..2653f2fc42 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -106,6 +106,15 @@ composite map resolution = 512 # Controls the maximum size of composite geometry, should be >= 1.0. With low values there will be many small chunks, with high values - lesser count of bigger chunks. max composite geometry size = 4.0 +# Load far objects on terrain +object paging = true + +# Turn off to save memory but worse FPS +object paging merge geometry = true + +# Cull objects smaller than this size divided by distance +object paging min size = 0.01 + [Fog] # If true, use extended fog parameters for distant terrain not controlled by