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/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index dcfe723f77..436e9c2bc9 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/statemanager.hpp" @@ -29,9 +30,9 @@ namespace MWGui { - LoadingScreen::LoadingScreen(const VFS::Manager* vfs, osgViewer::Viewer* viewer) + LoadingScreen::LoadingScreen(Resource::ResourceSystem* resourceSystem, osgViewer::Viewer* viewer) : WindowBase("openmw_loading_screen.layout") - , mVFS(vfs) + , mResourceSystem(resourceSystem) , mViewer(viewer) , mTargetFrameRate(120.0) , mLastWallpaperChangeTime(0.0) @@ -64,9 +65,9 @@ namespace MWGui void LoadingScreen::findSplashScreens() { - const std::map& index = mVFS->getIndex(); + const std::map& index = mResourceSystem->getVFS()->getIndex(); std::string pattern = "Splash/"; - mVFS->normalizeFilename(pattern); + mResourceSystem->getVFS()->normalizeFilename(pattern); /* priority given to the left */ const std::array supported_extensions {{".tga", ".dds", ".ktx", ".png", ".bmp", ".jpeg", ".jpg"}}; @@ -171,6 +172,11 @@ namespace MWGui // We are already using node masks to avoid the scene from being updated/rendered, but node masks don't work for computeBound() mViewer->getSceneData()->setComputeBoundingSphereCallback(new DontComputeBoundCallback); + if (const osgUtil::IncrementalCompileOperation* ico = mViewer->getIncrementalCompileOperation()) { + mOldIcoMin = ico->getMinimumTimeAvailableForGLCompileAndDeletePerFrame(); + mOldIcoMax = ico->getMaximumNumOfObjectsToCompilePerFrame(); + } + mVisible = visible; mLoadingBox->setVisible(mVisible); setVisible(true); @@ -215,6 +221,12 @@ namespace MWGui //std::cout << "loading took " << mTimer.time_m() - mLoadingOnTime << std::endl; setVisible(false); + if (osgUtil::IncrementalCompileOperation* ico = mViewer->getIncrementalCompileOperation()) + { + ico->setMinimumTimeAvailableForGLCompileAndDeletePerFrame(mOldIcoMin); + ico->setMaximumNumOfObjectsToCompilePerFrame(mOldIcoMax); + } + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Loading); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_LoadingWallpaper); } @@ -336,7 +348,13 @@ namespace MWGui MWBase::Environment::get().getInputManager()->update(0, true, true); - //osg::Timer timer; + mResourceSystem->reportStats(mViewer->getFrameStamp()->getFrameNumber(), mViewer->getViewerStats()); + if (osgUtil::IncrementalCompileOperation* ico = mViewer->getIncrementalCompileOperation()) + { + ico->setMinimumTimeAvailableForGLCompileAndDeletePerFrame(1.f/getTargetFrameRate()); + ico->setMaximumNumOfObjectsToCompilePerFrame(1000); + } + // at the time this function is called we are in the middle of a frame, // so out of order calls are necessary to get a correct frameNumber for the next frame. // refer to the advance() and frame() order in Engine::go() @@ -344,10 +362,6 @@ namespace MWGui mViewer->updateTraversal(); mViewer->renderingTraversals(); mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); - //std::cout << "frame took " << timer.time_m() << std::endl; - - //if (mViewer->getIncrementalCompileOperation()) - //std::cout << "num to compile " << mViewer->getIncrementalCompileOperation()->getToCompile().size() << std::endl; mLastRenderTime = mTimer.time_m(); } diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index c054f3bbd6..1be1b3a0ce 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -20,9 +20,9 @@ namespace osg class Texture2D; } -namespace VFS +namespace Resource { - class Manager; + class ResourceSystem; } namespace MWGui @@ -32,7 +32,7 @@ namespace MWGui class LoadingScreen : public WindowBase, public Loading::Listener { public: - LoadingScreen(const VFS::Manager* vfs, osgViewer::Viewer* viewer); + LoadingScreen(Resource::ResourceSystem* resourceSystem, osgViewer::Viewer* viewer); virtual ~LoadingScreen(); /// Overridden from Loading::Listener, see the Loading::Listener documentation for usage details @@ -53,7 +53,7 @@ namespace MWGui void setupCopyFramebufferToTextureCallback(); - const VFS::Manager* mVFS; + Resource::ResourceSystem* mResourceSystem; osg::ref_ptr mViewer; double mTargetFrameRate; @@ -70,6 +70,8 @@ namespace MWGui size_t mProgress; bool mShowWallpaper; + float mOldIcoMin = 0.f; + unsigned int mOldIcoMax = 0; MyGUI::Widget* mLoadingBox; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index ab7c3334ce..e1f7a4fbf3 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -230,7 +230,7 @@ namespace MWGui mKeyboardNavigation->setEnabled(keyboardNav); Gui::ImageButton::setDefaultNeedKeyFocus(keyboardNav); - mLoadingScreen = new LoadingScreen(mResourceSystem->getVFS(), mViewer); + mLoadingScreen = new LoadingScreen(mResourceSystem, mViewer); mWindows.push_back(mLoadingScreen); //set up the hardware cursor manager diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 5f5e7d13b0..6b175239eb 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -7,6 +7,8 @@ #include #include +#include + #include #include "../mwworld/esmstore.hpp" @@ -900,6 +902,12 @@ namespace MWMechanics bool MechanicsManager::toggleAI() { mAI = !mAI; + + MWBase::World* world = MWBase::Environment::get().getWorld(); + world->getNavigator()->setUpdatesEnabled(mAI); + if (mAI) + world->getNavigator()->update(world->getPlayerPtr().getRefData().getPosition().asVec3()); + return mAI; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 7cc0f77706..bbb2ae8d3a 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -397,6 +397,15 @@ namespace MWPhysics return osg::Vec3f(); } + osg::BoundingBox PhysicsSystem::getBoundingBox(const MWWorld::ConstPtr &object) const + { + const Object * physobject = getObject(object); + if (!physobject) return osg::BoundingBox(); + btVector3 min, max; + physobject->getCollisionObject()->getCollisionShape()->getAabb(physobject->getCollisionObject()->getWorldTransform(), min, max); + return osg::BoundingBox(Misc::Convert::toOsg(min), Misc::Convert::toOsg(max)); + } + osg::Vec3f PhysicsSystem::getCollisionObjectPosition(const MWWorld::ConstPtr &actor) const { const Actor* physactor = getActor(actor); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 8b09722afb..32d460b1d8 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -7,6 +7,7 @@ #include #include +#include #include #include "../mwworld/ptr.hpp" @@ -144,6 +145,9 @@ namespace MWPhysics /// @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space. osg::Vec3f getCollisionObjectPosition(const MWWorld::ConstPtr& actor) const; + /// Get bounding box in world space of the given object. + osg::BoundingBox getBoundingBox(const MWWorld::ConstPtr &object) const; + /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will /// be overwritten. Valid until the next call to applyQueuedMovement. void queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 8f8e8c2336..594713a1cf 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -513,6 +513,9 @@ namespace MWRender if (mShadowUniform) stateset->addUniform(mShadowUniform); + stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + stateset->setRenderBinMode(osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); + // FIXME: overriding diffuse/ambient/emissive colors osg::Material* material = new osg::Material; material->setColorMode(osg::Material::OFF); @@ -1369,7 +1372,7 @@ namespace MWRender osg::Group* sheathParent = findVisitor.mFoundNode; if (sheathParent) { - osg::Node* copy = osg::clone(nodePair.first, osg::CopyOp::DEEP_COPY_NODES); + osg::Node* copy = static_cast(nodePair.first->clone(osg::CopyOp::DEEP_COPY_NODES)); sheathParent->addChild(copy); } } @@ -1741,31 +1744,16 @@ namespace MWRender if (mTransparencyUpdater == nullptr) { mTransparencyUpdater = new TransparencyUpdater(alpha, mResourceSystem->getSceneManager()->getShaderManager().getShadowMapAlphaTestEnableUniform()); - mObjectRoot->addUpdateCallback(mTransparencyUpdater); + mObjectRoot->addCullCallback(mTransparencyUpdater); } else mTransparencyUpdater->setAlpha(alpha); } else { - mObjectRoot->removeUpdateCallback(mTransparencyUpdater); + mObjectRoot->removeCullCallback(mTransparencyUpdater); mTransparencyUpdater = nullptr; - mObjectRoot->setStateSet(nullptr); - } - - setRenderBin(); - } - - void Animation::setRenderBin() - { - if (mAlpha != 1.f) - { - osg::StateSet* stateset = mObjectRoot->getOrCreateStateSet(); - stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); - stateset->setRenderBinMode(osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); } - else if (osg::StateSet* stateset = mObjectRoot->getStateSet()) - stateset->setRenderBinToInherit(); } void Animation::setLightEffect(float effect) diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 2890e7be41..c53cf98a95 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -336,9 +336,6 @@ protected: */ virtual void addControllers(); - /// Set the render bin for this animation's object root. May be customized by subclasses. - virtual void setRenderBin(); - public: Animation(const MWWorld::Ptr &ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem); diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 416a753ebc..12401f45ea 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -168,11 +168,10 @@ void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, float width, float height, const osg::Vec3d& upVector, float zmin, float zmax) { osg::ref_ptr camera (new osg::Camera); - camera->setProjectionMatrixAsOrtho(-width/2, width/2, -height/2, height/2, 5, (zmax-zmin) + 10); camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); camera->setViewMatrixAsLookAt(osg::Vec3d(x, y, zmax + 5), osg::Vec3d(x, y, zmin), upVector); - camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); + camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 1.f)); camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -360,11 +359,6 @@ void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell) osg::ref_ptr camera = createOrthographicCamera(x*mMapWorldSize + mMapWorldSize/2.f, y*mMapWorldSize + mMapWorldSize/2.f, mMapWorldSize, mMapWorldSize, osg::Vec3d(0,1,0), zmin, zmax); - camera->getOrCreateUserDataContainer()->addDescription("NoTerrainLod"); - std::ostringstream stream; - stream << x << " " << y; - camera->getOrCreateUserDataContainer()->addDescription(stream.str()); - setupRenderToTexture(camera, cell->getCell()->getGridX(), cell->getCell()->getGridY()); MapSegment& segment = mSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())]; diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index ec825ca2f7..6e7669976e 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -435,12 +435,10 @@ void NpcAnimation::setRenderBin() osgUtil::RenderBin::addRenderBinPrototype("DepthClear", depthClearBin); prototypeAdded = true; } - - osg::StateSet* stateset = mObjectRoot->getOrCreateStateSet(); - stateset->setRenderBinDetails(RenderBin_FirstPerson, "DepthClear", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); + mObjectRoot->getOrCreateStateSet()->setRenderBinDetails(RenderBin_FirstPerson, "DepthClear", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); } - else - Animation::setRenderBin(); + else if (osg::StateSet* stateset = mObjectRoot->getStateSet()) + stateset->setRenderBinToInherit(); } void NpcAnimation::rebuild() diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index e102f5097e..7f8d5434bf 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -88,7 +88,7 @@ private: void addPartGroup(int group, int priority, const std::vector &parts, bool enchantedGlow=false, osg::Vec4f* glowColor=nullptr); - virtual void setRenderBin(); + void setRenderBin(); osg::ref_ptr mFirstPersonNeckController; diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp new file mode 100644 index 0000000000..71268a52c8 --- /dev/null +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -0,0 +1,781 @@ +#include "objectpaging.hpp" + +#include + +#include +#include +#include +#include +#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; + + default: + return false; + } + } + + 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: + return std::string(); + } + } + + osg::ref_ptr ObjectPaging::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) + { + if (activeGrid && !mActiveGrid) + return nullptr; + + ChunkId id = std::make_tuple(center, size, activeGrid); + + osg::ref_ptr obj = mCache->getRefFromObjectCache(id); + if (obj) + return obj->asNode(); + else + { + osg::ref_ptr node = createChunk(size, center, activeGrid, viewPoint, compile); + 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 (node->getDataVariance() != osg::Object::DYNAMIC); + } + }; + + class CopyOp : public osg::CopyOp + { + public: + bool mOptimizeBillboards = true; + float mSqrDistance = 0.f; + osg::Vec3f mViewVector; + mutable std::vector mNodePath; + + void copy(const osg::Node* toCopy, osg::Group* attachTo) + { + const osg::Group* groupToCopy = toCopy->asGroup(); + if (toCopy->getStateSet() || toCopy->asTransform() || !groupToCopy) + attachTo->addChild(operator()(toCopy)); + else + { + for (unsigned int i=0; igetNumChildren(); ++i) + attachTo->addChild(operator()(groupToCopy->getChild(i))); + } + } + + virtual osg::Node* operator() (const osg::Node* node) const + { + if (const osg::Drawable* d = node->asDrawable()) + return operator()(d); + + 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) * lod->getMinRange(i) <= mSqrDistance && mSqrDistance < lod->getMaxRange(i) * lod->getMaxRange(i)) + n->addChild(operator()(lod->getChild(i))); + n->setDataVariance(osg::Object::STATIC); + return n; + } + + mNodePath.push_back(node); + + osg::Node* cloned = static_cast(node->clone(*this)); + cloned->setDataVariance(osg::Object::STATIC); + cloned->setUserDataContainer(nullptr); + cloned->setName(""); + + mNodePath.pop_back(); + + handleCallbacks(node, cloned); + + return cloned; + } + void handleCallbacks(const osg::Node* node, osg::Node *cloned) const + { + for (const osg::Callback* callback = node->getCullCallback(); callback != nullptr; callback = callback->getNestedCallback()) + { + if (callback->className() == std::string("BillboardCallback")) + { + if (mOptimizeBillboards) + { + handleBillboard(cloned); + continue; + } + else + cloned->setDataVariance(osg::Object::DYNAMIC); + } + + if (node->getCullCallback()->getNestedCallback()) + { + osg::Callback *clonedCallback = osg::clone(callback, osg::CopyOp::SHALLOW_COPY); + clonedCallback->setNestedCallback(nullptr); + cloned->addCullCallback(clonedCallback); + } + else + cloned->addCullCallback(const_cast(callback)); + } + } + void handleBillboard(osg::Node* node) const + { + osg::Transform* transform = node->asTransform(); + if (!transform) return; + osg::MatrixTransform* matrixTransform = transform->asMatrixTransform(); + if (!matrixTransform) return; + + osg::Matrix worldToLocal = osg::Matrix::identity(); + for (auto node : mNodePath) + if (const osg::Transform* t = node->asTransform()) + t->computeWorldToLocalMatrix(worldToLocal, nullptr); + worldToLocal = osg::Matrix::orthoNormal(worldToLocal); + + osg::Matrix billboardMatrix; + osg::Vec3f viewVector = -(mViewVector + worldToLocal.getTrans()); + viewVector.normalize(); + osg::Vec3f right = viewVector ^ osg::Vec3f(0,0,1); + right.normalize(); + osg::Vec3f up = right ^ viewVector; + up.normalize(); + billboardMatrix.makeLookAt(osg::Vec3f(0,0,0), viewVector, up); + billboardMatrix.invert(billboardMatrix); + + const osg::Matrix& oldMatrix = matrixTransform->getMatrix(); + float mag[3]; // attempt to preserve scale + for (int i=0;i<3;++i) + mag[i] = std::sqrt(oldMatrix(0,i) * oldMatrix(0,i) + oldMatrix(1,i) * oldMatrix(1,i) + oldMatrix(2,i) * oldMatrix(2,i)); + osg::Matrix newMatrix; + worldToLocal.setTrans(0,0,0); + newMatrix *= worldToLocal; + newMatrix.preMult(billboardMatrix); + newMatrix.preMultScale(osg::Vec3f(mag[0], mag[1], mag[2])); + newMatrix.setTrans(oldMatrix.getTrans()); + + matrixTransform->setMatrix(newMatrix); + } + virtual osg::Drawable* operator() (const osg::Drawable* drawable) const + { + if (dynamic_cast(drawable)) + return nullptr; + + if (const SceneUtil::RigGeometry* rig = dynamic_cast(drawable)) + return operator()(rig->getSourceGeometry()); + if (const SceneUtil::MorphGeometry* morph = dynamic_cast(drawable)) + return operator()(morph->getSourceGeometry()); + + if (getCopyFlags() & DEEP_COPY_DRAWABLES) + { + osg::Drawable* d = static_cast(drawable->clone(*this)); + d->setDataVariance(osg::Object::STATIC); + d->setUserDataContainer(nullptr); + d->setName(""); + return d; + } + else + return const_cast(drawable); + } + virtual osg::Callback* operator() (const osg::Callback* callback) const + { + return nullptr; + } + }; + + class TemplateRef : public osg::Object + { + public: + TemplateRef() {} + TemplateRef(const TemplateRef& copy, const osg::CopyOp&) : mObjects(copy.mObjects) {} + META_Object(MWRender, TemplateRef) + std::vector> mObjects; + }; + + class RefnumSet : public osg::Object + { + public: + RefnumSet(){} + RefnumSet(const RefnumSet& copy, const osg::CopyOp&) : mRefnums(copy.mRefnums) {} + META_Object(MWRender, RefnumSet) + std::set mRefnums; + }; + + class AnalyzeVisitor : public osg::NodeVisitor + { + public: + AnalyzeVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mCurrentStateSet(nullptr) {} + + typedef std::unordered_map StateSetCounter; + struct Result + { + StateSetCounter mStateSetCounter; + unsigned int mNumVerts = 0; + }; + + virtual void apply(osg::Node& node) + { + if (node.getStateSet()) + mCurrentStateSet = node.getStateSet(); + traverse(node); + } + virtual void apply(osg::Geometry& geom) + { + mResult.mNumVerts += geom.getVertexArray()->getNumElements(); + ++mResult.mStateSetCounter[mCurrentStateSet]; + ++mGlobalStateSetCounter[mCurrentStateSet]; + } + Result retrieveResult() + { + Result result = mResult; + mResult = Result(); + mCurrentStateSet = nullptr; + return result; + } + void addInstance(const Result& result) + { + for (auto pair : result.mStateSetCounter) + mGlobalStateSetCounter[pair.first] += pair.second; + } + float getMergeBenefit(const Result& result) + { + if (result.mStateSetCounter.empty()) return 1; + float mergeBenefit = 0; + for (auto pair : result.mStateSetCounter) + { + mergeBenefit += mGlobalStateSetCounter[pair.first]; + } + mergeBenefit /= result.mStateSetCounter.size(); + return mergeBenefit; + } + + Result mResult; + osg::StateSet* mCurrentStateSet; + StateSetCounter mGlobalStateSetCounter; + }; + + class DebugVisitor : public osg::NodeVisitor + { + public: + DebugVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) {} + virtual void apply(osg::Drawable& node) + { + osg::ref_ptr m (new osg::Material); + osg::Vec4f color(Misc::Rng::rollProbability(), Misc::Rng::rollProbability(), Misc::Rng::rollProbability(), 0.f); + color.normalize(); + m->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.1f,0.1f,0.1f,1.f)); + m->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.1f,0.1f,0.1f,1.f)); + m->setColorMode(osg::Material::OFF); + m->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(color)); + osg::ref_ptr stateset = node.getStateSet() ? osg::clone(node.getStateSet(), osg::CopyOp::SHALLOW_COPY) : new osg::StateSet; + stateset->setAttribute(m); + stateset->addUniform(new osg::Uniform("colorMode", 0)); + node.setStateSet(stateset); + } + }; + + class AddRefnumMarkerVisitor : public osg::NodeVisitor + { + public: + AddRefnumMarkerVisitor(const ESM::RefNum &refnum) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mRefnum(refnum) {} + ESM::RefNum mRefnum; + virtual void apply(osg::Geometry &node) + { + osg::ref_ptr marker (new RefnumMarker); + marker->mRefnum = mRefnum; + if (osg::Array* array = node.getVertexArray()) + marker->mNumVertices = array->getNumElements(); + node.getOrCreateUserDataContainer()->addUserObject(marker); + } + }; + + ObjectPaging::ObjectPaging(Resource::SceneManager* sceneManager) + : GenericResourceManager(nullptr) + , mSceneManager(sceneManager) + , mRefTrackerLocked(false) + { + mActiveGrid = Settings::Manager::getBool("object paging active grid", "Terrain"); + mDebugBatches = Settings::Manager::getBool("object paging debug batches", "Terrain"); + mMergeFactor = Settings::Manager::getFloat("object paging merge factor", "Terrain"); + mMinSize = Settings::Manager::getFloat("object paging min size", "Terrain"); + mMinSizeMergeFactor = Settings::Manager::getFloat("object paging min size merge factor", "Terrain"); + mMinSizeCostMultiplier = Settings::Manager::getFloat("object paging min size cost multiplier", "Terrain"); + } + + osg::ref_ptr ObjectPaging::createChunk(float size, const osg::Vec2f& center, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) + { + osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size/2.f), std::floor(center.y() - size/2.f)); + + osg::Vec3f worldCenter = osg::Vec3f(center.x(), center.y(), 0)*ESM::Land::REAL_SIZE; + osg::Vec3f relativeViewPoint = viewPoint - worldCenter; + + std::map 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)) + { + Misc::StringUtils::lowerCaseInPlace(ref.mRefID); + if (std::find(cell->mMovedRefs.begin(), cell->mMovedRefs.end(), ref.mRefNum) != cell->mMovedRefs.end()) continue; + int type = store.findStatic(ref.mRefID); + if (!typeFilter(type,size>=2)) continue; + if (deleted) { refs.erase(ref.mRefNum); continue; } + refs[ref.mRefNum] = 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; + Misc::StringUtils::lowerCaseInPlace(ref.mRefID); + bool deleted = it->second; + if (deleted) { refs.erase(ref.mRefNum); continue; } + int type = store.findStatic(ref.mRefID); + if (!typeFilter(type,size>=2)) continue; + refs[ref.mRefNum] = ref; + } + } + } + + if (activeGrid) + { + OpenThreads::ScopedLock lock(mRefTrackerMutex); + for (auto ref : getRefTracker().mBlacklist) + refs.erase(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)); + struct InstanceList + { + std::vector mInstances; + AnalyzeVisitor::Result mAnalyzeResult; + bool mNeedCompile = false; + }; + typedef std::map, InstanceList> NodeMap; + NodeMap nodes; + osg::ref_ptr refnumSet = activeGrid ? new RefnumSet : nullptr; + AnalyzeVisitor analyzeVisitor; + float minSize = mMinSize; + if (mMinSizeMergeFactor) + minSize *= mMinSizeMergeFactor; + for (const auto& pair : refs) + { + const ESM::CellRef& ref = pair.second; + + osg::Vec3f pos = ref.mPos.asVec3(); + if (size < 1.f) + { + osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE; + if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y()) + || (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) || (minBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y())) + continue; + } + + float dSqr = (viewPoint - pos).length2(); + if (!activeGrid) + { + OpenThreads::ScopedLock lock(mSizeCacheMutex); + SizeCache::iterator found = mSizeCache.find(pair.first); + if (found != mSizeCache.end() && found->second < dSqr*minSize*minSize) + continue; + } + + if (ref.mRefID == "prisonmarker" || ref.mRefID == "divinemarker" || ref.mRefID == "templemarker" || ref.mRefID == "northmarker") + continue; // marker objects that have a hardcoded function in the game logic, should be hidden from the player + + int type = store.findStatic(ref.mRefID); + std::string model = getModel(type, ref.mRefID, store); + if (model.empty()) continue; + model = "meshes/" + model; + + if (activeGrid && type != ESM::REC_STAT) + { + model = Misc::ResourceHelpers::correctActorModelPath(model, mSceneManager->getVFS()); + std::string kfname = Misc::StringUtils::lowerCase(model); + if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) + { + kfname.replace(kfname.size()-4, 4, ".kf"); + if (mSceneManager->getVFS()->exists(kfname)) + continue; + } + } + + osg::ref_ptr cnode = mSceneManager->getTemplate(model, false); + + if (activeGrid) + { + if (cnode->getNumChildrenRequiringUpdateTraversal() > 0 || SceneUtil::hasUserDescription(cnode, Constants::NightDayLabel) || SceneUtil::hasUserDescription(cnode, Constants::HerbalismLabel)) + continue; + else + refnumSet->mRefnums.insert(pair.first); + } + + { + OpenThreads::ScopedLock lock(mRefTrackerMutex); + if (getRefTracker().mDisabled.count(pair.first)) + continue; + } + + float radius2 = cnode->getBound().radius2() * ref.mScale*ref.mScale; + if (radius2 < dSqr*minSize*minSize && !activeGrid) + { + OpenThreads::ScopedLock lock(mSizeCacheMutex); + mSizeCache[pair.first] = radius2; + continue; + } + + auto emplaced = nodes.emplace(cnode, InstanceList()); + if (emplaced.second) + { + const_cast(cnode.get())->accept(analyzeVisitor); // const-trickery required because there is no const version of NodeVisitor + emplaced.first->second.mAnalyzeResult = analyzeVisitor.retrieveResult(); + emplaced.first->second.mNeedCompile = compile && cnode->referenceCount() <= 3; + } + else + analyzeVisitor.addInstance(emplaced.first->second.mAnalyzeResult); + emplaced.first->second.mInstances.push_back(&ref); + } + + osg::ref_ptr group = new osg::Group; + osg::ref_ptr mergeGroup = new osg::Group; + osg::ref_ptr templateRefs = new TemplateRef; + osgUtil::StateToCompile stateToCompile(0, nullptr); + CopyOp copyop; + for (const auto& pair : nodes) + { + const osg::Node* cnode = pair.first; + + const AnalyzeVisitor::Result& analyzeResult = pair.second.mAnalyzeResult; + + float mergeCost = analyzeResult.mNumVerts * size; + float mergeBenefit = analyzeVisitor.getMergeBenefit(analyzeResult) * mMergeFactor; + bool merge = mergeBenefit > mergeCost; + + float minSizeMerged = mMinSize; + float factor2 = mergeBenefit > 0 ? std::min(1.f, mergeCost * mMinSizeCostMultiplier / mergeBenefit) : 1; + float minSizeMergeFactor2 = (1-factor2) * mMinSizeMergeFactor + factor2; + if (minSizeMergeFactor2 > 0) + minSizeMerged *= minSizeMergeFactor2; + + unsigned int numinstances = 0; + for (auto cref : pair.second.mInstances) + { + const ESM::CellRef& ref = *cref; + osg::Vec3f pos = ref.mPos.asVec3(); + + if (!activeGrid && minSizeMerged != minSize && cnode->getBound().radius2() * cref->mScale*cref->mScale < (viewPoint-pos).length2()*minSizeMerged*minSizeMerged) + continue; + + 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->setDataVariance(osg::Object::STATIC); + + copyop.setCopyFlags(merge ? osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES : osg::CopyOp::DEEP_COPY_NODES); + copyop.mOptimizeBillboards = (size > 1/4.f); + copyop.mNodePath.push_back(trans); + copyop.mSqrDistance = (viewPoint - pos).length2(); + copyop.mViewVector = (viewPoint - worldCenter); + copyop.copy(cnode, trans); + + if (activeGrid) + { + if (merge) + { + AddRefnumMarkerVisitor visitor(ref.mRefNum); + trans->accept(visitor); + } + else + { + osg::ref_ptr marker = new RefnumMarker; marker->mRefnum = ref.mRefNum; + trans->getOrCreateUserDataContainer()->addUserObject(marker); + } + } + + osg::Group* attachTo = merge ? mergeGroup : group; + attachTo->addChild(trans); + ++numinstances; + } + if (numinstances > 0) + { + // add a ref to the original template, to hint to the cache that it's still being used and should be kept in cache + templateRefs->mObjects.push_back(cnode); + + if (pair.second.mNeedCompile) + { + int mode = osgUtil::GLObjectsVisitor::COMPILE_STATE_ATTRIBUTES; + if (!merge) + mode |= osgUtil::GLObjectsVisitor::COMPILE_DISPLAY_LISTS; + stateToCompile._mode = mode; + const_cast(cnode)->accept(stateToCompile); + } + } + } + + if (mergeGroup->getNumChildren()) + { + SceneUtil::Optimizer optimizer; + if (size > 1/8.f) + { + 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(mergeGroup, options); + + group->addChild(mergeGroup); + + if (mDebugBatches) + { + DebugVisitor dv; + mergeGroup->accept(dv); + } + if (compile) + { + stateToCompile._mode = osgUtil::GLObjectsVisitor::COMPILE_DISPLAY_LISTS; + mergeGroup->accept(stateToCompile); + } + } + + auto ico = mSceneManager->getIncrementalCompileOperation(); + if (!stateToCompile.empty() && ico) + { + auto compileSet = new osgUtil::IncrementalCompileOperation::CompileSet(group); + compileSet->buildCompileMap(ico->getContextSet(), stateToCompile); + ico->add(compileSet, false); + } + + group->getBound(); + group->setNodeMask(Mask_Static); + osg::UserDataContainer* udc = group->getOrCreateUserDataContainer(); + if (activeGrid) + { + udc->addUserObject(refnumSet); + group->addCullCallback(new SceneUtil::LightListCallback); + } + udc->addUserObject(templateRefs); + + return group; + } + + unsigned int ObjectPaging::getNodeMask() + { + return Mask_Static; + } + + struct ClearCacheFunctor + { + void operator()(MWRender::ChunkId id, osg::Object* obj) + { + if (intersects(id, mPosition)) + mToClear.insert(id); + } + bool intersects(ChunkId id, osg::Vec3f pos) + { + if (mActiveGridOnly && !std::get<2>(id)) return false; + pos /= ESM::Land::REAL_SIZE; + osg::Vec2f center = std::get<0>(id); + float halfSize = std::get<1>(id)/2; + return pos.x() >= center.x()-halfSize && pos.y() >= center.y()-halfSize && pos.x() <= center.x()+halfSize && pos.y() <= center.y()+halfSize; + } + osg::Vec3f mPosition; + std::set mToClear; + bool mActiveGridOnly = false; + }; + + bool ObjectPaging::enableObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos, bool enabled) + { + if (!typeFilter(type, false)) + return false; + + { + OpenThreads::ScopedLock lock(mRefTrackerMutex); + if (enabled && !getWritableRefTracker().mDisabled.erase(refnum)) return false; + if (!enabled && !getWritableRefTracker().mDisabled.insert(refnum).second) return false; + if (mRefTrackerLocked) return false; + } + + ClearCacheFunctor ccf; + ccf.mPosition = pos; + mCache->call(ccf); + if (ccf.mToClear.empty()) return false; + for (auto chunk : ccf.mToClear) + mCache->removeFromObjectCache(chunk); + return true; + } + + bool ObjectPaging::blacklistObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos) + { + if (!typeFilter(type, false)) + return false; + + { + OpenThreads::ScopedLock lock(mRefTrackerMutex); + if (!getWritableRefTracker().mBlacklist.insert(refnum).second) return false; + if (mRefTrackerLocked) return false; + } + + ClearCacheFunctor ccf; + ccf.mPosition = pos; + ccf.mActiveGridOnly = true; + mCache->call(ccf); + if (ccf.mToClear.empty()) return false; + for (auto chunk : ccf.mToClear) + mCache->removeFromObjectCache(chunk); + return true; + } + + + void ObjectPaging::clear() + { + OpenThreads::ScopedLock lock(mRefTrackerMutex); + mRefTrackerNew.mDisabled.clear(); + mRefTrackerNew.mBlacklist.clear(); + mRefTrackerLocked = true; + } + + bool ObjectPaging::unlockCache() + { + if (!mRefTrackerLocked) return false; + { + OpenThreads::ScopedLock lock(mRefTrackerMutex); + mRefTrackerLocked = false; + if (mRefTracker == mRefTrackerNew) + return false; + else + mRefTracker = mRefTrackerNew; + } + mCache->clear(); + return true; + } + + struct GetRefnumsFunctor + { + GetRefnumsFunctor(std::set& output) : mOutput(output) {} + void operator()(MWRender::ChunkId chunkId, osg::Object* obj) + { + if (!std::get<2>(chunkId)) return; + const osg::Vec2f& center = std::get<0>(chunkId); + bool activeGrid = (center.x() > mActiveGrid.x() || center.y() > mActiveGrid.y() || center.x() < mActiveGrid.z() || center.y() < mActiveGrid.w()); + if (!activeGrid) return; + + osg::UserDataContainer* udc = obj->getUserDataContainer(); + if (udc && udc->getNumUserObjects()) + { + RefnumSet* refnums = dynamic_cast(udc->getUserObject(0)); + if (!refnums) return; + mOutput.insert(refnums->mRefnums.begin(), refnums->mRefnums.end()); + } + } + osg::Vec4i mActiveGrid; + std::set& mOutput; + }; + + void ObjectPaging::getPagedRefnums(const osg::Vec4i &activeGrid, std::set &out) + { + GetRefnumsFunctor grf(out); + grf.mActiveGrid = activeGrid; + mCache->call(grf); + } + + void ObjectPaging::reportStats(unsigned int frameNumber, osg::Stats *stats) const + { + stats->setAttribute(frameNumber, "Object Chunk", mCache->getCacheSize()); + } + +} diff --git a/apps/openmw/mwrender/objectpaging.hpp b/apps/openmw/mwrender/objectpaging.hpp new file mode 100644 index 0000000000..c79dd6e066 --- /dev/null +++ b/apps/openmw/mwrender/objectpaging.hpp @@ -0,0 +1,92 @@ +#ifndef OPENMW_COMPONENTS_ESMPAGING_CHUNKMANAGER_H +#define OPENMW_COMPONENTS_ESMPAGING_CHUNKMANAGER_H + +#include +#include +#include + +#include + +namespace Resource +{ + class SceneManager; +} +namespace MWWorld +{ + class ESMStore; +} + +namespace MWRender +{ + + typedef std::tuple ChunkId; // Center, Size, ActiveGrid + + 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 activeGrid, const osg::Vec3f& viewPoint, bool compile) override; + + osg::ref_ptr createChunk(float size, const osg::Vec2f& center, bool activeGrid, const osg::Vec3f& viewPoint, bool compile); + + virtual unsigned int getNodeMask() override; + + /// @return true if view needs rebuild + bool enableObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos, bool enabled); + + /// @return true if view needs rebuild + bool blacklistObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos); + + void clear(); + + /// Must be called after clear() before rendering starts. + /// @return true if view needs rebuild + bool unlockCache(); + + void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; + + void getPagedRefnums(const osg::Vec4i &activeGrid, std::set &out); + + private: + Resource::SceneManager* mSceneManager; + bool mActiveGrid; + bool mDebugBatches; + float mMergeFactor; + float mMinSize; + float mMinSizeMergeFactor; + float mMinSizeCostMultiplier; + + OpenThreads::Mutex mRefTrackerMutex; + struct RefTracker + { + std::set mDisabled; + std::set mBlacklist; + bool operator==(const RefTracker&other) { return mDisabled == other.mDisabled && mBlacklist == other.mBlacklist; } + }; + RefTracker mRefTracker; + RefTracker mRefTrackerNew; + bool mRefTrackerLocked; + + const RefTracker& getRefTracker() const { return mRefTracker; } + RefTracker& getWritableRefTracker() { return mRefTrackerLocked ? mRefTrackerNew : mRefTracker; } + + OpenThreads::Mutex mSizeCacheMutex; + typedef std::map SizeCache; + SizeCache mSizeCache; + }; + + class RefnumMarker : public osg::Object + { + public: + RefnumMarker() : mNumVertices(0) {} + RefnumMarker(const RefnumMarker ©, osg::CopyOp co) : mRefnum(copy.mRefnum), mNumVertices(copy.mNumVertices) {} + META_Object(MWRender, RefnumMarker) + + ESM::RefNum mRefnum; + unsigned int mNumVertices; + }; +} + +#endif diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 462f9fbb6e..d214828942 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 { @@ -258,7 +260,6 @@ namespace MWRender { mViewer->setIncrementalCompileOperation(new osgUtil::IncrementalCompileOperation); mViewer->getIncrementalCompileOperation()->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); - mViewer->getIncrementalCompileOperation()->setMaximumNumOfObjectsToCompilePerFrame(100); } mResourceSystem->getSceneManager()->setIncrementalCompileOperation(mViewer->getIncrementalCompileOperation()); @@ -286,6 +287,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)); @@ -970,20 +977,14 @@ namespace MWRender renderCameraToImage(rttCamera.get(),image,w,h); } - osg::Vec4f RenderingManager::getScreenBounds(const MWWorld::Ptr& ptr) + osg::Vec4f RenderingManager::getScreenBounds(const osg::BoundingBox &worldbb) { - if (!ptr.getRefData().getBaseNode()) - return osg::Vec4f(); - - osg::ComputeBoundsVisitor computeBoundsVisitor; - computeBoundsVisitor.setTraversalMask(~(Mask_ParticleSystem|Mask_Effect)); - ptr.getRefData().getBaseNode()->accept(computeBoundsVisitor); - + if (!worldbb.valid()) return osg::Vec4f(); osg::Matrix viewProj = mViewer->getCamera()->getViewMatrix() * mViewer->getCamera()->getProjectionMatrix(); float min_x = 1.0f, max_x = 0.0f, min_y = 1.0f, max_y = 0.0f; for (int i=0; i<8; ++i) { - osg::Vec3f corner = computeBoundsVisitor.getBoundingBox().corner(i); + osg::Vec3f corner = worldbb.corner(i); corner = corner * viewProj; float x = (corner.x() + 1.f) * 0.5f; @@ -1009,6 +1010,7 @@ namespace MWRender { RenderingManager::RayResult result; result.mHit = false; + result.mHitRefnum.mContentFile = -1; result.mRatio = 0; if (intersector->containsIntersections()) { @@ -1020,6 +1022,7 @@ namespace MWRender result.mRatio = intersection.ratio; PtrHolder* ptrHolder = nullptr; + std::vector refnumMarkers; for (osg::NodePath::const_iterator it = intersection.nodePath.begin(); it != intersection.nodePath.end(); ++it) { osg::UserDataContainer* userDataContainer = (*it)->getUserDataContainer(); @@ -1029,11 +1032,25 @@ namespace MWRender { if (PtrHolder* p = dynamic_cast(userDataContainer->getUserObject(i))) ptrHolder = p; + if (RefnumMarker* r = dynamic_cast(userDataContainer->getUserObject(i))) + refnumMarkers.push_back(r); } } if (ptrHolder) result.mHitObject = ptrHolder->mPtr; + + unsigned int vertexCounter = 0; + for (unsigned int i=0; imNumVertices || (intersectionIndex >= vertexCounter && intersectionIndex < vertexCounter + refnumMarkers[i]->mNumVertices)) + { + result.mHitRefnum = refnumMarkers[i]->mRefnum; + break; + } + vertexCounter += refnumMarkers[i]->mNumVertices; + } } return result; @@ -1046,6 +1063,7 @@ namespace MWRender mIntersectionVisitor = new osgUtil::IntersectionVisitor; mIntersectionVisitor->setTraversalNumber(mViewer->getFrameStamp()->getFrameNumber()); + mIntersectionVisitor->setFrameStamp(mViewer->getFrameStamp()); mIntersectionVisitor->setIntersector(intersector); int mask = ~0; @@ -1111,6 +1129,8 @@ namespace MWRender mSky->setMoonColour(false); notifyWorldSpaceChanged(); + if (mObjectPaging) + mObjectPaging->clear(); } MWRender::Animation* RenderingManager::getAnimation(const MWWorld::Ptr &ptr) @@ -1467,4 +1487,43 @@ namespace MWRender mRecastMesh->update(mNavigator.getRecastMeshTiles(), mNavigator.getSettings()); } + + void RenderingManager::setActiveGrid(const osg::Vec4i &grid) + { + mTerrain->setActiveGrid(grid); + } + bool RenderingManager::pagingEnableObject(int type, const MWWorld::ConstPtr& ptr, bool enabled) + { + if (!ptr.isInCell() || !ptr.getCell()->isExterior() || !mObjectPaging) + return false; + if (mObjectPaging->enableObject(type, ptr.getCellRef().getRefNum(), ptr.getCellRef().getPosition().asVec3(), enabled)) + { + mTerrain->rebuildViews(); + return true; + } + return false; + } + void RenderingManager::pagingBlacklistObject(int type, const MWWorld::ConstPtr &ptr) + { + if (!ptr.isInCell() || !ptr.getCell()->isExterior() || !mObjectPaging) + return; + const ESM::RefNum & refnum = ptr.getCellRef().getRefNum(); + if (!refnum.hasContentFile()) return; + if (mObjectPaging->blacklistObject(type, refnum, ptr.getCellRef().getPosition().asVec3())) + mTerrain->rebuildViews(); + } + bool RenderingManager::pagingUnlockCache() + { + if (mObjectPaging && mObjectPaging->unlockCache()) + { + mTerrain->rebuildViews(); + return true; + } + return false; + } + void RenderingManager::getPagedRefnums(const osg::Vec4i &activeGrid, std::set &out) + { + if (mObjectPaging) + mObjectPaging->getPagedRefnums(activeGrid, out); + } } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 13f09b359e..db4788970e 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -42,6 +42,7 @@ namespace osgViewer namespace ESM { struct Cell; + struct RefNum; } namespace Terrain @@ -84,6 +85,7 @@ namespace MWRender class NavMesh; class ActorsPaths; class RecastMesh; + class ObjectPaging; class RenderingManager : public MWRender::RenderingInterface { @@ -155,6 +157,7 @@ namespace MWRender osg::Vec3f mHitNormalWorld; osg::Vec3f mHitPointWorld; MWWorld::Ptr mHitObject; + ESM::RefNum mHitRefnum; float mRatio; }; @@ -165,7 +168,7 @@ namespace MWRender RayResult castCameraToViewportRay(const float nX, const float nY, float maxDistance, bool ignorePlayer, bool ignoreActors=false); /// Get the bounding box of the given object in screen coordinates as (minX, minY, maxX, maxY), with (0,0) being the top left corner. - osg::Vec4f getScreenBounds(const MWWorld::Ptr& ptr); + osg::Vec4f getScreenBounds(const osg::BoundingBox &worldbb); void setSkyEnabled(bool enabled); @@ -237,6 +240,13 @@ namespace MWRender void setNavMeshNumber(const std::size_t value); + void setActiveGrid(const osg::Vec4i &grid); + + bool pagingEnableObject(int type, const MWWorld::ConstPtr& ptr, bool enabled); + void pagingBlacklistObject(int type, const MWWorld::ConstPtr &ptr); + bool pagingUnlockCache(); + void getPagedRefnums(const osg::Vec4i &activeGrid, std::set &out); + private: void updateProjectionMatrix(); void updateTextureFiltering(); @@ -275,6 +285,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..bee6a957dc 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -65,23 +66,7 @@ namespace MWWorld mTerrainView = mTerrain->createView(); ListModelsVisitor visitor (mMeshes); - if (cell->getState() == MWWorld::CellStore::State_Loaded) - { - cell->forEach(visitor); - } - else - { - const std::vector& objectIds = cell->getPreloadedIds(); - - // could possibly build the model list in the worker thread if we manage to make the Store thread safe - for (const std::string& id : objectIds) - { - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), id); - std::string model = ref.getPtr().getClass().getModel(ref.getPtr()); - if (!model.empty()) - mMeshes.push_back(model); - } - } + cell->forEach(visitor); } virtual void abort() @@ -97,7 +82,7 @@ namespace MWWorld try { mTerrain->cacheCell(mTerrainView.get(), mX, mY); - mPreloadedObjects.push_back(mLandManager->getLand(mX, mY)); + mPreloadedObjects.insert(mLandManager->getLand(mX, mY)); } catch(std::exception& e) { @@ -113,17 +98,7 @@ namespace MWWorld { mesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mSceneManager->getVFS()); - if (mPreloadInstances) - { - mPreloadedObjects.push_back(mSceneManager->cacheInstance(mesh)); - mPreloadedObjects.push_back(mBulletShapeManager->cacheInstance(mesh)); - } - else - { - mPreloadedObjects.push_back(mSceneManager->getTemplate(mesh)); - mPreloadedObjects.push_back(mBulletShapeManager->getShape(mesh)); - } - + bool animated = false; size_t slashpos = mesh.find_last_of("/\\"); if (slashpos != std::string::npos && slashpos != mesh.size()-1) { @@ -134,11 +109,23 @@ namespace MWWorld if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) { kfname.replace(kfname.size()-4, 4, ".kf"); - mPreloadedObjects.push_back(mKeyframeManager->get(kfname)); + if (mSceneManager->getVFS()->exists(kfname)) + { + mPreloadedObjects.insert(mKeyframeManager->get(kfname)); + animated = true; + } } - } } + if (mPreloadInstances && animated) + mPreloadedObjects.insert(mSceneManager->cacheInstance(mesh)); + else + mPreloadedObjects.insert(mSceneManager->getTemplate(mesh)); + if (mPreloadInstances) + mPreloadedObjects.insert(mBulletShapeManager->cacheInstance(mesh)); + else + mPreloadedObjects.insert(mBulletShapeManager->getShape(mesh)); + } catch (std::exception& e) { @@ -166,24 +153,28 @@ namespace MWWorld osg::ref_ptr mTerrainView; // keep a ref to the loaded objects to make sure it stays loaded as long as this cell is in the preloaded state - std::vector > mPreloadedObjects; + std::set > mPreloadedObjects; }; 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) + , mProgress(views.size()) + , mProgressRange(0) , mTerrainViews(views) , mWorld(world) , mPreloadPositions(preloadPositions) { } - void storeViews(double referenceTime) + bool storeViews(double referenceTime) { for (unsigned int i=0; istoreView(mTerrainViews[i], referenceTime); + if (!mWorld->storeView(mTerrainViews[i], referenceTime)) + return false; + return true; } virtual void doWork() @@ -191,7 +182,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, mProgress[i], mProgressRange); } } @@ -200,11 +191,16 @@ namespace MWWorld mAbort = true; } + int getProgress() const { return !mProgress.empty() ? mProgress[0].load() : 0; } + int getProgressRange() const { return !mProgress.empty() && mProgress[0].load() ? mProgressRange : 0; } + private: std::atomic mAbort; + std::vector> mProgress; + int mProgressRange; 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. @@ -237,6 +233,7 @@ namespace MWWorld , mMaxCacheSize(0) , mPreloadInstances(true) , mLastResourceCacheUpdate(0.0) + , mStoreViewsFailCount(0) { } @@ -328,9 +325,6 @@ namespace MWWorld } mPreloadCells.erase(found); - - if (cell->isExterior() && mTerrainPreloadItem && mTerrainPreloadItem->isDone()) - mTerrainPreloadItem->storeViews(0.0); } } @@ -375,7 +369,17 @@ namespace MWWorld if (mTerrainPreloadItem && mTerrainPreloadItem->isDone()) { - mTerrainPreloadItem->storeViews(timestamp); + if (!mTerrainPreloadItem->storeViews(timestamp)) + { + if (++mStoreViewsFailCount > 100) + { + OSG_ALWAYS << "paging views are rebuilt every frame, please check for faulty enable/disable scripts." << std::endl; + mStoreViewsFailCount = 0; + } + setTerrainPreloadPositions(std::vector()); + } + else + mStoreViewsFailCount = 0; mTerrainPreloadItem = nullptr; } } @@ -415,11 +419,71 @@ namespace MWWorld mUnrefQueue = unrefQueue; } - void CellPreloader::setTerrainPreloadPositions(const std::vector &positions) + bool CellPreloader::syncTerrainLoad(const std::vector &positions, int& progress, int& progressRange, double timestamp) { + if (!mTerrainPreloadItem) + return true; + else if (mTerrainPreloadItem->isDone()) + { + if (mTerrainPreloadItem->storeViews(timestamp)) + { + mTerrainPreloadItem = nullptr; + return true; + } + else + { + setTerrainPreloadPositions(std::vector()); + setTerrainPreloadPositions(positions); + return false; + } + } + else + { + progress = mTerrainPreloadItem->getProgress(); + progressRange = mTerrainPreloadItem->getProgressRange(); + return false; + } + } + + void CellPreloader::abortTerrainPreloadExcept(const CellPreloader::PositionCellGrid *exceptPos) + { + const float resetThreshold = ESM::Land::REAL_SIZE; + for (auto pos : mTerrainPreloadPositions) + if (exceptPos && (pos.first-exceptPos->first).length2() < resetThreshold*resetThreshold && pos.second == exceptPos->second) + return; if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) + { + mTerrainPreloadItem->abort(); + mTerrainPreloadItem->waitTillDone(); + } + setTerrainPreloadPositions(std::vector()); + } + + bool contains(const std::vector& container, const std::vector& contained) + { + for (auto pos : contained) + { + bool found = false; + for (auto pos2 : container) + { + if ((pos.first-pos2.first).length2() < 1 && pos.second == pos2.second) + { + found = true; + break; + } + } + if (!found) return false; + } + return true; + } + + void CellPreloader::setTerrainPreloadPositions(const std::vector &positions) + { + if (positions.empty()) + mTerrainPreloadPositions.clear(); + else if (contains(mTerrainPreloadPositions, positions)) return; - else if (positions == mTerrainPreloadPositions) + if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) return; else { @@ -436,8 +500,11 @@ namespace MWWorld } mTerrainPreloadPositions = positions; - mTerrainPreloadItem = new TerrainPreloadItem(mTerrainViews, mTerrain, positions); - mWorkQueue->addWorkItem(mTerrainPreloadItem); + if (!positions.empty()) + { + mTerrainPreloadItem = new TerrainPreloadItem(mTerrainViews, mTerrain, positions); + mWorkQueue->addWorkItem(mTerrainPreloadItem); + } } } diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index 0501a4f9be..e719f2e606 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,11 @@ namespace MWWorld void setUnrefQueue(SceneUtil::UnrefQueue* unrefQueue); - void setTerrainPreloadPositions(const std::vector& positions); + typedef std::pair PositionCellGrid; + void setTerrainPreloadPositions(const std::vector& positions); + + bool syncTerrainLoad(const std::vector &positions, int& progress, int& progressRange, double timestamp); + void abortTerrainPreloadExcept(const PositionCellGrid *exceptPos); private: Resource::ResourceSystem* mResourceSystem; @@ -83,6 +88,7 @@ namespace MWWorld bool mPreloadInstances; double mLastResourceCacheUpdate; + int mStoreViewsFailCount; struct PreloadEntry { @@ -105,7 +111,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/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 599f345b8a..2cda83e17d 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -106,7 +106,7 @@ namespace template void readReferenceCollection (ESM::ESMReader& reader, - MWWorld::CellRefList& collection, const ESM::CellRef& cref, const std::map& contentFileMap) + MWWorld::CellRefList& collection, const ESM::CellRef& cref, const std::map& contentFileMap, MWWorld::CellStore* cellstore) { const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); @@ -141,7 +141,18 @@ namespace if (iter->mRef.getRefNum()==state.mRef.mRefNum && *iter->mRef.getRefIdPtr() == state.mRef.mRefID) { // overwrite existing reference + float oldscale = iter->mRef.getScale(); iter->load (state); + const ESM::Position & oldpos = iter->mRef.getPosition(); + const ESM::Position & newpos = iter->mData.getPosition(); + const MWWorld::Ptr ptr(&*iter, cellstore); + if ((oldscale != iter->mRef.getScale() || oldpos.asVec3() != newpos.asVec3() || oldpos.rot[0] != newpos.rot[0] || oldpos.rot[1] != newpos.rot[1] || oldpos.rot[2] != newpos.rot[2]) && !ptr.getClass().isActor()) + MWBase::Environment::get().getWorld()->moveObject(ptr, newpos.pos[0], newpos.pos[1], newpos.pos[2]); + if (!iter->mData.isEnabled()) + { + iter->mData.enable(); + MWBase::Environment::get().getWorld()->disable(MWWorld::Ptr(&*iter, cellstore)); + } return; } @@ -154,28 +165,6 @@ namespace ref.load (state); collection.mList.push_back (ref); } - - struct SearchByRefNumVisitor - { - MWWorld::LiveCellRefBase* mFound; - ESM::RefNum mRefNumToFind; - - SearchByRefNumVisitor(const ESM::RefNum& toFind) - : mFound(nullptr) - , mRefNumToFind(toFind) - { - } - - bool operator()(const MWWorld::Ptr& ptr) - { - if (ptr.getCellRef().getRefNum() == mRefNumToFind) - { - mFound = ptr.getBase(); - return false; - } - return true; - } - }; } namespace MWWorld @@ -252,9 +241,7 @@ namespace MWWorld throw std::runtime_error("moveTo: can't move object from a non-loaded cell (how did you get this object anyway?)"); // Ensure that the object actually exists in the cell - SearchByRefNumVisitor searchVisitor(object.getCellRef().getRefNum()); - forEach(searchVisitor); - if (!searchVisitor.mFound) + if (searchViaRefNum(object.getCellRef().getRefNum()).isEmpty()) throw std::runtime_error("moveTo: object is not in this cell"); @@ -809,107 +796,107 @@ namespace MWWorld { case ESM::REC_ACTI: - readReferenceCollection (reader, mActivators, cref, contentFileMap); + readReferenceCollection (reader, mActivators, cref, contentFileMap, this); break; case ESM::REC_ALCH: - readReferenceCollection (reader, mPotions, cref, contentFileMap); + readReferenceCollection (reader, mPotions, cref, contentFileMap, this); break; case ESM::REC_APPA: - readReferenceCollection (reader, mAppas, cref, contentFileMap); + readReferenceCollection (reader, mAppas, cref, contentFileMap, this); break; case ESM::REC_ARMO: - readReferenceCollection (reader, mArmors, cref, contentFileMap); + readReferenceCollection (reader, mArmors, cref, contentFileMap, this); break; case ESM::REC_BOOK: - readReferenceCollection (reader, mBooks, cref, contentFileMap); + readReferenceCollection (reader, mBooks, cref, contentFileMap, this); break; case ESM::REC_CLOT: - readReferenceCollection (reader, mClothes, cref, contentFileMap); + readReferenceCollection (reader, mClothes, cref, contentFileMap, this); break; case ESM::REC_CONT: - readReferenceCollection (reader, mContainers, cref, contentFileMap); + readReferenceCollection (reader, mContainers, cref, contentFileMap, this); break; case ESM::REC_CREA: - readReferenceCollection (reader, mCreatures, cref, contentFileMap); + readReferenceCollection (reader, mCreatures, cref, contentFileMap, this); break; case ESM::REC_DOOR: - readReferenceCollection (reader, mDoors, cref, contentFileMap); + readReferenceCollection (reader, mDoors, cref, contentFileMap, this); break; case ESM::REC_INGR: - readReferenceCollection (reader, mIngreds, cref, contentFileMap); + readReferenceCollection (reader, mIngreds, cref, contentFileMap, this); break; case ESM::REC_LEVC: - readReferenceCollection (reader, mCreatureLists, cref, contentFileMap); + readReferenceCollection (reader, mCreatureLists, cref, contentFileMap, this); break; case ESM::REC_LEVI: - readReferenceCollection (reader, mItemLists, cref, contentFileMap); + readReferenceCollection (reader, mItemLists, cref, contentFileMap, this); break; case ESM::REC_LIGH: - readReferenceCollection (reader, mLights, cref, contentFileMap); + readReferenceCollection (reader, mLights, cref, contentFileMap, this); break; case ESM::REC_LOCK: - readReferenceCollection (reader, mLockpicks, cref, contentFileMap); + readReferenceCollection (reader, mLockpicks, cref, contentFileMap, this); break; case ESM::REC_MISC: - readReferenceCollection (reader, mMiscItems, cref, contentFileMap); + readReferenceCollection (reader, mMiscItems, cref, contentFileMap, this); break; case ESM::REC_NPC_: - readReferenceCollection (reader, mNpcs, cref, contentFileMap); + readReferenceCollection (reader, mNpcs, cref, contentFileMap, this); break; case ESM::REC_PROB: - readReferenceCollection (reader, mProbes, cref, contentFileMap); + readReferenceCollection (reader, mProbes, cref, contentFileMap, this); break; case ESM::REC_REPA: - readReferenceCollection (reader, mRepairs, cref, contentFileMap); + readReferenceCollection (reader, mRepairs, cref, contentFileMap, this); break; case ESM::REC_STAT: - readReferenceCollection (reader, mStatics, cref, contentFileMap); + readReferenceCollection (reader, mStatics, cref, contentFileMap, this); break; case ESM::REC_WEAP: - readReferenceCollection (reader, mWeapons, cref, contentFileMap); + readReferenceCollection (reader, mWeapons, cref, contentFileMap, this); break; case ESM::REC_BODY: - readReferenceCollection (reader, mBodyParts, cref, contentFileMap); + readReferenceCollection (reader, mBodyParts, cref, contentFileMap, this); break; default: @@ -931,26 +918,22 @@ namespace MWWorld movedTo.load(reader); // Search for the reference. It might no longer exist if its content file was removed. - SearchByRefNumVisitor visitor(refnum); - forEachInternal(visitor); - - if (!visitor.mFound) + Ptr movedRef = searchViaRefNum(refnum); + if (movedRef.isEmpty()) { Log(Debug::Warning) << "Warning: Dropping moved ref tag for " << refnum.mIndex << " (moved object no longer exists)"; continue; } - MWWorld::LiveCellRefBase* movedRef = visitor.mFound; - CellStore* otherCell = callback->getCellStore(movedTo); if (otherCell == nullptr) { - Log(Debug::Warning) << "Warning: Dropping moved ref tag for " << movedRef->mRef.getRefId() + Log(Debug::Warning) << "Warning: Dropping moved ref tag for " << movedRef.getCellRef().getRefId() << " (target cell " << movedTo.mWorldspace << " no longer exists). Reference moved back to its original location."; // Note by dropping tag the object will automatically re-appear in its original cell, though potentially at inapproriate coordinates. // Restore original coordinates: - movedRef->mData.setPosition(movedRef->mRef.getPosition()); + movedRef.getRefData().setPosition(movedRef.getCellRef().getPosition()); continue; } @@ -961,7 +944,7 @@ namespace MWWorld continue; } - moveTo(MWWorld::Ptr(movedRef, this), otherCell); + moveTo(movedRef, otherCell); } } 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..b311107f47 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -86,8 +87,21 @@ namespace ); } + std::string getModel(const MWWorld::Ptr &ptr, const VFS::Manager *vfs) + { + bool useAnim = ptr.getClass().useAnim(); + std::string model = ptr.getClass().getModel(ptr); + if (useAnim) + model = Misc::ResourceHelpers::correctActorModelPath(model, vfs); + + const std::string &id = ptr.getCellRef().getRefId(); + if (id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker") + model = ""; // marker objects that have a hardcoded function in the game logic, should be hidden from the player + return model; + } + void addObject(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, - MWRender::RenderingManager& rendering) + MWRender::RenderingManager& rendering, std::set& pagedRefs) { if (ptr.getRefData().getBaseNode() || physics.getActor(ptr)) { @@ -96,15 +110,13 @@ namespace } bool useAnim = ptr.getClass().useAnim(); - std::string model = ptr.getClass().getModel(ptr); - if (useAnim) - model = Misc::ResourceHelpers::correctActorModelPath(model, rendering.getResourceSystem()->getVFS()); + std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS()); - std::string id = ptr.getCellRef().getRefId(); - if (id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker") - model = ""; // marker objects that have a hardcoded function in the game logic, should be hidden from the player - - ptr.getClass().insertObjectRendering(ptr, model, rendering); + const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); + if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) + ptr.getClass().insertObjectRendering(ptr, model, rendering); + else + ptr.getRefData().setBaseNode(new SceneUtil::PositionAttitudeTransform); // FIXME remove this when physics code is fixed not to depend on basenode setNodeRotation(ptr, rendering, RotationOrder::direct); ptr.getClass().insertObject (ptr, model, physics); @@ -183,27 +195,6 @@ namespace } } - void updateObjectRotation (const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, - MWRender::RenderingManager& rendering, RotationOrder order) - { - setNodeRotation(ptr, rendering, order); - physics.updateRotation(ptr); - } - - void updateObjectScale(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, - MWRender::RenderingManager& rendering) - { - if (ptr.getRefData().getBaseNode() != nullptr) - { - float scale = ptr.getCellRef().getScale(); - osg::Vec3f scaleVec (scale, scale, scale); - ptr.getClass().adjustScale(ptr, scaleVec, true); - rendering.scaleObject(ptr, scaleVec); - - physics.updateScale(ptr); - } - } - struct InsertVisitor { MWWorld::CellStore& mCell; @@ -276,50 +267,48 @@ namespace namespace MWWorld { - void Scene::updateObjectRotation(const Ptr& ptr, RotationOrder order) + void Scene::removeFromPagedRefs(const Ptr &ptr) { - ::updateObjectRotation(ptr, *mPhysics, mRendering, order); + const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); + if (refnum.hasContentFile() && mPagedRefs.erase(refnum)) + { + if (!ptr.getRefData().getBaseNode()) return; + ptr.getClass().insertObjectRendering(ptr, getModel(ptr, mRendering.getResourceSystem()->getVFS()), mRendering); + setNodeRotation(ptr, mRendering, RotationOrder::direct); + reloadTerrain(); + } } - void Scene::updateObjectScale(const Ptr &ptr) + void Scene::updateObjectPosition(const Ptr &ptr, const osg::Vec3f &pos, bool movePhysics) { - ::updateObjectScale(ptr, *mPhysics, mRendering); + mRendering.moveObject(ptr, pos); + if (movePhysics) + { + mPhysics->updatePosition(ptr); + } } - void Scene::getGridCenter(int &cellX, int &cellY) + void Scene::updateObjectRotation(const Ptr &ptr, RotationOrder order) { - 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; + setNodeRotation(ptr, mRendering, order); + mPhysics->updateRotation(ptr); + } + + void Scene::updateObjectScale(const Ptr &ptr) + { + float scale = ptr.getCellRef().getScale(); + osg::Vec3f scaleVec (scale, scale, scale); + ptr.getClass().adjustScale(ptr, scaleVec, true); + mRendering.scaleObject(ptr, scaleVec); + mPhysics->updateScale(ptr); } void Scene::update (float duration, bool paused) { - mPreloadTimer += duration; - if (mPreloadTimer > 0.1f) - { - preloadCells(0.1f); - mPreloadTimer = 0.f; - } + mPreloader->updateCache(mRendering.getReferenceTime()); + preloadCells(duration); mRendering.update (duration, paused); - - mPreloader->updateCache(mRendering.getReferenceTime()); } void Scene::unloadCell (CellStoreCollection::iterator iter, bool test) @@ -488,6 +477,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(centerX-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,30 +507,13 @@ 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(pos, newCell.x(), newCell.y()); } - void Scene::changeCellGrid (int playerCellX, int playerCellY, bool changeEvent) + void Scene::changeCellGrid (const osg::Vec3f &pos, int playerCellX, int playerCellY, bool changeEvent) { - Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); - Loading::ScopedLoad load(loadingListener); - - int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount(); - std::string loadingExteriorText = "#{sLoadingMessage3}"; - loadingListener->setLabel(loadingExteriorText, false, messagesCount > 0); - CellStoreCollection::iterator active = mActiveCells.begin(); while (active!=mActiveCells.end()) { @@ -537,6 +530,14 @@ namespace MWWorld unloadCell (active++); } + mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY); + osg::Vec4i newGrid = gridCenterToBounds(mCurrentGridCenter); + mRendering.setActiveGrid(newGrid); + + preloadTerrain(pos, true); + mPagedRefs.clear(); + mRendering.getPagedRefnums(newGrid, mPagedRefs); + std::size_t refsToLoad = 0; std::vector> cellsPositionsToLoad; // get the number of refs to load @@ -565,6 +566,11 @@ namespace MWWorld } } + Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); + Loading::ScopedLoad load(loadingListener); + int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount(); + std::string loadingExteriorText = "#{sLoadingMessage3}"; + loadingListener->setLabel(loadingExteriorText, false, messagesCount > 0); loadingListener->setProgressRange(refsToLoad); const auto getDistanceToPlayerCell = [&] (const std::pair& cellPosition) @@ -745,13 +751,12 @@ namespace MWWorld MWBase::Environment::get().getWorld()->adjustSky(); - mLastPlayerPos = pos.asVec3(); + mLastPlayerPos = player.getRefData().getPosition().asVec3(); } Scene::Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics, DetourNavigator::Navigator& navigator) : mCurrentCell (0), mCellChanged (false), mPhysics(physics), mRendering(rendering), mNavigator(navigator) - , mPreloadTimer(0.f) , mHalfGridSize(Settings::Manager::getInt("exterior cell load distance", "Cells")) , mCellLoadingThreshold(1024.f) , mPreloadDistance(Settings::Manager::getInt("preload distance", "Cells")) @@ -828,6 +833,7 @@ namespace MWWorld loadingListener->setProgressRange(cell->count()); // Load cell. + mPagedRefs.clear(); loadCell (cell, loadingListener, changeEvent); changePlayerCell(cell, position, adjustPlayerPos); @@ -857,7 +863,7 @@ namespace MWWorld if (changeEvent) MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5); - changeCellGrid(x, y, changeEvent); + changeCellGrid(position.asVec3(), x, y, changeEvent); CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y); changePlayerCell(current, position, adjustPlayerPos); @@ -880,7 +886,7 @@ namespace MWWorld { InsertVisitor insertVisitor (cell, *loadingListener, test); cell.forEach (insertVisitor); - insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering); }); + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs); }); insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order @@ -892,7 +898,7 @@ namespace MWWorld { try { - addObject(ptr, *mPhysics, mRendering); + addObject(ptr, *mPhysics, mRendering, mPagedRefs); addObject(ptr, *mPhysics, mNavigator); MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); @@ -924,6 +930,7 @@ namespace MWWorld mRendering.removeObject (ptr); if (ptr.getClass().isActor()) mRendering.removeWaterRippleEmitter(ptr); + ptr.getRefData().setBaseNode(nullptr); } bool Scene::isCellActive(const CellStore &cell) @@ -983,7 +990,8 @@ namespace MWWorld void Scene::preloadCells(float dt) { - std::vector exteriorPositions; + if (dt<=1e-06) return; + std::vector exteriorPositions; const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); @@ -991,7 +999,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 +1016,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 +1050,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 +1070,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); @@ -1108,11 +1116,41 @@ namespace MWWorld mPreloader->preload(cell, mRendering.getReferenceTime()); } - void Scene::preloadTerrain(const osg::Vec3f &pos) + void Scene::preloadTerrain(const osg::Vec3f &pos, bool sync) { - std::vector vec; - vec.push_back(pos); + std::vector vec; + vec.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos))); + if (sync && mRendering.pagingUnlockCache()) + mPreloader->abortTerrainPreloadExcept(nullptr); + else + mPreloader->abortTerrainPreloadExcept(&vec[0]); mPreloader->setTerrainPreloadPositions(vec); + if (!sync) return; + + Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); + Loading::ScopedLoad load(loadingListener); + int progress = 0, initialProgress = -1, progressRange = 0; + while (!mPreloader->syncTerrainLoad(vec, progress, progressRange, mRendering.getReferenceTime())) + { + if (initialProgress == -1) + { + loadingListener->setLabel("#{sLoadingMessage4}"); + initialProgress = progress; + } + if (progress) + { + loadingListener->setProgressRange(std::max(0, progressRange-initialProgress)); + loadingListener->setProgress(progress-initialProgress); + } + else + loadingListener->setProgress(0); + OpenThreads::Thread::microSleep(5000); + } + } + + void Scene::reloadTerrain() + { + mPreloader->setTerrainPreloadPositions(std::vector()); } struct ListFastTravelDestinationsVisitor @@ -1145,7 +1183,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 +1204,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..b9a39779de 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" @@ -72,7 +75,6 @@ namespace MWWorld MWRender::RenderingManager& mRendering; DetourNavigator::Navigator& mNavigator; std::unique_ptr mPreloader; - float mPreloadTimer; int mHalfGridSize; float mCellLoadingThreshold; float mPreloadDistance; @@ -85,17 +87,23 @@ namespace MWWorld osg::Vec3f mLastPlayerPos; + std::set mPagedRefs; + 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 changeCellGrid (const osg::Vec3f &pos, 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: @@ -105,7 +113,8 @@ namespace MWWorld ~Scene(); void preloadCell(MWWorld::CellStore* cell, bool preloadSurrounding=false); - void preloadTerrain(const osg::Vec3f& pos); + void preloadTerrain(const osg::Vec3f& pos, bool sync=false); + void reloadTerrain(); void unloadCell (CellStoreCollection::iterator iter, bool test = false); @@ -143,8 +152,11 @@ namespace MWWorld void removeObjectFromScene (const Ptr& ptr); ///< Remove an object from the scene, but not from the world model. + void removeFromPagedRefs(const Ptr &ptr); + void updateObjectRotation(const Ptr& ptr, RotationOrder order); void updateObjectScale(const Ptr& ptr); + void updateObjectPosition(const Ptr &ptr, const osg::Vec3f &pos, bool movePhysics); bool isCellActive(const CellStore &cell); diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index a414585efe..4c44175879 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -139,18 +139,26 @@ namespace MWWorld std::string idLower = Misc::StringUtils::lowerCase(id); typename Dynamic::const_iterator dit = mDynamic.find(idLower); - if (dit != mDynamic.end()) { + if (dit != mDynamic.end()) return &dit->second; - } typename std::map::const_iterator it = mStatic.find(idLower); + if (it != mStatic.end()) + return &(it->second); - if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { + 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()) return &(it->second); - } return 0; } + template bool Store::isDynamic(const std::string &id) const { @@ -276,7 +284,7 @@ namespace MWWorld typename std::map::iterator it = mStatic.find(idLower); - if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { + if (it != mStatic.end()) { // delete from the static part of mShared typename std::vector::iterator sharedIter = mShared.begin(); typename std::vector::iterator end = sharedIter + mStatic.size(); @@ -553,7 +561,7 @@ namespace MWWorld std::map::const_iterator it = mInt.find(cell.mName); - if (it != mInt.end() && Misc::StringUtils::ciEqual(it->second.mName, id)) { + if (it != mInt.end()) { return &(it->second); } @@ -582,6 +590,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); @@ -1104,9 +1124,8 @@ namespace MWWorld { auto it = mStatic.find(Misc::StringUtils::lowerCase(id)); - if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { + if (it != mStatic.end()) mStatic.erase(it); - } return true; } 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/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index c51266bab6..12521f0591 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -243,7 +243,6 @@ namespace MWWorld if (bypass && !mStartCell.empty()) { ESM::Position pos; - if (findExteriorPosition (mStartCell, pos)) { changeToExteriorCell (pos, true); @@ -384,9 +383,9 @@ namespace MWWorld mPlayer->readRecord(reader, type); if (getPlayerPtr().isInCell()) { - mWorldScene->preloadCell(getPlayerPtr().getCell(), true); if (getPlayerPtr().getCell()->isExterior()) mWorldScene->preloadTerrain(getPlayerPtr().getRefData().getPosition().asVec3()); + mWorldScene->preloadCell(getPlayerPtr().getCell(), true); } break; default: @@ -814,6 +813,13 @@ namespace MWWorld if(mWorldScene->getActiveCells().find (reference.getCell()) != mWorldScene->getActiveCells().end() && reference.getRefData().getCount()) mWorldScene->addObjectToScene (reference); + + if (reference.getCellRef().getRefNum().hasContentFile()) + { + int type = mStore.find(Misc::StringUtils::lowerCase(reference.getCellRef().getRefId())); + if (mRendering->pagingEnableObject(type, reference, true)) + mWorldScene->reloadTerrain(); + } } } @@ -838,20 +844,27 @@ namespace MWWorld void World::disable (const Ptr& reference) { + if (!reference.getRefData().isEnabled()) + return; + // disable is a no-op for items in containers if (!reference.isInCell()) return; - if (reference.getRefData().isEnabled()) - { - if (reference == getPlayerPtr()) - throw std::runtime_error("can not disable player object"); + if (reference == getPlayerPtr()) + throw std::runtime_error("can not disable player object"); - reference.getRefData().disable(); + reference.getRefData().disable(); - if(mWorldScene->getActiveCells().find (reference.getCell())!=mWorldScene->getActiveCells().end() && reference.getRefData().getCount()) - mWorldScene->removeObjectFromScene (reference); + if (reference.getCellRef().getRefNum().hasContentFile()) + { + int type = mStore.find(Misc::StringUtils::lowerCase(reference.getCellRef().getRefId())); + if (mRendering->pagingEnableObject(type, reference, false)) + mWorldScene->reloadTerrain(); } + + if(mWorldScene->getActiveCells().find (reference.getCell())!=mWorldScene->getActiveCells().end() && reference.getRefData().getCount()) + mWorldScene->removeObjectFromScene (reference); } void World::advanceTime (double hours, bool incremental) @@ -1191,20 +1204,22 @@ namespace MWWorld } if (haveToMove && newPtr.getRefData().getBaseNode()) { - mRendering->moveObject(newPtr, vec); + mWorldScene->updateObjectPosition(newPtr, vec, movePhysics); if (movePhysics) { - mPhysics->updatePosition(newPtr); - mPhysics->updatePtr(ptr, newPtr); - - if (const auto object = mPhysics->getObject(newPtr)) + if (const auto object = mPhysics->getObject(ptr)) updateNavigatorObject(object); } } + if (isPlayer) - { mWorldScene->playerMoved(vec); + else + { + mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr); + mWorldScene->removeFromPagedRefs(newPtr); } + return newPtr; } @@ -1233,9 +1248,15 @@ namespace MWWorld if (mPhysics->getActor(ptr)) mNavigator->removeAgent(getPathfindingHalfExtents(ptr)); - ptr.getCellRef().setScale(scale); + if (scale != ptr.getCellRef().getScale()) + { + ptr.getCellRef().setScale(scale); + mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr); + mWorldScene->removeFromPagedRefs(ptr); + } - mWorldScene->updateObjectScale(ptr); + if(ptr.getRefData().getBaseNode() != 0) + mWorldScene->updateObjectScale(ptr); if (mPhysics->getActor(ptr)) mNavigator->addAgent(getPathfindingHalfExtents(ptr)); @@ -1279,6 +1300,9 @@ namespace MWWorld ptr.getRefData().setPosition(pos); + mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr); + mWorldScene->removeFromPagedRefs(ptr); + if(ptr.getRefData().getBaseNode() != 0) { const auto order = flags & MWBase::RotationFlag_inverseOrder @@ -1910,8 +1934,15 @@ namespace MWWorld // retrieve object dimensions so we know where to place the floating label if (!object.isEmpty ()) { - osg::Vec4f screenBounds = mRendering->getScreenBounds(object); - + osg::BoundingBox bb = mPhysics->getBoundingBox(object); + if (!bb.valid() && object.getRefData().getBaseNode()) + { + osg::ComputeBoundsVisitor computeBoundsVisitor; + computeBoundsVisitor.setTraversalMask(~(MWRender::Mask_ParticleSystem|MWRender::Mask_Effect)); + object.getRefData().getBaseNode()->accept(computeBoundsVisitor); + bb = computeBoundsVisitor.getBoundingBox(); + } + osg::Vec4f screenBounds = mRendering->getScreenBounds(bb); MWBase::Environment::get().getWindowManager()->setFocusObjectScreenCoords( screenBounds.x(), screenBounds.y(), screenBounds.z(), screenBounds.w()); } @@ -1941,6 +1972,14 @@ namespace MWWorld rayToObject = mRendering->castCameraToViewportRay(0.5f, 0.5f, maxDistance, ignorePlayer); facedObject = rayToObject.mHitObject; + if (facedObject.isEmpty() && rayToObject.mHitRefnum.hasContentFile()) + { + for (CellStore* cellstore : mWorldScene->getActiveCells()) + { + facedObject = cellstore->searchViaRefNum(rayToObject.mHitRefnum); + if (!facedObject.isEmpty()) break; + } + } if (rayToObject.mHit) mDistanceToFacedObject = (rayToObject.mRatio * maxDistance) - camDist; else @@ -3552,6 +3591,8 @@ namespace MWWorld std::string World::exportSceneGraph(const Ptr &ptr) { std::string file = mUserDataPath + "/openmw.osgt"; + mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr); + mWorldScene->removeFromPagedRefs(ptr); mRendering->exportSceneGraph(ptr, file, "Ascii"); return file; } diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 391c63022a..3e7f541783 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -155,6 +155,11 @@ namespace DetourNavigator */ virtual void update(const osg::Vec3f& playerPosition) = 0; + /** + * @brief disable navigator updates + */ + virtual void setUpdatesEnabled(bool enabled) = 0; + /** * @brief wait locks thread until all tiles are updated from last update call. */ diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index d7889629eb..c47cf97662 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -12,6 +12,7 @@ namespace DetourNavigator NavigatorImpl::NavigatorImpl(const Settings& settings) : mSettings(settings) , mNavMeshManager(mSettings) + , mUpdatesEnabled(true) { } @@ -138,11 +139,18 @@ namespace DetourNavigator void NavigatorImpl::update(const osg::Vec3f& playerPosition) { + if (!mUpdatesEnabled) + return; removeUnusedNavMeshes(); for (const auto& v : mAgents) mNavMeshManager.update(playerPosition, v.first); } + void NavigatorImpl::setUpdatesEnabled(bool enabled) + { + mUpdatesEnabled = enabled; + } + void NavigatorImpl::wait() { mNavMeshManager.wait(); diff --git a/components/detournavigator/navigatorimpl.hpp b/components/detournavigator/navigatorimpl.hpp index 3a81adb975..a1d66463c1 100644 --- a/components/detournavigator/navigatorimpl.hpp +++ b/components/detournavigator/navigatorimpl.hpp @@ -46,6 +46,8 @@ namespace DetourNavigator void update(const osg::Vec3f& playerPosition) override; + void setUpdatesEnabled(bool enabled) override; + void wait() override; SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& agentHalfExtents) const override; @@ -61,6 +63,7 @@ namespace DetourNavigator private: Settings mSettings; NavMeshManager mNavMeshManager; + bool mUpdatesEnabled; std::map mAgents; std::unordered_map mAvoidIds; std::unordered_map mWaterIds; diff --git a/components/detournavigator/navigatorstub.hpp b/components/detournavigator/navigatorstub.hpp index 9c379338ff..9279e77e3a 100644 --- a/components/detournavigator/navigatorstub.hpp +++ b/components/detournavigator/navigatorstub.hpp @@ -66,6 +66,8 @@ namespace DetourNavigator void update(const osg::Vec3f& /*playerPosition*/) override {} + void setUpdatesEnabled(bool enabled) override {} + void wait() override {} SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& /*agentHalfExtents*/) const override diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index ab6ba27547..4b9852d656 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -232,19 +232,3 @@ void ESM::CellRef::blank() mPos.rot[i] = 0; } } - -bool ESM::operator== (const RefNum& left, const RefNum& right) -{ - return left.mIndex==right.mIndex && left.mContentFile==right.mContentFile; -} - -bool ESM::operator< (const RefNum& left, const RefNum& right) -{ - if (left.mIndexright.mIndex) - return false; - - return left.mContentFileright.mIndex) + return false; + return left.mContentFilemKeyList, 1.f) + , mBaseMaterial(baseMaterial) { } @@ -365,14 +366,13 @@ AlphaController::AlphaController() AlphaController::AlphaController(const AlphaController ©, const osg::CopyOp ©op) : StateSetUpdater(copy, copyop), Controller(copy) , mData(copy.mData) + , mBaseMaterial(copy.mBaseMaterial) { } void AlphaController::setDefaults(osg::StateSet *stateset) { - // need to create a deep copy of StateAttributes we will modify - osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); - stateset->setAttribute(osg::clone(mat, osg::CopyOp::DEEP_COPY_ALL), osg::StateAttribute::ON); + stateset->setAttribute(static_cast(mBaseMaterial->clone(osg::CopyOp::DEEP_COPY_ALL)), osg::StateAttribute::ON); } void AlphaController::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) @@ -387,9 +387,10 @@ void AlphaController::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) } } -MaterialColorController::MaterialColorController(const Nif::NiPosData *data, TargetColor color) +MaterialColorController::MaterialColorController(const Nif::NiPosData *data, TargetColor color, const osg::Material* baseMaterial) : mData(data->mKeyList, osg::Vec3f(1,1,1)) , mTargetColor(color) + , mBaseMaterial(baseMaterial) { } @@ -401,14 +402,13 @@ MaterialColorController::MaterialColorController(const MaterialColorController & : StateSetUpdater(copy, copyop), Controller(copy) , mData(copy.mData) , mTargetColor(copy.mTargetColor) + , mBaseMaterial(copy.mBaseMaterial) { } void MaterialColorController::setDefaults(osg::StateSet *stateset) { - // need to create a deep copy of StateAttributes we will modify - osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); - stateset->setAttribute(osg::clone(mat, osg::CopyOp::DEEP_COPY_ALL), osg::StateAttribute::ON); + stateset->setAttribute(static_cast(mBaseMaterial->clone(osg::CopyOp::DEEP_COPY_ALL)), osg::StateAttribute::ON); } void MaterialColorController::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index 3f66013a22..c81f97a714 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -24,6 +24,7 @@ namespace osg { class Node; class StateSet; + class Material; } namespace osgParticle @@ -268,9 +269,9 @@ namespace NifOsg { private: FloatInterpolator mData; - + osg::ref_ptr mBaseMaterial; public: - AlphaController(const Nif::NiFloatData *data); + AlphaController(const Nif::NiFloatData *data, const osg::Material* baseMaterial); AlphaController(); AlphaController(const AlphaController& copy, const osg::CopyOp& copyop); @@ -291,7 +292,7 @@ namespace NifOsg Specular = 2, Emissive = 3 }; - MaterialColorController(const Nif::NiPosData *data, TargetColor color); + MaterialColorController(const Nif::NiPosData *data, TargetColor color, const osg::Material* baseMaterial); MaterialColorController(); MaterialColorController(const MaterialColorController& copy, const osg::CopyOp& copyop); @@ -304,6 +305,7 @@ namespace NifOsg private: Vec3Interpolator mData; TargetColor mTargetColor = Ambient; + osg::ref_ptr mBaseMaterial; }; class FlipController : public SceneUtil::StateSetUpdater, public SceneUtil::Controller diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index bff4147076..88e89b4005 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -639,7 +639,15 @@ namespace NifOsg handleParticleSystem(nifNode, node, composite, animflags, rootNode); if (composite->getNumControllers() > 0) - node->addUpdateCallback(composite); + { + osg::Callback *cb = composite; + if (composite->getNumControllers() == 1) + cb = composite->getController(0); + if (animflags & Nif::NiNode::AnimFlag_AutoPlay) + node->addCullCallback(cb); + else + node->addUpdateCallback(cb); // have to remain as UpdateCallback so AssignControllerSourcesVisitor can find it. + } bool isAnimated = false; handleNodeControllers(nifNode, node, animflags, isAnimated); @@ -778,7 +786,7 @@ namespace NifOsg } } - void handleMaterialControllers(const Nif::Property *materialProperty, SceneUtil::CompositeStateSetUpdater* composite, int animflags) + void handleMaterialControllers(const Nif::Property *materialProperty, SceneUtil::CompositeStateSetUpdater* composite, int animflags, const osg::Material* baseMaterial) { for (Nif::ControllerPtr ctrl = materialProperty->controller; !ctrl.empty(); ctrl = ctrl->next) { @@ -789,7 +797,7 @@ namespace NifOsg const Nif::NiAlphaController* alphactrl = static_cast(ctrl.getPtr()); if (alphactrl->data.empty()) continue; - osg::ref_ptr osgctrl(new AlphaController(alphactrl->data.getPtr())); + osg::ref_ptr osgctrl(new AlphaController(alphactrl->data.getPtr(), baseMaterial)); setupController(alphactrl, osgctrl, animflags); composite->addController(osgctrl); } @@ -799,7 +807,7 @@ namespace NifOsg if (matctrl->data.empty()) continue; auto targetColor = static_cast(matctrl->targetColor); - osg::ref_ptr osgctrl(new MaterialColorController(matctrl->data.getPtr(), targetColor)); + osg::ref_ptr osgctrl(new MaterialColorController(matctrl->data.getPtr(), targetColor, baseMaterial)); setupController(matctrl, osgctrl, animflags); composite->addController(osgctrl); } @@ -1767,7 +1775,7 @@ namespace NifOsg if (!matprop->controller.empty()) { hasMatCtrl = true; - handleMaterialControllers(matprop, composite, animflags); + handleMaterialControllers(matprop, composite, animflags, mat); } break; diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 1b1e469bc2..804a8f8ab8 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -277,7 +277,7 @@ Emitter::Emitter(const Emitter ©, const osg::CopyOp ©op) , mPlacer(copy.mPlacer) , mShooter(copy.mShooter) // need a deep copy because the remainder is stored in the object - , mCounter(osg::clone(copy.mCounter.get(), osg::CopyOp::DEEP_COPY_ALL)) + , mCounter(static_cast(copy.mCounter->clone(osg::CopyOp::DEEP_COPY_ALL))) { } diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index cfd41f19cf..24d7d04a97 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -161,13 +161,13 @@ class GenericObjectCache : public osg::Referenced } } - /** call operator()(osg::Object*) for each object in the cache. */ + /** call operator()(KeyType, osg::Object*) for each object in the cache. */ template void call(Functor& f) { OpenThreads::ScopedLock lock(_objectCacheMutex); for (typename ObjectCacheMap::iterator it = _objectCache.begin(); it != _objectCache.end(); ++it) - f(it->second.first.get()); + f(it->first, it->second.first.get()); } /** Get the number of objects in the cache. */ diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 61a40ee4b3..d06a072734 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(); @@ -577,7 +577,7 @@ namespace Resource osg::ref_ptr SceneManager::createInstance(const osg::Node *base) { - osg::ref_ptr cloned = osg::clone(base, SceneUtil::CopyOp()); + osg::ref_ptr cloned = static_cast(base->clone(SceneUtil::CopyOp())); // add a ref to the original template, to hint to the cache that it's still being used and should be kept in cache cloned->getOrCreateUserDataContainer()->addUserObject(new TemplateRef(base)); @@ -713,6 +713,24 @@ namespace Resource mSharedStateMutex.lock(); mSharedStateManager->prune(); mSharedStateMutex.unlock(); + + if (mIncrementalCompileOperation) + { + OpenThreads::ScopedLock lock(*mIncrementalCompileOperation->getToCompiledMutex()); + osgUtil::IncrementalCompileOperation::CompileSets& sets = mIncrementalCompileOperation->getToCompile(); + for(osgUtil::IncrementalCompileOperation::CompileSets::iterator it = sets.begin(); it != sets.end();) + { + int refcount = (*it)->_subgraphToCompile->referenceCount(); + if ((*it)->_subgraphToCompile->asDrawable()) refcount -= 1; // ref by CompileList. + if (refcount <= 2) // ref by ObjectCache + ref by _subgraphToCompile. + { + // no other ref = not needed anymore. + it = sets.erase(it); + } + else + ++it; + } + } } 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/resource/stats.cpp b/components/resource/stats.cpp index 0dd52ffb6a..2dda2c2dfa 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -292,6 +292,7 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) "Nif", "Keyframe", "", + "Object Chunk", "Terrain Chunk", "Terrain Texture", "Land", diff --git a/components/sceneutil/clone.cpp b/components/sceneutil/clone.cpp index 0df0f4a5b2..c3261515d6 100644 --- a/components/sceneutil/clone.cpp +++ b/components/sceneutil/clone.cpp @@ -22,20 +22,11 @@ namespace SceneUtil | osg::CopyOp::DEEP_COPY_USERDATA); } - osg::StateSet* CopyOp::operator ()(const osg::StateSet* stateset) const - { - if (!stateset) - return nullptr; - if (stateset->getDataVariance() == osg::StateSet::DYNAMIC) - return osg::clone(stateset, *this); - return const_cast(stateset); - } - osg::Object* CopyOp::operator ()(const osg::Object* node) const { // We should copy node transformations when we copy node - if (const NifOsg::NodeUserData* data = dynamic_cast(node)) - return osg::clone(data, *this); + if (dynamic_cast(node)) + return static_cast(node->clone(*this)); return osg::CopyOp::operator()(node); } @@ -60,7 +51,7 @@ namespace SceneUtil if (dynamic_cast(drawable) || dynamic_cast(drawable)) { - return osg::clone(drawable, *this); + return static_cast(drawable->clone(*this)); } return osg::CopyOp::operator()(drawable); @@ -68,7 +59,7 @@ namespace SceneUtil osgParticle::ParticleProcessor* CopyOp::operator() (const osgParticle::ParticleProcessor* processor) const { - osgParticle::ParticleProcessor* cloned = osg::clone(processor, osg::CopyOp::DEEP_COPY_CALLBACKS); + osgParticle::ParticleProcessor* cloned = static_cast(processor->clone(osg::CopyOp::DEEP_COPY_CALLBACKS)); for (const auto& oldPsNewPsPair : mOldPsToNewPs) { if (processor->getParticleSystem() == oldPsNewPsPair.first) @@ -84,7 +75,7 @@ namespace SceneUtil osgParticle::ParticleSystem* CopyOp::operator ()(const osgParticle::ParticleSystem* partsys) const { - osgParticle::ParticleSystem* cloned = osg::clone(partsys, *this); + osgParticle::ParticleSystem* cloned = static_cast(partsys->clone(*this)); for (const auto& processorPsPair : mProcessorToOldPs) { diff --git a/components/sceneutil/clone.hpp b/components/sceneutil/clone.hpp index 20788799fa..cf6d79e683 100644 --- a/components/sceneutil/clone.hpp +++ b/components/sceneutil/clone.hpp @@ -17,7 +17,6 @@ namespace SceneUtil /// @par Defines the cloning behaviour we need: /// * Assigns updated ParticleSystem pointers on cloned emitters and programs. - /// * Creates deep copy of StateSets if they have a DYNAMIC data variance. /// * Deep copies RigGeometry and MorphGeometry so they can animate without affecting clones. /// @warning Do not use an object of this class for more than one copy operation. class CopyOp : public osg::CopyOp @@ -31,7 +30,6 @@ namespace SceneUtil virtual osg::Node* operator() (const osg::Node* node) const; virtual osg::Drawable* operator() (const osg::Drawable* drawable) const; - virtual osg::StateSet* operator() (const osg::StateSet* stateset) const; virtual osg::Object* operator ()(const osg::Object* node) const; private: diff --git a/components/sceneutil/morphgeometry.cpp b/components/sceneutil/morphgeometry.cpp index 01bb35c455..04fd6fb365 100644 --- a/components/sceneutil/morphgeometry.cpp +++ b/components/sceneutil/morphgeometry.cpp @@ -44,7 +44,7 @@ void MorphGeometry::setSourceGeometry(osg::ref_ptr sourceGeom) osg::ref_ptr vbo (new osg::VertexBufferObject); vbo->setUsage(GL_DYNAMIC_DRAW_ARB); - osg::ref_ptr vertexArray = osg::clone(from.getVertexArray(), osg::CopyOp::DEEP_COPY_ALL); + osg::ref_ptr vertexArray = static_cast(from.getVertexArray()->clone(osg::CopyOp::DEEP_COPY_ALL)); if (vertexArray) { vertexArray->setVertexBufferObject(vbo); diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index cb3a1b278b..98771bbb42 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include @@ -1580,7 +1581,9 @@ void MWShadowTechnique::createShaders() _shadowCastingStateSet->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON); _shadowCastingStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", false)); _shadowCastingStateSet->addUniform(_shadowMapAlphaTestDisableUniform); - + osg::ref_ptr depth = new osg::Depth; + depth->setWriteMask(true); + _shadowCastingStateSet->setAttribute(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); _shadowCastingStateSet->setMode(GL_DEPTH_CLAMP, osg::StateAttribute::ON); _shadowCastingStateSet->setRenderBinDetails(osg::StateSet::OPAQUE_BIN, "RenderBin", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS); diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index 487126627c..e8a9451143 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -18,6 +18,7 @@ #include "optimizer.hpp" +#include #include #include #include @@ -27,6 +28,7 @@ #include #include #include +#include #include #include @@ -103,7 +105,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(); @@ -585,17 +589,36 @@ void Optimizer::FlattenStaticTransformsVisitor::apply(osg::Node& node) traverse(node); } +bool needvbo(const osg::Geometry* geom) +{ +#if OSG_MIN_VERSION_REQUIRED(3,5,6) + return true; +#else + return geom->getUseVertexBufferObjects(); +#endif +} + +osg::Array* cloneArray(osg::Array* array, osg::VertexBufferObject*& vbo, const osg::Geometry* geom) +{ + array = static_cast(array->clone(osg::CopyOp::DEEP_COPY_ALL)); + if (!vbo && needvbo(geom)) + vbo = new osg::VertexBufferObject; + if (vbo) + array->setVertexBufferObject(vbo); + return array; +} void Optimizer::FlattenStaticTransformsVisitor::apply(osg::Drawable& drawable) { osg::Geometry *geometry = drawable.asGeometry(); if((geometry) && (isOperationPermissibleForObject(&drawable))) { + osg::VertexBufferObject* vbo = nullptr; if(geometry->getVertexArray() && geometry->getVertexArray()->referenceCount() > 1) { - geometry->setVertexArray(dynamic_cast(geometry->getVertexArray()->clone(osg::CopyOp::DEEP_COPY_ALL))); + geometry->setVertexArray(cloneArray(geometry->getVertexArray(), vbo, geometry)); } if(geometry->getNormalArray() && geometry->getNormalArray()->referenceCount() > 1) { - geometry->setNormalArray(dynamic_cast(geometry->getNormalArray()->clone(osg::CopyOp::DEEP_COPY_ALL))); + geometry->setNormalArray(cloneArray(geometry->getNormalArray(), vbo, geometry)); } } _drawableSet.insert(&drawable); @@ -988,6 +1011,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 +1089,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 +1114,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 +1122,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 +1131,30 @@ void Optimizer::MergeGeometryVisitor::apply(osg::Group &group) popStateSet(); } +osg::PrimitiveSet* clonePrimitive(osg::PrimitiveSet* ps, osg::ElementBufferObject*& ebo, const osg::Geometry* geom) +{ + if (ps->referenceCount() <= 1) + return ps; + ps = static_cast(ps->clone(osg::CopyOp::DEEP_COPY_ALL)); + + osg::DrawElements* drawElements = ps->getDrawElements(); + if (!drawElements) return ps; + + if (!ebo && needvbo(geom)) + ebo = new osg::ElementBufferObject; + if (ebo) + drawElements->setElementBufferObject(ebo); + + return ps; +} + +bool containsSharedPrimitives(const osg::Geometry* geom) +{ + for (unsigned int i=0; igetNumPrimitiveSets(); ++i) + if (geom->getPrimitiveSet(i)->referenceCount() > 1) return true; + return false; +} + bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) { if (!isOperationPermissibleForObject(&group)) return false; @@ -1120,7 +1178,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 +1312,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()); @@ -1278,6 +1342,7 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) if (!drawable) continue; osg::Geometry* geom = drawable->asGeometry(); + osg::ElementBufferObject* ebo = nullptr; if (geom) { osg::Geometry::PrimitiveSetList& primitives = geom->getPrimitiveSetList(); @@ -1290,10 +1355,12 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) { if (prim->getNumIndices()==3) { + prim = clonePrimitive(prim, ebo, geom); (*itr) = prim; prim->setMode(osg::PrimitiveSet::TRIANGLES); } else if (prim->getNumIndices()==4) { + prim = clonePrimitive(prim, ebo, geom); (*itr) = prim; prim->setMode(osg::PrimitiveSet::QUADS); } } @@ -1308,6 +1375,7 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) if (!drawable) continue; osg::Geometry* geom = drawable->asGeometry(); + osg::ElementBufferObject* ebo = nullptr; if (geom) { if (geom->getNumPrimitiveSets()>0 && @@ -1320,6 +1388,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 +1418,8 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) if (combine) { + lhs = clonePrimitive(lhs, ebo, geom); + primitives[lhsNo] = lhs; switch(lhs->getType()) { @@ -1375,7 +1447,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 +1462,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 +1471,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 @@ -1467,6 +1538,18 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) } } #endif + if (doneCombine && !geom->containsSharedArrays() && !containsSharedPrimitives(geom)) + { + // prefer to use vbo for merged geometries as vbo uses less memory than display lists. + geom->setUseVertexBufferObjects(true); + geom->setUseDisplayList(false); + } + if (_alphaBlendingActive && _mergeAlphaBlending && !geom->getStateSet()) + { + osg::Depth* d = new osg::Depth; + d->setWriteMask(0); + geom->getOrCreateStateSet()->setAttribute(d); + } } } @@ -1479,34 +1562,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,14 +1629,14 @@ class MergeArrayVisitor : public osg::ArrayVisitor bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geometry& rhs) { - MergeArrayVisitor merger; - + osg::VertexBufferObject* vbo = nullptr; unsigned int base = 0; if (lhs.getVertexArray() && rhs.getVertexArray()) { - base = lhs.getVertexArray()->getNumElements(); + if (lhs.getVertexArray()->referenceCount() > 1) + lhs.setVertexArray(cloneArray(lhs.getVertexArray(), vbo, &lhs)); if (!merger.merge(lhs.getVertexArray(),rhs.getVertexArray())) { OSG_DEBUG << "MergeGeometry: vertex array not merged. Some data may be lost." <getBinding()!=osg::Array::BIND_OVERALL) { + if (lhs.getNormalArray()->referenceCount() > 1) + lhs.setNormalArray(cloneArray(lhs.getNormalArray(), vbo, &lhs)); if (!merger.merge(lhs.getNormalArray(),rhs.getNormalArray())) { OSG_DEBUG << "MergeGeometry: normal array not merged. Some data may be lost." <getBinding()!=osg::Array::BIND_OVERALL) { + if (lhs.getColorArray()->referenceCount() > 1) + lhs.setColorArray(cloneArray(lhs.getColorArray(), vbo, &lhs)); if (!merger.merge(lhs.getColorArray(),rhs.getColorArray())) { OSG_DEBUG << "MergeGeometry: color array not merged. Some data may be lost." <getBinding()!=osg::Array::BIND_OVERALL) { + if (lhs.getSecondaryColorArray()->referenceCount() > 1) + lhs.setSecondaryColorArray(cloneArray(lhs.getSecondaryColorArray(), vbo, &lhs)); if (!merger.merge(lhs.getSecondaryColorArray(),rhs.getSecondaryColorArray())) { OSG_DEBUG << "MergeGeometry: secondary color array not merged. Some data may be lost." <getBinding()!=osg::Array::BIND_OVERALL) { + if (lhs.getFogCoordArray()->referenceCount() > 1) + lhs.setFogCoordArray(cloneArray(lhs.getFogCoordArray(), vbo, &lhs)); if (!merger.merge(lhs.getFogCoordArray(),rhs.getFogCoordArray())) { OSG_DEBUG << "MergeGeometry: fog coord array not merged. Some data may be lost." <referenceCount() > 1) + lhs.setTexCoordArray(unit, cloneArray(lhs.getTexCoordArray(unit), vbo, &lhs)); if (!merger.merge(lhs.getTexCoordArray(unit),rhs.getTexCoordArray(unit))) { OSG_DEBUG << "MergeGeometry: tex coord array not merged. Some data may be lost." <referenceCount() > 1) + lhs.setVertexAttribArray(unit, cloneArray(lhs.getVertexAttribArray(unit), vbo, &lhs)); if (!merger.merge(lhs.getVertexAttribArray(unit),rhs.getVertexAttribArray(unit))) { OSG_DEBUG << "MergeGeometry: vertex attrib array not merged. Some data may be lost." <getMode()); + if (needvbo(&lhs)) + { + if (!ebo) ebo = new osg::ElementBufferObject; + new_primitive->setElementBufferObject(ebo); + } std::copy(primitiveUByte->begin(),primitiveUByte->end(),std::back_inserter(*new_primitive)); new_primitive->offsetIndices(base); (*primItr) = new_primitive; @@ -1691,13 +1765,19 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom { // must promote to a DrawElementsUShort osg::DrawElementsUShort* new_primitive = new osg::DrawElementsUShort(primitive->getMode()); + if (needvbo(&lhs)) + { + if (!ebo) ebo = new osg::ElementBufferObject; + new_primitive->setElementBufferObject(ebo); + } std::copy(primitiveUByte->begin(),primitiveUByte->end(),std::back_inserter(*new_primitive)); new_primitive->offsetIndices(base); (*primItr) = new_primitive; } else { - primitive->offsetIndices(base); + (*primItr) = clonePrimitive(primitive, ebo, &lhs); + (*primItr)->offsetIndices(base); } } break; @@ -1716,13 +1796,19 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom { // must promote to a DrawElementsUInt osg::DrawElementsUInt* new_primitive = new osg::DrawElementsUInt(primitive->getMode()); + if (needvbo(&lhs)) + { + if (!ebo) ebo = new osg::ElementBufferObject; + new_primitive->setElementBufferObject(ebo); + } std::copy(primitiveUShort->begin(),primitiveUShort->end(),std::back_inserter(*new_primitive)); new_primitive->offsetIndices(base); (*primItr) = new_primitive; } else { - primitive->offsetIndices(base); + (*primItr) = clonePrimitive(primitive, ebo, &lhs); + (*primItr)->offsetIndices(base); } } break; @@ -1731,7 +1817,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, ebo, &lhs); + (*primItr)->offsetIndices(base); break; } } @@ -1744,6 +1831,10 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom lhs.dirtyBound(); lhs.dirtyDisplayList(); + if (osg::UserDataContainer* rhsUserData = rhs.getUserDataContainer()) + for (unsigned int i=0; igetNumUserObjects(); ++i) + lhs.getOrCreateUserDataContainer()->addUserObject(rhsUserData->getUserObject(i)); + return true; } 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..b9201fdf66 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -77,7 +77,7 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr sourceGeometry) osg::ref_ptr vbo (new osg::VertexBufferObject); vbo->setUsage(GL_DYNAMIC_DRAW_ARB); - osg::ref_ptr vertexArray = osg::clone(from.getVertexArray(), osg::CopyOp::DEEP_COPY_ALL); + osg::ref_ptr vertexArray = static_cast(from.getVertexArray()->clone(osg::CopyOp::DEEP_COPY_ALL)); if (vertexArray) { vertexArray->setVertexBufferObject(vbo); @@ -86,7 +86,7 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr sourceGeometry) if (const osg::Array* normals = from.getNormalArray()) { - osg::ref_ptr normalArray = osg::clone(normals, osg::CopyOp::DEEP_COPY_ALL); + osg::ref_ptr normalArray = static_cast(normals->clone(osg::CopyOp::DEEP_COPY_ALL)); if (normalArray) { normalArray->setVertexBufferObject(vbo); @@ -97,7 +97,7 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr sourceGeometry) if (const osg::Vec4Array* tangents = dynamic_cast(from.getTexCoordArray(7))) { mSourceTangents = tangents; - osg::ref_ptr tangentArray = osg::clone(tangents, osg::CopyOp::DEEP_COPY_ALL); + osg::ref_ptr tangentArray = static_cast(tangents->clone(osg::CopyOp::DEEP_COPY_ALL)); tangentArray->setVertexBufferObject(vbo); to.setTexCoordArray(7, tangentArray, osg::Array::BIND_PER_VERTEX); } @@ -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/sceneutil/statesetupdater.cpp b/components/sceneutil/statesetupdater.cpp index 121cdaca67..08418f3312 100644 --- a/components/sceneutil/statesetupdater.cpp +++ b/components/sceneutil/statesetupdater.cpp @@ -2,30 +2,38 @@ #include #include +#include namespace SceneUtil { void StateSetUpdater::operator()(osg::Node* node, osg::NodeVisitor* nv) { + bool isCullVisitor = nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR; if (!mStateSets[0]) { - // first time setup - osg::StateSet* src = node->getOrCreateStateSet(); - for (int i=0; i<2; ++i) // Using SHALLOW_COPY for StateAttributes, if users want to modify it is their responsibility to set a non-shared one first - // This can be done conveniently in user implementations of the setDefaults() method + for (int i=0; i<2; ++i) { - mStateSets[i] = new osg::StateSet(*src, osg::CopyOp::SHALLOW_COPY); + if (!isCullVisitor) + mStateSets[i] = new osg::StateSet(*node->getOrCreateStateSet(), osg::CopyOp::SHALLOW_COPY); // Using SHALLOW_COPY for StateAttributes, if users want to modify it is their responsibility to set a non-shared one first in setDefaults + else + mStateSets[i] = new osg::StateSet; setDefaults(mStateSets[i]); } } osg::StateSet* stateset = mStateSets[nv->getTraversalNumber()%2]; - node->setStateSet(stateset); - apply(stateset, nv); + if (!isCullVisitor) + node->setStateSet(stateset); + else + static_cast(nv)->pushStateSet(stateset); + traverse(node, nv); + + if (isCullVisitor) + static_cast(nv)->popStateSet(); } void StateSetUpdater::reset() diff --git a/components/sceneutil/statesetupdater.hpp b/components/sceneutil/statesetupdater.hpp index 51398844cf..d12316fb23 100644 --- a/components/sceneutil/statesetupdater.hpp +++ b/components/sceneutil/statesetupdater.hpp @@ -13,7 +13,7 @@ namespace SceneUtil /// traversals run in parallel can yield up to 200% framerates. /// @par Race conditions are prevented using a "double buffering" scheme - we have two StateSets that take turns, /// one StateSet we can write to, the second one is currently in use by the draw traversal of the last frame. - /// @par Must be set as UpdateCallback on a Node. + /// @par Must be set as UpdateCallback or CullCallback on a Node. If set as a CullCallback, the StateSetUpdater operates on an empty StateSet, otherwise it operates on a clone of the node's existing StateSet. /// @note Do not add the same StateSetUpdater to multiple nodes. /// @note Do not add multiple StateSetControllers on the same Node as they will conflict - instead use the CompositeStateSetUpdater. class StateSetUpdater : public osg::NodeCallback diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 95c1ca4914..3c3bd0f9d5 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -4,13 +4,13 @@ #include #include +#include #include #include #include -#include #include #include "terraindrawable.hpp" @@ -32,10 +32,14 @@ ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, T , mCompositeMapLevel(1.f) , mMaxCompGeometrySize(1.f) { - + mMultiPassRoot = new osg::StateSet; + mMultiPassRoot->setRenderingHint(osg::StateSet::OPAQUE_BIN); + osg::ref_ptr material (new osg::Material); + material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); + mMultiPassRoot->setAttributeAndModes(material, osg::StateAttribute::ON); } -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, bool compile) { ChunkId id = std::make_tuple(center, lod, lodFlags); osg::ref_ptr obj = mCache->getRefFromObjectCache(id); @@ -43,7 +47,7 @@ osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f &cen return obj->asNode(); else { - osg::ref_ptr node = createChunk(size, center, lod, lodFlags); + osg::ref_ptr node = createChunk(size, center, lod, lodFlags, compile); mCache->addEntryToObjectCache(id, node.get()); return node; } @@ -161,12 +165,8 @@ std::vector > ChunkManager::createPasses(float chunk return ::Terrain::createPasses(useShaders, &mSceneManager->getShaderManager(), layers, blendmapTextures, blendmapScale, blendmapScale); } -osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Vec2f &chunkCenter, unsigned char lod, unsigned int lodFlags) +osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Vec2f &chunkCenter, unsigned char lod, unsigned int lodFlags, bool compile) { - 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); @@ -201,6 +201,8 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve geometry->createClusterCullingCallback(); + geometry->setStateSet(mMultiPassRoot); + if (useCompositeMap) { osg::ref_ptr compositeMap = new CompositeMap; @@ -224,16 +226,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()) + if (compile && 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..11e5769deb 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,17 +30,20 @@ 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, bool compile); 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; @@ -47,7 +51,7 @@ namespace Terrain void releaseGLObjects(osg::State* state) override; private: - osg::ref_ptr createChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags); + osg::ref_ptr createChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool compile); osg::ref_ptr createCompositeMapRTT(); @@ -61,6 +65,10 @@ namespace Terrain CompositeMapRenderer* mCompositeMapRenderer; BufferCache mBufferCache; + osg::ref_ptr mMultiPassRoot; + + unsigned int mNodeMask; + unsigned int mCompositeMapSize; float mCompositeMapLevel; float mMaxCompGeometrySize; diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index fd385e793c..e662f4439f 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -183,17 +183,20 @@ namespace Terrain osg::ref_ptr stateset (new osg::StateSet); - stateset->setMode(GL_BLEND, osg::StateAttribute::ON); - - if (!firstLayer) + if (!blendmaps.empty()) { - stateset->setAttributeAndModes(BlendFunc::value(), osg::StateAttribute::ON); - stateset->setAttributeAndModes(EqualDepth::value(), osg::StateAttribute::ON); - } - else - { - stateset->setAttributeAndModes(BlendFuncFirst::value(), osg::StateAttribute::ON); - stateset->setAttributeAndModes(LequalDepth::value(), osg::StateAttribute::ON); + stateset->setMode(GL_BLEND, osg::StateAttribute::ON); + stateset->setRenderBinDetails(passIndex++, "RenderBin"); + if (!firstLayer) + { + stateset->setAttributeAndModes(BlendFunc::value(), osg::StateAttribute::ON); + stateset->setAttributeAndModes(EqualDepth::value(), osg::StateAttribute::ON); + } + else + { + stateset->setAttributeAndModes(BlendFuncFirst::value(), osg::StateAttribute::ON); + stateset->setAttributeAndModes(LequalDepth::value(), osg::StateAttribute::ON); + } } int texunit = 0; @@ -268,8 +271,6 @@ namespace Terrain } - stateset->setRenderBinDetails(passIndex++, "RenderBin"); - passes.push_back(stateset); } return passes; diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index a28554be94..7baea45c82 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -108,71 +108,26 @@ void QuadTreeNode::initNeighbours() getChild(i)->initNeighbours(); } -void QuadTreeNode::traverseNodes(ViewData* vd, const osg::Vec3f& viewPoint, LodCallback* lodCallback, float maxDist) +void QuadTreeNode::traverseNodes(ViewData* vd, const osg::Vec3f& viewPoint, LodCallback* lodCallback) { if (!hasValidBounds()) return; - - float dist = distance(viewPoint); - if (dist > maxDist) + LodCallback::ReturnValue lodResult = lodCallback->isSufficientDetail(this, distance(viewPoint)); + if (lodResult == LodCallback::StopTraversal) return; - - bool stopTraversal = (lodCallback->isSufficientDetail(this, dist)) || !getNumChildren(); - - if (stopTraversal) - vd->add(this); - else + else if (lodResult == LodCallback::Deeper && getNumChildren()) { for (unsigned int i=0; itraverseNodes(vd, viewPoint, lodCallback, maxDist); + getChild(i)->traverseNodes(vd, viewPoint, lodCallback); } -} - -void QuadTreeNode::traverseTo(ViewData* vd, float size, const osg::Vec2f& center) -{ - if (!hasValidBounds()) - return; - - if (getCenter().x() + getSize()/2.f <= center.x() - size/2.f - || getCenter().x() - getSize()/2.f >= center.x() + size/2.f - || getCenter().y() + getSize()/2.f <= center.y() - size/2.f - || getCenter().y() - getSize()/2.f >= center.y() + size/2.f) - return; - - bool stopTraversal = (getSize() == size); - - if (stopTraversal) - vd->add(this); else - { - for (unsigned int i=0; itraverseTo(vd, size, center); - } -} - -void QuadTreeNode::intersect(ViewData* vd, TerrainLineIntersector& intersector) -{ - if (!hasValidBounds()) - return; - - if (!intersector.intersectAndClip(getBoundingBox())) - return; - - if (getNumChildren() == 0) vd->add(this); - else - { - for (unsigned int i=0; iintersect(vd, intersector); - } } void QuadTreeNode::setBoundingBox(const osg::BoundingBox &boundingBox) { mBoundingBox = boundingBox; mValidBounds = boundingBox.valid(); - dirtyBound(); - getBound(); } const osg::BoundingBox &QuadTreeNode::getBoundingBox() const @@ -180,11 +135,6 @@ const osg::BoundingBox &QuadTreeNode::getBoundingBox() const return mBoundingBox; } -osg::BoundingSphere QuadTreeNode::computeBound() const -{ - return osg::BoundingSphere(mBoundingBox); -} - float QuadTreeNode::getSize() const { return mSize; diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp index 4adbc60250..a309d91fc8 100644 --- a/components/terrain/quadtreenode.hpp +++ b/components/terrain/quadtreenode.hpp @@ -2,39 +2,12 @@ #define OPENMW_COMPONENTS_TERRAIN_QUADTREENODE_H #include -#include #include "defs.hpp" namespace Terrain { - class TerrainLineIntersector : public osgUtil::LineSegmentIntersector - { - public: - TerrainLineIntersector(osgUtil::LineSegmentIntersector* intersector, osg::Matrix& matrix) : - osgUtil::LineSegmentIntersector(intersector->getStart() * matrix, intersector->getEnd() * matrix) - { - setPrecisionHint(intersector->getPrecisionHint()); - _intersectionLimit = intersector->getIntersectionLimit(); - _parent = intersector; - } - - TerrainLineIntersector(osgUtil::LineSegmentIntersector* intersector) : - osgUtil::LineSegmentIntersector(intersector->getStart(), intersector->getEnd()) - { - setPrecisionHint(intersector->getPrecisionHint()); - _intersectionLimit = intersector->getIntersectionLimit(); - _parent = intersector; - } - - bool intersectAndClip(const osg::BoundingBox& bbInput) - { - osg::Vec3d s(_start), e(_end); - return osgUtil::LineSegmentIntersector::intersectAndClip(s, e, bbInput); - } - }; - enum ChildDirection { NW = 0, @@ -50,10 +23,15 @@ namespace Terrain public: virtual ~LodCallback() {} - virtual bool isSufficientDetail(QuadTreeNode *node, float dist) = 0; + enum ReturnValue + { + Deeper, + StopTraversal, + StopTraversalAndUse + }; + virtual ReturnValue isSufficientDetail(QuadTreeNode *node, float dist) = 0; }; - class ViewDataMap; class ViewData; class QuadTreeNode : public osg::Group @@ -91,8 +69,6 @@ namespace Terrain const osg::BoundingBox& getBoundingBox() const; bool hasValidBounds() const { return mValidBounds; } - virtual osg::BoundingSphere computeBound() const; - /// size in cell coordinates float getSize() const; @@ -100,13 +76,7 @@ namespace Terrain const osg::Vec2f& getCenter() const; /// Traverse nodes according to LOD selection. - void traverseNodes(ViewData* vd, const osg::Vec3f& viewPoint, LodCallback* lodCallback, float maxDist); - - /// Traverse to a specific node and add only that node. - void traverseTo(ViewData* vd, float size, const osg::Vec2f& center); - - /// Adds all leaf nodes which intersect the line from start to end - void intersect(ViewData* vd, TerrainLineIntersector& intersector); + void traverseNodes(ViewData* vd, const osg::Vec3f& viewPoint, LodCallback* lodCallback); private: QuadTreeNode* mParent; diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index c43a9a21b9..e094330610 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -9,6 +9,7 @@ #include #include +#include #include "quadtreenode.hpp" #include "storage.hpp" @@ -52,25 +53,45 @@ namespace Terrain class DefaultLodCallback : public LodCallback { public: - DefaultLodCallback(float factor, float minSize) + DefaultLodCallback(float factor, float minSize, float viewDistance, const osg::Vec4i& grid) : mFactor(factor) , mMinSize(minSize) + , mViewDistance(viewDistance) + , mActiveGrid(grid) { } - virtual bool isSufficientDetail(QuadTreeNode* node, float dist) + virtual ReturnValue isSufficientDetail(QuadTreeNode* node, float dist) { + const osg::Vec2f& center = node->getCenter(); + bool activeGrid = (center.x() > mActiveGrid.x() && center.y() > mActiveGrid.y() && center.x() < mActiveGrid.z() && center.y() < mActiveGrid.w()); + if (dist > mViewDistance && !activeGrid) // for Scene<->ObjectPaging sync the activegrid must remain loaded + return StopTraversal; + if (node->getSize()>1) + { + float halfSize = node->getSize()/2; + osg::Vec4i nodeBounds (static_cast(center.x() - halfSize), static_cast(center.y() - halfSize), static_cast(center.x() + halfSize), static_cast(center.y() + halfSize)); + bool intersects = (std::max(nodeBounds.x(), mActiveGrid.x()) < std::min(nodeBounds.z(), mActiveGrid.z()) && std::max(nodeBounds.y(), mActiveGrid.y()) < std::min(nodeBounds.w(), mActiveGrid.w())); + // to prevent making chunks who will cross the activegrid border + if (intersects) + return Deeper; + } + int nativeLodLevel = Log2(static_cast(node->getSize()/mMinSize)); int lodLevel = Log2(static_cast(dist/(Constants::CellSizeInUnits*mMinSize*mFactor))); - return nativeLodLevel <= lodLevel; + return nativeLodLevel <= lodLevel ? StopTraversalAndUse : Deeper; } private: float mFactor; float mMinSize; + float mViewDistance; + osg::Vec4i mActiveGrid; }; +const float MIN_SIZE = 1/8.f; + class RootNode : public QuadTreeNode { public: @@ -125,6 +146,8 @@ public: addChildren(mRootNode); mRootNode->initNeighbours(); + float cellWorldSize = mStorage->getCellWorldSize(); + mRootNode->setInitialBound(osg::BoundingSphere(osg::BoundingBox(osg::Vec3(mMinX*cellWorldSize, mMinY*cellWorldSize, 0), osg::Vec3(mMaxX*cellWorldSize, mMaxY*cellWorldSize, 0)))); } void addChildren(QuadTreeNode* parent) @@ -231,11 +254,11 @@ 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() { - mViewDataMap->clear(); } /// get the level of vertex detail to render this node at, expressed relative to the native resolution of the data set. @@ -261,7 +284,7 @@ unsigned int getVertexLod(QuadTreeNode* node, int vertexLodMod) } /// get the flags to use for stitching in the index buffer so that chunks of different LOD connect seamlessly -unsigned int getLodFlags(QuadTreeNode* node, int ourLod, int vertexLodMod, ViewData* vd) +unsigned int getLodFlags(QuadTreeNode* node, int ourLod, int vertexLodMod, const ViewData* vd) { unsigned int lodFlags = 0; for (unsigned int i=0; i<4; ++i) @@ -289,7 +312,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, bool compile) { if (!vd->hasChanged() && entry.mRenderingNode) return; @@ -308,7 +331,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); + { + osg::ref_ptr 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 activeGrid = (center.x() > gridbounds.x() && center.y() > gridbounds.y() && center.x() < gridbounds.z() && center.y() < gridbounds.w()); + + for (QuadTreeWorld::ChunkManager* m : chunkManagers) + { + osg::ref_ptr n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, entry.mLodFlags, activeGrid, vd->getViewPoint(), compile); + if (n) pat->addChild(n); + } + entry.mRenderingNode = pat; + } } void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil::CullVisitor* cv, float cellworldsize, bool outofworld) @@ -382,71 +418,29 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) return; } + osg::Object * viewer = isCullVisitor ? static_cast(&nv)->getCurrentCamera() : nullptr; bool needsUpdate = true; - ViewData* vd = nullptr; - if (isCullVisitor) - vd = mViewDataMap->getViewData(static_cast(&nv)->getCurrentCamera(), nv.getViewPoint(), needsUpdate); - else - { - static ViewData sIntersectionViewData; - vd = &sIntersectionViewData; - } + ViewData *vd = mViewDataMap->getViewData(viewer, nv.getViewPoint(), mActiveGrid, needsUpdate); if (needsUpdate) { vd->reset(); - if (isCullVisitor) - { - osgUtil::CullVisitor* cv = static_cast(&nv); - - osg::UserDataContainer* udc = cv->getCurrentCamera()->getUserDataContainer(); - if (udc && udc->getNumDescriptions() >= 2 && udc->getDescriptions()[0] == "NoTerrainLod") - { - std::istringstream stream(udc->getDescriptions()[1]); - int x,y; - stream >> x; - stream >> y; - mRootNode->traverseTo(vd, 1, osg::Vec2f(x+0.5,y+0.5)); - } - else - mRootNode->traverseNodes(vd, cv->getViewPoint(), mLodCallback, mViewDistance); - } - else - { - osgUtil::IntersectionVisitor* iv = static_cast(&nv); - osgUtil::LineSegmentIntersector* lineIntersector = dynamic_cast(iv->getIntersector()); - if (!lineIntersector) - throw std::runtime_error("Cannot update QuadTreeWorld: node visitor is not LineSegmentIntersector"); - - if (lineIntersector->getCoordinateFrame() == osgUtil::Intersector::CoordinateFrame::MODEL && iv->getModelMatrix() == 0) - { - TerrainLineIntersector terrainIntersector(lineIntersector); - mRootNode->intersect(vd, terrainIntersector); - } - else - { - osg::Matrix matrix(lineIntersector->getTransformation(*iv, lineIntersector->getCoordinateFrame())); - TerrainLineIntersector terrainIntersector(lineIntersector, matrix); - mRootNode->intersect(vd, terrainIntersector); - } - } + DefaultLodCallback lodCallback(mLodFactor, MIN_SIZE, mViewDistance, mActiveGrid); + mRootNode->traverseNodes(vd, nv.getViewPoint(), &lodCallback); } + 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, false); entry.mRenderingNode->accept(nv); } if (isCullVisitor) updateWaterCullingView(mHeightCullCallback, vd, static_cast(&nv), mStorage->getCellWorldSize(), !isGridEmpty()); - if (!isCullVisitor) - vd->clear(); // we can't reuse intersection views in the next frame because they only contain what is touched by the intersection ray. - vd->markUnchanged(); double referenceTime = nv.getFrameStamp() ? nv.getFrameStamp()->getReferenceTime() : 0.0; @@ -463,9 +457,7 @@ void QuadTreeWorld::ensureQuadTreeBuilt() if (mQuadTreeBuilt) return; - const float minSize = 1/8.f; - mLodCallback = new DefaultLodCallback(mLodFactor, minSize); - QuadTreeBuilder builder(mStorage, minSize); + QuadTreeBuilder builder(mStorage, MIN_SIZE); builder.build(); mRootNode = builder.getRootNode(); @@ -487,48 +479,38 @@ void QuadTreeWorld::enable(bool enabled) mRootNode->setNodeMask(enabled ? ~0 : 0); } -void QuadTreeWorld::cacheCell(View *view, int x, int y) -{ - ensureQuadTreeBuilt(); - ViewData* vd = static_cast(view); - mRootNode->traverseTo(vd, 1, osg::Vec2f(x+0.5f,y+0.5f)); - - for (unsigned int i=0; igetNumEntries(); ++i) - { - ViewData::Entry& entry = vd->getEntry(i); - loadRenderingNode(entry, vd, mVertexLodMod, mChunkManager.get()); - } -} - View* QuadTreeWorld::createView() { - return new ViewData; + return mViewDataMap->createIndependentView(); } -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, std::atomic &progress, int& progressTotal) { ensureQuadTreeBuilt(); ViewData* vd = static_cast(view); vd->setViewPoint(viewPoint); - mRootNode->traverseNodes(vd, viewPoint, mLodCallback, mViewDistance); + vd->setActiveGrid(grid); + DefaultLodCallback lodCallback(mLodFactor, MIN_SIZE, mViewDistance, grid); + mRootNode->traverseNodes(vd, viewPoint, &lodCallback); + if (!progressTotal) + for (unsigned int i=0; igetNumEntries(); ++i) + progressTotal += vd->getEntry(i).mNode->getSize(); + + 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, true); + progress += entry.mNode->getSize(); } vd->markUnchanged(); } -void QuadTreeWorld::storeView(const View* view, double referenceTime) +bool 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); - stored->copyFrom(*vd); - stored->setLastUsageTimeStamp(referenceTime); + return mViewDataMap->storeView(static_cast(view), referenceTime); } void QuadTreeWorld::reportStats(unsigned int frameNumber, osg::Stats *stats) @@ -556,5 +538,15 @@ 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()); +} + +void QuadTreeWorld::rebuildViews() +{ + mViewDataMap->rebuildViews(); +} } diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index bcb671ee13..7b395bff62 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -15,7 +15,6 @@ namespace Terrain { class RootNode; class ViewDataMap; - class LodCallback; /// @brief Terrain implementation that loads cells into a Quad Tree, with geometry LOD and texture LOD. class QuadTreeWorld : public TerrainGrid // note: derived from TerrainGrid is only to render default cells (see loadCell) @@ -31,25 +30,36 @@ namespace Terrain virtual void setViewDistance(float distance) { mViewDistance = distance; } - void cacheCell(View *view, int x, int y); + void cacheCell(View *view, int x, int y) {} /// @note Not thread safe. virtual void loadCell(int x, int y); /// @note Not thread safe. virtual void unloadCell(int x, int y); View* createView(); - void preload(View* view, const osg::Vec3f& eyePoint, std::atomic& abort); - void storeView(const View* view, double referenceTime); + void preload(View* view, const osg::Vec3f& eyePoint, const osg::Vec4i &cellgrid, std::atomic& abort, std::atomic& progress, int& progressRange); + bool storeView(const View* view, double referenceTime); + void rebuildViews() override; 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, bool compile) = 0; + virtual unsigned int getNodeMask() { return 0; } + }; + void addChunkManager(ChunkManager*); + private: void ensureQuadTreeBuilt(); osg::ref_ptr mRootNode; osg::ref_ptr mViewDataMap; - osg::ref_ptr mLodCallback; + + std::vector mChunkManagers; OpenThreads::Mutex mQuadTreeMutex; bool mQuadTreeBuilt; diff --git a/components/terrain/terraindrawable.cpp b/components/terrain/terraindrawable.cpp index 9593687cfd..0d82be4fff 100644 --- a/components/terrain/terraindrawable.cpp +++ b/components/terrain/terraindrawable.cpp @@ -102,6 +102,10 @@ void TerrainDrawable::cull(osgUtil::CullVisitor *cv) bool pushedLight = mLightListCallback && mLightListCallback->pushLightState(this, cv); + osg::StateSet* stateset = getStateSet(); + if (stateset) + cv->pushStateSet(stateset); + for (PassVector::const_iterator it = mPasses.begin(); it != mPasses.end(); ++it) { cv->pushStateSet(*it); @@ -109,6 +113,8 @@ void TerrainDrawable::cull(osgUtil::CullVisitor *cv) cv->popStateSet(); } + if (stateset) + cv->popStateSet(); if (pushedLight) cv->popStateSet(); } diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index a0e5e4718b..5f99cd97eb 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(), true); 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/texturemanager.cpp b/components/terrain/texturemanager.cpp index b901fa1590..c28b13f1dc 100644 --- a/components/terrain/texturemanager.cpp +++ b/components/terrain/texturemanager.cpp @@ -25,7 +25,7 @@ struct UpdateTextureFilteringFunctor } Resource::SceneManager* mSceneManager; - void operator()(osg::Object* obj) + void operator()(std::string, osg::Object* obj) { mSceneManager->applyFilterSettings(static_cast(obj)); } diff --git a/components/terrain/viewdata.cpp b/components/terrain/viewdata.cpp index d07a0e356a..c24252b7d5 100644 --- a/components/terrain/viewdata.cpp +++ b/components/terrain/viewdata.cpp @@ -1,5 +1,7 @@ #include "viewdata.hpp" +#include "quadtreenode.hpp" + namespace Terrain { @@ -8,6 +10,7 @@ ViewData::ViewData() , mLastUsageTimeStamp(0.0) , mChanged(false) , mHasViewPoint(false) + , mWorldUpdateRevision(0) { } @@ -24,6 +27,8 @@ void ViewData::copyFrom(const ViewData& other) mChanged = other.mChanged; mHasViewPoint = other.mHasViewPoint; mViewPoint = other.mViewPoint; + mActiveGrid = other.mActiveGrid; + mWorldUpdateRevision = other.mWorldUpdateRevision; } void ViewData::add(QuadTreeNode *node) @@ -90,7 +95,12 @@ void ViewData::clear() mHasViewPoint = false; } -bool ViewData::contains(QuadTreeNode *node) +bool ViewData::suitableToUse(const osg::Vec4i &activeGrid) const +{ + return hasViewPoint() && activeGrid == mActiveGrid && getNumEntries(); +} + +bool ViewData::contains(QuadTreeNode *node) const { for (unsigned int i=0; ihasViewPoint() && (vd->getViewPoint() - viewPoint).length2() < maxDist*maxDist; -} - -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); + ViewerMap::const_iterator found = mViewers.find(viewer); ViewData* vd = nullptr; - if (found == mViews.end()) + if (found == mViewers.end()) { vd = createOrReuseView(); - mViews[viewer] = vd; + mViewers[viewer] = vd; } else vd = found->second; + needsUpdate = false; - if (!suitable(vd, viewPoint, mReuseDistance)) + if (!(vd->suitableToUse(activeGrid) && (vd->getViewPoint()-viewPoint).length2() < mReuseDistance*mReuseDistance && vd->getWorldUpdateRevision() >= mWorldUpdateRevision)) { - for (Map::const_iterator other = mViews.begin(); other != mViews.end(); ++other) + float shortestDist = std::numeric_limits::max(); + const ViewData* mostSuitableView = nullptr; + for (const ViewData* other : mUsedViews) { - if (suitable(other->second, viewPoint, mReuseDistance) && other->second->getNumEntries()) + if (other->suitableToUse(activeGrid) && other->getWorldUpdateRevision() >= mWorldUpdateRevision) { - vd->copyFrom(*other->second); - needsUpdate = false; - return vd; + float dist = (viewPoint-other->getViewPoint()).length2(); + if (dist < shortestDist) + { + shortestDist = dist; + mostSuitableView = other; + } } } + if (mostSuitableView && mostSuitableView != vd) + { + vd->copyFrom(*mostSuitableView); + return vd; + } + } + if (!vd->suitableToUse(activeGrid)) + { vd->setViewPoint(viewPoint); + vd->setActiveGrid(activeGrid); needsUpdate = true; } - else - needsUpdate = false; - return vd; } +bool ViewDataMap::storeView(const ViewData* view, double referenceTime) +{ + if (view->getWorldUpdateRevision() < mWorldUpdateRevision) + return false; + ViewData* store = createOrReuseView(); + store->copyFrom(*view); + store->setLastUsageTimeStamp(referenceTime); + return true; +} + ViewData *ViewDataMap::createOrReuseView() { + ViewData* vd = nullptr; if (mUnusedViews.size()) { - ViewData* vd = mUnusedViews.front(); + vd = mUnusedViews.front(); mUnusedViews.pop_front(); - return vd; } else { mViewVector.push_back(ViewData()); - return &mViewVector.back(); + vd = &mViewVector.back(); } + mUsedViews.push_back(vd); + vd->setWorldUpdateRevision(mWorldUpdateRevision); + return vd; +} + +ViewData *ViewDataMap::createIndependentView() const +{ + ViewData* vd = new ViewData; + vd->setWorldUpdateRevision(mWorldUpdateRevision); + return vd; } void ViewDataMap::clearUnusedViews(double referenceTime) { - for (Map::iterator it = mViews.begin(); it != mViews.end(); ) + for (ViewerMap::iterator it = mViewers.begin(); it != mViewers.end(); ) + { + if (it->second->getLastUsageTimeStamp() + mExpiryDelay < referenceTime) + mViewers.erase(it++); + else + ++it; + } + for (std::deque::iterator it = mUsedViews.begin(); it != mUsedViews.end(); ) { - ViewData* vd = it->second; - if (vd->getLastUsageTimeStamp() + mExpiryDelay < referenceTime) + if ((*it)->getLastUsageTimeStamp() + mExpiryDelay < referenceTime) { - vd->clear(); - mUnusedViews.push_back(vd); - mViews.erase(it++); + (*it)->clear(); + mUnusedViews.push_back(*it); + it = mUsedViews.erase(it); } else ++it; } } -void ViewDataMap::clear() +void ViewDataMap::rebuildViews() { - mViews.clear(); - mUnusedViews.clear(); - mViewVector.clear(); + ++mWorldUpdateRevision; } } diff --git a/components/terrain/viewdata.hpp b/components/terrain/viewdata.hpp index 7f9f14af55..400d9fbbea 100644 --- a/components/terrain/viewdata.hpp +++ b/components/terrain/viewdata.hpp @@ -23,9 +23,11 @@ namespace Terrain void reset(); + bool suitableToUse(const osg::Vec4i& activeGrid) const; + void clear(); - bool contains(QuadTreeNode* node); + bool contains(QuadTreeNode* node) const; void copyFrom(const ViewData& other); @@ -57,6 +59,12 @@ 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;} + + unsigned int getWorldUpdateRevision() const { return mWorldUpdateRevision; } + void setWorldUpdateRevision(int updateRevision) { mWorldUpdateRevision = updateRevision; } + private: std::vector mEntries; unsigned int mNumEntries; @@ -64,34 +72,41 @@ namespace Terrain bool mChanged; osg::Vec3f mViewPoint; bool mHasViewPoint; + osg::Vec4i mActiveGrid; + unsigned int mWorldUpdateRevision; }; class ViewDataMap : public osg::Referenced { public: ViewDataMap() - : mReuseDistance(300) // large value should be safe because the visibility of each node is still updated individually for each camera even if the base view was reused. + : mReuseDistance(150) // large value should be safe because the visibility of each node is still updated individually for each camera even if the base view was reused. // this value also serves as a threshold for when a newly loaded LOD gets unloaded again so that if you hover around an LOD transition point the LODs won't keep loading and unloading all the time. , mExpiryDelay(1.f) + , mWorldUpdateRevision(0) {} - 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(); + ViewData* createIndependentView() const; void clearUnusedViews(double referenceTime); - - void clear(); + void rebuildViews(); + bool storeView(const ViewData* view, double referenceTime); private: std::list mViewVector; - typedef std::map, ViewData*> Map; - Map mViews; + typedef std::map, ViewData*> ViewerMap; + ViewerMap mViewers; float mReuseDistance; float mExpiryDelay; // time in seconds for unused view to be removed + unsigned int mWorldUpdateRevision; + + std::deque mUsedViews; std::deque mUnusedViews; }; diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 2d53f40908..5b4807b387 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -1,7 +1,6 @@ #include "world.hpp" #include -#include #include #include @@ -23,11 +22,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); - mTerrainRoot->setName("Terrain Root"); osg::ref_ptr compositeCam = new osg::Camera; @@ -48,6 +42,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..9f08454c8c 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -147,11 +147,13 @@ 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, std::atomic& progress, int& progressRange) {} /// Store a preloaded view into the cache with the intent that the next rendering traversal can use it. /// @note Not thread safe. - virtual void storeView(const View* view, double referenceTime) {} + virtual bool storeView(const View* view, double referenceTime) {return true;} + + virtual void rebuildViews() {} virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) {} @@ -161,6 +163,8 @@ namespace Terrain osg::Callback* getHeightCullCallback(float highz, unsigned int mask); + void setActiveGrid(const osg::Vec4i &grid) { mActiveGrid = grid; } + protected: Storage* mStorage; @@ -181,6 +185,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..686d65adc8 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -106,6 +106,27 @@ 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 +# Use object paging for non active cells +object paging = true + +# Use object paging for active cells grid +object paging active grid = true + +# Affects the likelyhood of objects being merged. A higher value means merging is more likely and may improve FPS at the cost of memory. +object paging merge factor = 250 + +# Cull objects smaller than this size divided by distance +object paging min size = 0.01 + +# Adjusts 'min size' based on merging decision. Allows inexpensive objects to be rendered from a greater distance. +object paging min size merge factor = 0.3 + +# Controls how inexpensive an object needs to be to utilize 'min size merge factor'. +object paging min size cost multiplier = 25 + +# Assign a random color to merged batches. +object paging debug batches = false + [Fog] # If true, use extended fog parameters for distant terrain not controlled by @@ -808,8 +829,8 @@ enable debug hud = false # Enable the debug overlay to see where each shadow map affects. enable debug overlay = false -# Attempt to better use the shadow map by making them cover a smaller area. May have a minor to major performance impact. -compute tight scene bounds = true +# Attempt to better use the shadow map by making them cover a smaller area. May have a major performance impact. +compute tight scene bounds = false # How large to make the shadow map(s). Higher values increase GPU load, but can produce better-looking results. Power-of-two values may turn out to be faster on some GPU/driver combinations. shadow map resolution = 1024