diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index e2d1f0c50d..25c00fb976 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -1,5 +1,6 @@ #include "groundcover.hpp" +#include #include #include #include @@ -7,6 +8,8 @@ #include #include +#include +#include #include #include "apps/openmw/mwworld/esmstore.hpp" @@ -106,6 +109,20 @@ namespace MWRender float mDensity = 0.f; }; + class ViewDistanceCallback : public SceneUtil::NodeCallback + { + public: + ViewDistanceCallback(float dist, const osg::BoundingBox& box) : mViewDistance(dist), mBox(box) {} + void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + if (Terrain::distance(mBox, nv->getEyePoint()) <= mViewDistance) + traverse(node, nv); + } + private: + float mViewDistance; + osg::BoundingBox mBox; + }; + inline bool isInChunkBorders(ESM::CellRef& ref, osg::Vec2f& minBound, osg::Vec2f& maxBound) { osg::Vec2f size = maxBound - minBound; @@ -122,8 +139,9 @@ namespace MWRender osg::ref_ptr Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) { + if (lod > getMaxLodLevel()) + return nullptr; GroundcoverChunkId id = std::make_tuple(center, size); - osg::ref_ptr obj = mCache->getRefFromObjectCache(id); if (obj) return static_cast(obj.get()); @@ -137,12 +155,13 @@ namespace MWRender } } - Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density) + Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density, float viewDistance) : GenericResourceManager(nullptr) , mSceneManager(sceneManager) , mDensity(density) , mStateset(new osg::StateSet) { + setViewDistance(viewDistance); // MGE uses default alpha settings for groundcover, so we can not rely on alpha properties // Force a unified alpha handling instead of data from meshes osg::ref_ptr alpha = new osg::AlphaFunc(osg::AlphaFunc::GEQUAL, 128.f / 255.f); @@ -220,10 +239,16 @@ namespace MWRender group->addChild(node); } + osg::ComputeBoundsVisitor cbv; + group->accept(cbv); + osg::BoundingBox box = cbv.getBoundingBox(); + box = osg::BoundingBox(box._min+worldCenter, box._max+worldCenter); + group->addCullCallback(new ViewDistanceCallback(getViewDistance(), box)); + group->setStateSet(mStateset); group->setNodeMask(Mask_Groundcover); if (mSceneManager->getLightingMethod() != SceneUtil::LightingMethod::FFP) - group->setCullCallback(new SceneUtil::LightListCallback); + group->addCullCallback(new SceneUtil::LightListCallback); mSceneManager->recreateShaders(group, "groundcover", true, mProgramTemplate); mSceneManager->shareState(group); group->getBound(); diff --git a/apps/openmw/mwrender/groundcover.hpp b/apps/openmw/mwrender/groundcover.hpp index ed88f7fe24..cd73e46eb0 100644 --- a/apps/openmw/mwrender/groundcover.hpp +++ b/apps/openmw/mwrender/groundcover.hpp @@ -12,7 +12,7 @@ namespace MWRender class Groundcover : public Resource::GenericResourceManager, public Terrain::QuadTreeWorld::ChunkManager { public: - Groundcover(Resource::SceneManager* sceneManager, float density); + Groundcover(Resource::SceneManager* sceneManager, float density, float viewDistance); ~Groundcover() = 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; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 4e2da15107..19972b094a 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -439,11 +439,9 @@ namespace MWRender float density = Settings::Manager::getFloat("density", "Groundcover"); density = std::clamp(density, 0.f, 1.f); - mGroundcover.reset(new Groundcover(mResourceSystem->getSceneManager(), density)); + mGroundcover.reset(new Groundcover(mResourceSystem->getSceneManager(), density, groundcoverDistance)); static_cast(mTerrain.get())->addChunkManager(mGroundcover.get()); mResourceSystem->addResourceManager(mGroundcover.get()); - - mGroundcover->setViewDistance(groundcoverDistance); } mStateUpdater = new StateUpdater; diff --git a/components/terrain/buffercache.cpp b/components/terrain/buffercache.cpp index 399df16d34..658c431b1b 100644 --- a/components/terrain/buffercache.cpp +++ b/components/terrain/buffercache.cpp @@ -12,8 +12,8 @@ namespace template osg::ref_ptr createIndexBuffer(unsigned int flags, unsigned int verts) { - // LOD level n means every 2^n-th vertex is kept - size_t lodLevel = (flags >> (4*4)); + // LOD level n means every 2^n-th vertex is kept, but we currently handle LOD elsewhere. + size_t lodLevel = 0;//(flags >> (4*4)); size_t lodDeltas[4]; for (int i=0; i<4; ++i) diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index e040cbdd93..d57b73768f 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -52,6 +52,9 @@ struct FindChunkTemplate osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) { + // Override lod with the vertexLodMod adjusted value. + // TODO: maybe we can refactor this code by moving all vertexLodMod code into this class. + lod = static_cast(lodFlags >> (4*4)); ChunkId id = std::make_tuple(center, lod, lodFlags); osg::ref_ptr obj = mCache->getRefFromObjectCache(id); if (obj) diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index c113a629c5..81d3ccb32d 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -10,6 +10,29 @@ namespace Terrain { +float distance(const osg::BoundingBox& box, const osg::Vec3f& v) +{ + if (box.contains(v)) + return 0; + else + { + osg::Vec3f maxDist(0,0,0); + if (v.x() < box.xMin()) + maxDist.x() = box.xMin() - v.x(); + else if (v.x() > box.xMax()) + maxDist.x() = v.x() - box.xMax(); + if (v.y() < box.yMin()) + maxDist.y() = box.yMin() - v.y(); + else if (v.y() > box.yMax()) + maxDist.y() = v.y() - box.yMax(); + if (v.z() < box.zMin()) + maxDist.z() = box.zMin() - v.z(); + else if (v.z() > box.zMax()) + maxDist.z() = v.z() - box.zMax(); + return maxDist.length(); + } +} + ChildDirection reflect(ChildDirection dir, Direction dir2) { assert(dir != Root); @@ -78,25 +101,7 @@ QuadTreeNode *QuadTreeNode::getNeighbour(Direction dir) float QuadTreeNode::distance(const osg::Vec3f& v) const { const osg::BoundingBox& box = getBoundingBox(); - if (box.contains(v)) - return 0; - else - { - osg::Vec3f maxDist(0,0,0); - if (v.x() < box.xMin()) - maxDist.x() = box.xMin() - v.x(); - else if (v.x() > box.xMax()) - maxDist.x() = v.x() - box.xMax(); - if (v.y() < box.yMin()) - maxDist.y() = box.yMin() - v.y(); - else if (v.y() > box.yMax()) - maxDist.y() = v.y() - box.yMax(); - if (v.z() < box.zMin()) - maxDist.z() = box.zMin() - v.z(); - else if (v.z() > box.zMax()) - maxDist.z() = v.z() - box.zMax(); - return maxDist.length(); - } + return Terrain::distance(box, v); } void QuadTreeNode::initNeighbours() diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp index 7ae59b92f0..aba78b8b3a 100644 --- a/components/terrain/quadtreenode.hpp +++ b/components/terrain/quadtreenode.hpp @@ -34,6 +34,8 @@ namespace Terrain class ViewData; + float distance(const osg::BoundingBox&, const osg::Vec3f& v); + class QuadTreeNode : public osg::Group { public: diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 7fc0895846..6dbed34331 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -40,9 +40,9 @@ namespace return 1 << depth; } - int Log2( unsigned int n ) + unsigned int Log2( unsigned int n ) { - int targetlevel = 0; + unsigned int targetlevel = 0; while (n >>= 1) ++targetlevel; return targetlevel; } @@ -78,16 +78,18 @@ public: if (intersects) return Deeper; } - dist = std::max(0.f, dist + mDistanceModifier); - if (dist > mViewDistance && !activeGrid) // for Scene<->ObjectPaging sync the activegrid must remain loaded return StopTraversal; - - int nativeLodLevel = Log2(static_cast(node->getSize()/mMinSize)); - int lodLevel = Log2(static_cast(dist/(Constants::CellSizeInUnits*mMinSize*mFactor))); - - return nativeLodLevel <= lodLevel ? StopTraversalAndUse : Deeper; + return getNativeLodLevel(node, mMinSize) <= convertDistanceToLodLevel(dist, mMinSize, mFactor) ? StopTraversalAndUse : Deeper; + } + static unsigned int getNativeLodLevel(const QuadTreeNode* node, float minSize) + { + return Log2(static_cast(node->getSize()/minSize)); + } + static unsigned int convertDistanceToLodLevel(float dist, float minSize, float factor) + { + return Log2(static_cast(dist/(Constants::CellSizeInUnits*minSize*factor))); } private: @@ -278,7 +280,6 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour , mViewDistance(std::numeric_limits::max()) , mMinSize(1/8.f) , mDebugTerrainChunks(debugChunks) - , mRevalidateDistance(0.f) { mChunkManager->setCompositeMapSize(compMapResolution); mChunkManager->setCompositeMapLevel(compMapLevel); @@ -296,13 +297,14 @@ QuadTreeWorld::~QuadTreeWorld() { } -/// get the level of vertex detail to render this node at, expressed relative to the native resolution of the data set. +/// get the level of vertex detail to render this node at, expressed relative to the native resolution of the vertex data set, +/// NOT relative to mMinSize as is the case with node LODs. unsigned int getVertexLod(QuadTreeNode* node, int vertexLodMod) { - int lod = Log2(int(node->getSize())); + unsigned int vertexLod = DefaultLodCallback::getNativeLodLevel(node, 1); if (vertexLodMod > 0) { - lod = std::max(0, lod-vertexLodMod); + vertexLod = static_cast(std::max(0, static_cast(vertexLod)-vertexLodMod)); } else if (vertexLodMod < 0) { @@ -313,13 +315,13 @@ unsigned int getVertexLod(QuadTreeNode* node, int vertexLodMod) size *= 2; vertexLodMod = std::min(0, vertexLodMod+1); } - lod += std::abs(vertexLodMod); + vertexLod += std::abs(vertexLodMod); } - return lod; + return vertexLod; } /// 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, const ViewData* vd) +unsigned int getLodFlags(QuadTreeNode* node, unsigned int ourVertexLod, int vertexLodMod, const ViewData* vd) { unsigned int lodFlags = 0; for (unsigned int i=0; i<4; ++i) @@ -332,40 +334,38 @@ unsigned int getLodFlags(QuadTreeNode* node, int ourLod, int vertexLodMod, const // our detail and the neighbour would handle stitching by itself. while (neighbour && !vd->contains(neighbour)) neighbour = neighbour->getParent(); - int lod = 0; + unsigned int lod = 0; if (neighbour) lod = getVertexLod(neighbour, vertexLodMod); - if (lod <= ourLod) // We only need to worry about neighbours less detailed than we are - + if (lod <= ourVertexLod) // We only need to worry about neighbours less detailed than we are - lod = 0; // neighbours with more detail will do the stitching themselves // Use 4 bits for each LOD delta if (lod > 0) { - lodFlags |= static_cast(lod - ourLod) << (4*i); + lodFlags |= (lod - ourVertexLod) << (4*i); } } + // Use the remaining bits for our vertex LOD + lodFlags |= (ourVertexLod << (4*4)); return lodFlags; } -void QuadTreeWorld::loadRenderingNode(ViewDataEntry& entry, ViewData* vd, float cellWorldSize, const osg::Vec4i &gridbounds, bool compile, float reuseDistance) +void QuadTreeWorld::loadRenderingNode(ViewDataEntry& entry, ViewData* vd, float cellWorldSize, const osg::Vec4i &gridbounds, bool compile) { if (!vd->hasChanged() && entry.mRenderingNode) return; - int ourLod = getVertexLod(entry.mNode, mVertexLodMod); - if (vd->hasChanged()) { + unsigned int ourVertexLod = getVertexLod(entry.mNode, mVertexLodMod); // have to recompute the lodFlags in case a neighbour has changed LOD. - unsigned int lodFlags = getLodFlags(entry.mNode, ourLod, mVertexLodMod, vd); + unsigned int lodFlags = getLodFlags(entry.mNode, ourVertexLod, mVertexLodMod, vd); if (lodFlags != entry.mLodFlags) { entry.mRenderingNode = nullptr; entry.mLodFlags = lodFlags; } - // have to revalidate chunks within a custom view distance. - if (mRevalidateDistance && entry.mNode->distance(vd->getViewPoint()) <= mRevalidateDistance + reuseDistance) - entry.mRenderingNode = nullptr; } if (!entry.mRenderingNode) @@ -378,9 +378,7 @@ void QuadTreeWorld::loadRenderingNode(ViewDataEntry& entry, ViewData* vd, float for (QuadTreeWorld::ChunkManager* m : mChunkManagers) { - if (mRevalidateDistance && m->getViewDistance() && entry.mNode->distance(vd->getViewPoint()) > m->getViewDistance() + reuseDistance) - continue; - osg::ref_ptr n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, entry.mLodFlags, activeGrid, vd->getViewPoint(), compile); + osg::ref_ptr n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), DefaultLodCallback::getNativeLodLevel(entry.mNode, mMinSize), entry.mLodFlags, activeGrid, vd->getViewPoint(), compile); if (n) pat->addChild(n); } entry.mRenderingNode = pat; @@ -462,7 +460,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) for (unsigned int i=0; igetNumEntries(); ++i) { ViewDataEntry& entry = vd->getEntry(i); - loadRenderingNode(entry, vd, cellWorldSize, mActiveGrid, false, mViewDataMap->getReuseDistance()); + loadRenderingNode(entry, vd, cellWorldSize, mActiveGrid, false); entry.mRenderingNode->accept(nv); } @@ -541,12 +539,11 @@ void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg:: reporter.addTotal(progressTotal); } - const float reuseDistance = std::max(mViewDataMap->getReuseDistance(), std::abs(distanceModifier)); for (unsigned int i=startEntry; igetNumEntries() && !abort; ++i) { ViewDataEntry& entry = vd->getEntry(i); - loadRenderingNode(entry, vd, cellWorldSize, grid, true, reuseDistance); + loadRenderingNode(entry, vd, cellWorldSize, grid, true); if (pass==0) reporter.addProgress(entry.mNode->getSize()); entry.mNode = nullptr; // Clear node lest we break the neighbours search for the next pass } @@ -584,7 +581,7 @@ void QuadTreeWorld::addChunkManager(QuadTreeWorld::ChunkManager* m) mChunkManagers.push_back(m); mTerrainRoot->setNodeMask(mTerrainRoot->getNodeMask()|m->getNodeMask()); if (m->getViewDistance()) - mRevalidateDistance = std::max(m->getViewDistance(), mRevalidateDistance); + m->setMaxLodLevel(DefaultLodCallback::convertDistanceToLodLevel(m->getViewDistance() + mViewDataMap->getReuseDistance(), mMinSize, mLodFactor)); } void QuadTreeWorld::rebuildViews() diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index 9d21d65fc5..3748f7df98 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -56,14 +56,19 @@ namespace Terrain void setViewDistance(float viewDistance) { mViewDistance = viewDistance; } float getViewDistance() const { return mViewDistance; } + + // Automatically set by addChunkManager based on getViewDistance() + unsigned int getMaxLodLevel() const { return mMaxLodLevel; } + void setMaxLodLevel(unsigned int level) { mMaxLodLevel = level; } private: float mViewDistance = 0.f; + unsigned int mMaxLodLevel = ~0u; }; void addChunkManager(ChunkManager*); private: void ensureQuadTreeBuilt(); - void loadRenderingNode(ViewDataEntry& entry, ViewData* vd, float cellWorldSize, const osg::Vec4i &gridbounds, bool compile, float reuseDistance); + void loadRenderingNode(ViewDataEntry& entry, ViewData* vd, float cellWorldSize, const osg::Vec4i &gridbounds, bool compile); osg::ref_ptr mRootNode; @@ -79,7 +84,6 @@ namespace Terrain float mMinSize; bool mDebugTerrainChunks; std::unique_ptr mDebugChunkManager; - float mRevalidateDistance; }; } diff --git a/components/terrain/viewdata.cpp b/components/terrain/viewdata.cpp index ae23f034a8..033b5f5faa 100644 --- a/components/terrain/viewdata.cpp +++ b/components/terrain/viewdata.cpp @@ -145,7 +145,6 @@ ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPo } vd->setViewPoint(viewPoint); vd->setActiveGrid(activeGrid); - vd->setChanged(true); needsUpdate = true; } } diff --git a/components/terrain/viewdata.hpp b/components/terrain/viewdata.hpp index b7dbc977b1..d51da011a2 100644 --- a/components/terrain/viewdata.hpp +++ b/components/terrain/viewdata.hpp @@ -49,7 +49,7 @@ namespace Terrain double getLastUsageTimeStamp() const { return mLastUsageTimeStamp; } void setLastUsageTimeStamp(double timeStamp) { mLastUsageTimeStamp = timeStamp; } - /// Indicates at least one mNode of mEntries has changed or the view point has moved beyond mReuseDistance. + /// Indicates at least one mNode of mEntries has changed. /// @note Such changes may necessitate a revalidation of cached mRenderingNodes elsewhere depending /// on the parameters that affect the creation of mRenderingNode. bool hasChanged() const { return mChanged; }