Merge pull request #2277 from akortunov/terrain

Camera-related fixes
This commit is contained in:
Bret Curtis 2019-04-12 11:17:20 +02:00 committed by GitHub
commit f4e113e7c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 223 additions and 132 deletions

View File

@ -311,7 +311,6 @@ namespace MWRender
else else
mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug)); mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug));
mTerrain->setDefaultViewer(mViewer->getCamera());
mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells"));
mTerrain->setWorkQueue(mWorkQueue.get()); mTerrain->setWorkQueue(mWorkQueue.get());

View File

@ -161,6 +161,23 @@ private:
osg::Plane mPlane; osg::Plane mPlane;
}; };
/// This callback on the Camera has the effect of a RELATIVE_RF_INHERIT_VIEWPOINT transform mode (which does not exist in OSG).
/// We want to keep the View Point of the parent camera so we will not have to recreate LODs.
class InheritViewPointCallback : public osg::NodeCallback
{
public:
InheritViewPointCallback() {}
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(nv);
osg::ref_ptr<osg::RefMatrix> modelViewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix());
cv->popModelViewMatrix();
cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::ABSOLUTE_RF_INHERIT_VIEWPOINT);
traverse(node, nv);
}
};
/// Moves water mesh away from the camera slightly if the camera gets too close on the Z axis. /// Moves water mesh away from the camera slightly if the camera gets too close on the Z axis.
/// The offset works around graphics artifacts that occurred with the GL_DEPTH_CLAMP when the camera gets extremely close to the mesh (seen on NVIDIA at least). /// The offset works around graphics artifacts that occurred with the GL_DEPTH_CLAMP when the camera gets extremely close to the mesh (seen on NVIDIA at least).
/// Must be added as a Cull callback. /// Must be added as a Cull callback.
@ -224,6 +241,7 @@ public:
setReferenceFrame(osg::Camera::RELATIVE_RF); setReferenceFrame(osg::Camera::RELATIVE_RF);
setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water"));
setName("RefractionCamera"); setName("RefractionCamera");
setCullCallback(new InheritViewPointCallback);
setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Static|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Static|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting);
setNodeMask(Mask_RenderToTexture); setNodeMask(Mask_RenderToTexture);
@ -315,6 +333,7 @@ public:
setReferenceFrame(osg::Camera::RELATIVE_RF); setReferenceFrame(osg::Camera::RELATIVE_RF);
setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water"));
setName("ReflectionCamera"); setName("ReflectionCamera");
setCullCallback(new InheritViewPointCallback);
int reflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); int reflectionDetail = Settings::Manager::getInt("reflection detail", "Water");
reflectionDetail = std::min(4, std::max(isInterior ? 2 : 0, reflectionDetail)); reflectionDetail = std::min(4, std::max(isInterior ? 2 : 0, reflectionDetail));

View File

@ -167,6 +167,44 @@ namespace MWWorld
std::vector<osg::ref_ptr<const osg::Object> > mPreloadedObjects; std::vector<osg::ref_ptr<const osg::Object> > mPreloadedObjects;
}; };
class TerrainPreloadItem : public SceneUtil::WorkItem
{
public:
TerrainPreloadItem(const std::vector<osg::ref_ptr<Terrain::View> >& views, Terrain::World* world, const std::vector<osg::Vec3f>& preloadPositions)
: mAbort(false)
, mTerrainViews(views)
, mWorld(world)
, mPreloadPositions(preloadPositions)
{
}
void storeViews(double referenceTime)
{
for (unsigned int i=0; i<mTerrainViews.size() && i<mPreloadPositions.size(); ++i)
mWorld->storeView(mTerrainViews[i], referenceTime);
}
virtual void doWork()
{
for (unsigned int i=0; i<mTerrainViews.size() && i<mPreloadPositions.size() && !mAbort; ++i)
{
mTerrainViews[i]->reset();
mWorld->preload(mTerrainViews[i], mPreloadPositions[i], mAbort);
}
}
virtual void abort()
{
mAbort = true;
}
private:
std::atomic<bool> mAbort;
std::vector<osg::ref_ptr<Terrain::View> > mTerrainViews;
Terrain::World* mWorld;
std::vector<osg::Vec3f> mPreloadPositions;
};
/// Worker thread item: update the resource system's cache, effectively deleting unused entries. /// Worker thread item: update the resource system's cache, effectively deleting unused entries.
class UpdateCacheItem : public SceneUtil::WorkItem class UpdateCacheItem : public SceneUtil::WorkItem
{ {
@ -288,6 +326,9 @@ namespace MWWorld
} }
mPreloadCells.erase(found); mPreloadCells.erase(found);
if (cell->isExterior() && mTerrainPreloadItem && mTerrainPreloadItem->isDone())
mTerrainPreloadItem->storeViews(0.0);
} }
} }
@ -329,6 +370,12 @@ namespace MWWorld
mWorkQueue->addWorkItem(mUpdateCacheItem, true); mWorkQueue->addWorkItem(mUpdateCacheItem, true);
mLastResourceCacheUpdate = timestamp; mLastResourceCacheUpdate = timestamp;
} }
if (mTerrainPreloadItem && mTerrainPreloadItem->isDone())
{
mTerrainPreloadItem->storeViews(timestamp);
mTerrainPreloadItem = nullptr;
}
} }
void CellPreloader::setExpiryDelay(double expiryDelay) void CellPreloader::setExpiryDelay(double expiryDelay)
@ -366,38 +413,6 @@ namespace MWWorld
mUnrefQueue = unrefQueue; mUnrefQueue = unrefQueue;
} }
class TerrainPreloadItem : public SceneUtil::WorkItem
{
public:
TerrainPreloadItem(const std::vector<osg::ref_ptr<Terrain::View> >& views, Terrain::World* world, const std::vector<osg::Vec3f>& preloadPositions)
: mAbort(false)
, mTerrainViews(views)
, mWorld(world)
, mPreloadPositions(preloadPositions)
{
}
virtual void doWork()
{
for (unsigned int i=0; i<mTerrainViews.size() && i<mPreloadPositions.size() && !mAbort; ++i)
{
mWorld->preload(mTerrainViews[i], mPreloadPositions[i], mAbort);
mTerrainViews[i]->reset(0);
}
}
virtual void abort()
{
mAbort = true;
}
private:
std::atomic<bool> mAbort;
std::vector<osg::ref_ptr<Terrain::View> > mTerrainViews;
Terrain::World* mWorld;
std::vector<osg::Vec3f> mPreloadPositions;
};
void CellPreloader::setTerrainPreloadPositions(const std::vector<osg::Vec3f> &positions) void CellPreloader::setTerrainPreloadPositions(const std::vector<osg::Vec3f> &positions)
{ {
if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone())
@ -418,8 +433,6 @@ namespace MWWorld
mTerrainViews.push_back(mTerrain->createView()); mTerrainViews.push_back(mTerrain->createView());
} }
// TODO: provide some way of giving the preloaded view to the main thread when we enter the cell
// right now, we just use it to make sure the resources are preloaded
mTerrainPreloadPositions = positions; mTerrainPreloadPositions = positions;
mTerrainPreloadItem = new TerrainPreloadItem(mTerrainViews, mTerrain, positions); mTerrainPreloadItem = new TerrainPreloadItem(mTerrainViews, mTerrain, positions);
mWorkQueue->addWorkItem(mTerrainPreloadItem); mWorkQueue->addWorkItem(mTerrainPreloadItem);

View File

@ -31,6 +31,7 @@ namespace MWRender
namespace MWWorld namespace MWWorld
{ {
class CellStore; class CellStore;
class TerrainPreloadItem;
class CellPreloader class CellPreloader
{ {
@ -105,7 +106,7 @@ namespace MWWorld
std::vector<osg::ref_ptr<Terrain::View> > mTerrainViews; std::vector<osg::ref_ptr<Terrain::View> > mTerrainViews;
std::vector<osg::Vec3f> mTerrainPreloadPositions; std::vector<osg::Vec3f> mTerrainPreloadPositions;
osg::ref_ptr<SceneUtil::WorkItem> mTerrainPreloadItem; osg::ref_ptr<TerrainPreloadItem> mTerrainPreloadItem;
osg::ref_ptr<SceneUtil::WorkItem> mUpdateCacheItem; osg::ref_ptr<SceneUtil::WorkItem> mUpdateCacheItem;
}; };

View File

@ -114,9 +114,10 @@ void QuadTreeNode::traverse(osg::NodeVisitor &nv)
if (!hasValidBounds()) if (!hasValidBounds())
return; return;
ViewData* vd = getView(nv); bool needsUpdate = true;
ViewData* vd = getView(nv, needsUpdate);
if ((mLodCallback && mLodCallback->isSufficientDetail(this, distance(vd->getEyePoint()))) || !getNumChildren()) if ((mLodCallback && mLodCallback->isSufficientDetail(this, distance(vd->getViewPoint()))) || !getNumChildren())
vd->add(this, true); vd->add(this, true);
else else
osg::Group::traverse(nv); osg::Group::traverse(nv);
@ -142,26 +143,22 @@ ViewDataMap *QuadTreeNode::getViewDataMap()
return mViewDataMap; return mViewDataMap;
} }
ViewData* QuadTreeNode::getView(osg::NodeVisitor &nv) ViewData* QuadTreeNode::getView(osg::NodeVisitor &nv, bool& needsUpdate)
{ {
ViewData* vd = NULL;
if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR)
{ {
osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(&nv); osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(&nv);
ViewData* vd = mViewDataMap->getViewData(cv->getCurrentCamera()); vd = mViewDataMap->getViewData(cv->getCurrentCamera(), nv.getViewPoint(), needsUpdate);
vd->setEyePoint(nv.getViewPoint());
return vd;
} }
else // INTERSECTION_VISITOR else // INTERSECTION_VISITOR
{ {
osg::Vec3f viewPoint = nv.getViewPoint();
static osg::ref_ptr<osg::Object> dummyObj = new osg::DummyObject; static osg::ref_ptr<osg::Object> dummyObj = new osg::DummyObject;
ViewData* vd = mViewDataMap->getViewData(dummyObj.get()); vd = mViewDataMap->getViewData(dummyObj.get(), viewPoint, needsUpdate);
ViewData* defaultView = mViewDataMap->getDefaultView(); needsUpdate = true;
if (defaultView->hasEyePoint())
vd->setEyePoint(defaultView->getEyePoint());
else
vd->setEyePoint(nv.getEyePoint());
return vd;
} }
return vd;
} }
void QuadTreeNode::setBoundingBox(const osg::BoundingBox &boundingBox) void QuadTreeNode::setBoundingBox(const osg::BoundingBox &boundingBox)

View File

@ -85,7 +85,7 @@ namespace Terrain
ViewDataMap* getViewDataMap(); ViewDataMap* getViewDataMap();
/// Create or retrieve a view for the given traversal. /// Create or retrieve a view for the given traversal.
ViewData* getView(osg::NodeVisitor& nv); ViewData* getView(osg::NodeVisitor& nv, bool& needsUpdate);
private: private:
QuadTreeNode* mParent; QuadTreeNode* mParent;

View File

@ -247,7 +247,7 @@ QuadTreeWorld::~QuadTreeWorld()
} }
void traverse(QuadTreeNode* node, ViewData* vd, osg::NodeVisitor* nv, LodCallback* lodCallback, const osg::Vec3f& eyePoint, bool visible, float maxDist) void traverse(QuadTreeNode* node, ViewData* vd, osg::NodeVisitor* nv, LodCallback* lodCallback, const osg::Vec3f& viewPoint, bool visible, float maxDist)
{ {
if (!node->hasValidBounds()) if (!node->hasValidBounds())
return; return;
@ -255,7 +255,7 @@ void traverse(QuadTreeNode* node, ViewData* vd, osg::NodeVisitor* nv, LodCallbac
if (nv && nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR) if (nv && nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR)
visible = visible && !static_cast<osgUtil::CullVisitor*>(nv)->isCulled(node->getBoundingBox()); visible = visible && !static_cast<osgUtil::CullVisitor*>(nv)->isCulled(node->getBoundingBox());
float dist = node->distance(eyePoint); float dist = node->distance(viewPoint);
if (dist > maxDist) if (dist > maxDist)
return; return;
@ -266,7 +266,7 @@ void traverse(QuadTreeNode* node, ViewData* vd, osg::NodeVisitor* nv, LodCallbac
else else
{ {
for (unsigned int i=0; i<node->getNumChildren(); ++i) for (unsigned int i=0; i<node->getNumChildren(); ++i)
traverse(node->getChild(i), vd, nv, lodCallback, eyePoint, visible, maxDist); traverse(node->getChild(i), vd, nv, lodCallback, viewPoint, visible, maxDist);
} }
} }
@ -367,7 +367,8 @@ void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, C
void QuadTreeWorld::accept(osg::NodeVisitor &nv) void QuadTreeWorld::accept(osg::NodeVisitor &nv)
{ {
if (nv.getVisitorType() != osg::NodeVisitor::CULL_VISITOR && nv.getVisitorType() != osg::NodeVisitor::INTERSECTION_VISITOR) bool isCullVisitor = nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR;
if (!isCullVisitor && nv.getVisitorType() != osg::NodeVisitor::INTERSECTION_VISITOR)
{ {
if (nv.getName().find("AcceptedByComponentsTerrainQuadTreeWorld") != std::string::npos) if (nv.getName().find("AcceptedByComponentsTerrainQuadTreeWorld") != std::string::npos)
{ {
@ -382,9 +383,13 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv)
return; return;
} }
ViewData* vd = mRootNode->getView(nv); bool needsUpdate = false;
ViewData* vd = mRootNode->getView(nv, needsUpdate);
if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) if (needsUpdate)
{
vd->reset();
if (isCullVisitor)
{ {
osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(&nv); osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(&nv);
@ -402,6 +407,16 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv)
} }
else else
mRootNode->traverse(nv); mRootNode->traverse(nv);
}
else if (isCullVisitor)
{
// view point is the same, but must still update visible status in case the camera has rotated
for (unsigned int i=0; i<vd->getNumEntries(); ++i)
{
ViewData::Entry& entry = vd->getEntry(i);
entry.set(entry.mNode, !static_cast<osgUtil::CullVisitor*>(&nv)->isCulled(entry.mNode->getBoundingBox()));
}
}
for (unsigned int i=0; i<vd->getNumEntries(); ++i) for (unsigned int i=0; i<vd->getNumEntries(); ++i)
{ {
@ -416,14 +431,23 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv)
{ {
mCompositeMapRenderer->setImmediate(static_cast<CompositeMap*>(udc->getUserData())); mCompositeMapRenderer->setImmediate(static_cast<CompositeMap*>(udc->getUserData()));
udc->setUserData(nullptr); udc->setUserData(nullptr);
} }
entry.mRenderingNode->accept(nv); entry.mRenderingNode->accept(nv);
} }
} }
vd->reset(nv.getTraversalNumber()); if (!isCullVisitor)
vd->reset(); // we can't reuse intersection views in the next frame because they only contain what is touched by the intersection ray.
mRootNode->getViewDataMap()->clearUnusedViews(nv.getTraversalNumber()); vd->markUnchanged();
double referenceTime = nv.getFrameStamp() ? nv.getFrameStamp()->getReferenceTime() : 0.0;
if (referenceTime != 0.0)
{
vd->setLastUsageTimeStamp(referenceTime);
mViewDataMap->clearUnusedViews(referenceTime);
}
} }
void QuadTreeWorld::ensureQuadTreeBuilt() void QuadTreeWorld::ensureQuadTreeBuilt()
@ -473,18 +497,30 @@ View* QuadTreeWorld::createView()
return new ViewData; return new ViewData;
} }
void QuadTreeWorld::preload(View *view, const osg::Vec3f &eyePoint, std::atomic<bool> &abort) void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, std::atomic<bool> &abort)
{ {
ensureQuadTreeBuilt(); ensureQuadTreeBuilt();
ViewData* vd = static_cast<ViewData*>(view); ViewData* vd = static_cast<ViewData*>(view);
traverse(mRootNode.get(), vd, nullptr, mRootNode->getLodCallback(), eyePoint, false, mViewDistance); vd->setViewPoint(viewPoint);
traverse(mRootNode.get(), vd, nullptr, mRootNode->getLodCallback(), viewPoint, false, mViewDistance);
for (unsigned int i=0; i<vd->getNumEntries() && !abort; ++i) for (unsigned int i=0; i<vd->getNumEntries() && !abort; ++i)
{ {
ViewData::Entry& entry = vd->getEntry(i); ViewData::Entry& entry = vd->getEntry(i);
loadRenderingNode(entry, vd, mVertexLodMod, mChunkManager.get()); loadRenderingNode(entry, vd, mVertexLodMod, mChunkManager.get());
} }
vd->markUnchanged();
}
void QuadTreeWorld::storeView(const View* view, double referenceTime)
{
osg::ref_ptr<osg::Object> dummy = new osg::DummyObject;
const ViewData* vd = static_cast<const ViewData*>(view);
bool needsUpdate = false;
ViewData* stored = mViewDataMap->getViewData(dummy, vd->getViewPoint(), needsUpdate);
stored->copyFrom(*vd);
stored->setLastUsageTimeStamp(referenceTime);
} }
void QuadTreeWorld::reportStats(unsigned int frameNumber, osg::Stats *stats) void QuadTreeWorld::reportStats(unsigned int frameNumber, osg::Stats *stats)
@ -492,11 +528,6 @@ void QuadTreeWorld::reportStats(unsigned int frameNumber, osg::Stats *stats)
stats->setAttribute(frameNumber, "Composite", mCompositeMapRenderer->getCompileSetSize()); stats->setAttribute(frameNumber, "Composite", mCompositeMapRenderer->getCompileSetSize());
} }
void QuadTreeWorld::setDefaultViewer(osg::Object *obj)
{
mViewDataMap->setDefaultViewer(obj);
}
void QuadTreeWorld::loadCell(int x, int y) void QuadTreeWorld::loadCell(int x, int y)
{ {
// fallback behavior only for undefined cells (every other is already handled in quadtree) // fallback behavior only for undefined cells (every other is already handled in quadtree)

View File

@ -38,11 +38,10 @@ namespace Terrain
View* createView(); View* createView();
void preload(View* view, const osg::Vec3f& eyePoint, std::atomic<bool>& abort); void preload(View* view, const osg::Vec3f& eyePoint, std::atomic<bool>& abort);
void storeView(const View* view, double referenceTime);
void reportStats(unsigned int frameNumber, osg::Stats* stats); void reportStats(unsigned int frameNumber, osg::Stats* stats);
virtual void setDefaultViewer(osg::Object* obj);
private: private:
void ensureQuadTreeBuilt(); void ensureQuadTreeBuilt();

View File

@ -15,7 +15,7 @@ class MyView : public View
public: public:
osg::ref_ptr<osg::Node> mLoaded; osg::ref_ptr<osg::Node> mLoaded;
virtual void reset(unsigned int frame) {} virtual void reset() {}
}; };
TerrainGrid::TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask, int borderMask) TerrainGrid::TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask, int borderMask)

View File

@ -5,9 +5,9 @@ namespace Terrain
ViewData::ViewData() ViewData::ViewData()
: mNumEntries(0) : mNumEntries(0)
, mFrameLastUsed(0) , mLastUsageTimeStamp(0.0)
, mChanged(false) , mChanged(false)
, mHasEyePoint(false) , mHasViewPoint(false)
{ {
} }
@ -17,6 +17,15 @@ ViewData::~ViewData()
} }
void ViewData::copyFrom(const ViewData& other)
{
mNumEntries = other.mNumEntries;
mEntries = other.mEntries;
mChanged = other.mChanged;
mHasViewPoint = other.mHasViewPoint;
mViewPoint = other.mViewPoint;
}
void ViewData::add(QuadTreeNode *node, bool visible) void ViewData::add(QuadTreeNode *node, bool visible)
{ {
unsigned int index = mNumEntries++; unsigned int index = mNumEntries++;
@ -44,23 +53,23 @@ bool ViewData::hasChanged() const
return mChanged; return mChanged;
} }
bool ViewData::hasEyePoint() const bool ViewData::hasViewPoint() const
{ {
return mHasEyePoint; return mHasViewPoint;
} }
void ViewData::setEyePoint(const osg::Vec3f &eye) void ViewData::setViewPoint(const osg::Vec3f &viewPoint)
{ {
mEyePoint = eye; mViewPoint = viewPoint;
mHasEyePoint = true; mHasViewPoint = true;
} }
const osg::Vec3f& ViewData::getEyePoint() const const osg::Vec3f& ViewData::getViewPoint() const
{ {
return mEyePoint; return mViewPoint;
} }
void ViewData::reset(unsigned int frame) void ViewData::reset()
{ {
// clear any unused entries // clear any unused entries
for (unsigned int i=mNumEntries; i<mEntries.size(); ++i) for (unsigned int i=mNumEntries; i<mEntries.size(); ++i)
@ -69,8 +78,6 @@ void ViewData::reset(unsigned int frame)
// reset index for next frame // reset index for next frame
mNumEntries = 0; mNumEntries = 0;
mChanged = false; mChanged = false;
mFrameLastUsed = frame;
} }
void ViewData::clear() void ViewData::clear()
@ -78,8 +85,9 @@ void ViewData::clear()
for (unsigned int i=0; i<mEntries.size(); ++i) for (unsigned int i=0; i<mEntries.size(); ++i)
mEntries[i].set(nullptr, false); mEntries[i].set(nullptr, false);
mNumEntries = 0; mNumEntries = 0;
mFrameLastUsed = 0; mLastUsageTimeStamp = 0;
mChanged = false; mChanged = false;
mHasViewPoint = false;
} }
bool ViewData::contains(QuadTreeNode *node) bool ViewData::contains(QuadTreeNode *node)
@ -112,17 +120,41 @@ bool ViewData::Entry::set(QuadTreeNode *node, bool visible)
} }
} }
ViewData *ViewDataMap::getViewData(osg::Object *viewer) bool suitable(ViewData* vd, const osg::Vec3f& viewPoint, float& maxDist)
{
return vd->hasViewPoint() && (vd->getViewPoint() - viewPoint).length2() < maxDist*maxDist;
}
ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPoint, bool& needsUpdate)
{ {
Map::const_iterator found = mViews.find(viewer); Map::const_iterator found = mViews.find(viewer);
ViewData* vd = nullptr;
if (found == mViews.end()) if (found == mViews.end())
{ {
ViewData* vd = createOrReuseView(); vd = createOrReuseView();
mViews[viewer] = vd; mViews[viewer] = vd;
return vd;
} }
else else
return found->second; vd = found->second;
if (!suitable(vd, viewPoint, mReuseDistance))
{
for (Map::const_iterator other = mViews.begin(); other != mViews.end(); ++other)
{
if (suitable(other->second, viewPoint, mReuseDistance) && other->second->getNumEntries())
{
vd->copyFrom(*other->second);
needsUpdate = false;
return vd;
}
}
vd->setViewPoint(viewPoint);
needsUpdate = true;
}
else
needsUpdate = false;
return vd;
} }
ViewData *ViewDataMap::createOrReuseView() ViewData *ViewDataMap::createOrReuseView()
@ -140,12 +172,12 @@ ViewData *ViewDataMap::createOrReuseView()
} }
} }
void ViewDataMap::clearUnusedViews(unsigned int frame) void ViewDataMap::clearUnusedViews(double referenceTime)
{ {
for (Map::iterator it = mViews.begin(); it != mViews.end(); ) for (Map::iterator it = mViews.begin(); it != mViews.end(); )
{ {
ViewData* vd = it->second; ViewData* vd = it->second;
if (vd->getFrameLastUsed() + 2 < frame) if (vd->getLastUsageTimeStamp() + mExpiryDelay < referenceTime)
{ {
vd->clear(); vd->clear();
mUnusedViews.push_back(vd); mUnusedViews.push_back(vd);
@ -163,15 +195,4 @@ void ViewDataMap::clear()
mViewVector.clear(); mViewVector.clear();
} }
void ViewDataMap::setDefaultViewer(osg::Object *viewer)
{
mDefaultViewer = viewer;
}
ViewData* ViewDataMap::getDefaultView()
{
return getViewData(mDefaultViewer);
}
} }

View File

@ -21,12 +21,14 @@ namespace Terrain
void add(QuadTreeNode* node, bool visible); void add(QuadTreeNode* node, bool visible);
void reset(unsigned int frame); void reset();
void clear(); void clear();
bool contains(QuadTreeNode* node); bool contains(QuadTreeNode* node);
void copyFrom(const ViewData& other);
struct Entry struct Entry
{ {
Entry(); Entry();
@ -44,49 +46,55 @@ namespace Terrain
Entry& getEntry(unsigned int i); Entry& getEntry(unsigned int i);
unsigned int getFrameLastUsed() const { return mFrameLastUsed; } double getLastUsageTimeStamp() const { return mLastUsageTimeStamp; }
void setLastUsageTimeStamp(double timeStamp) { mLastUsageTimeStamp = timeStamp; }
/// @return Have any nodes changed since the last frame /// @return Have any nodes changed since the last frame
bool hasChanged() const; bool hasChanged() const;
void markUnchanged() { mChanged = false; }
bool hasEyePoint() const; bool hasViewPoint() const;
void setEyePoint(const osg::Vec3f& eye); void setViewPoint(const osg::Vec3f& viewPoint);
const osg::Vec3f& getEyePoint() const; const osg::Vec3f& getViewPoint() const;
private: private:
std::vector<Entry> mEntries; std::vector<Entry> mEntries;
unsigned int mNumEntries; unsigned int mNumEntries;
unsigned int mFrameLastUsed; double mLastUsageTimeStamp;
bool mChanged; bool mChanged;
osg::Vec3f mEyePoint; osg::Vec3f mViewPoint;
bool mHasEyePoint; bool mHasViewPoint;
float mReuseDistance;
}; };
class ViewDataMap : public osg::Referenced class ViewDataMap : public osg::Referenced
{ {
public: public:
ViewData* getViewData(osg::Object* viewer); 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.
// 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)
{}
ViewData* getViewData(osg::Object* viewer, const osg::Vec3f& viewPoint, bool& needsUpdate);
ViewData* createOrReuseView(); ViewData* createOrReuseView();
void clearUnusedViews(unsigned int frame); void clearUnusedViews(double referenceTime);
void clear(); void clear();
void setDefaultViewer(osg::Object* viewer);
ViewData* getDefaultView();
private: private:
std::list<ViewData> mViewVector; std::list<ViewData> mViewVector;
typedef std::map<osg::ref_ptr<osg::Object>, ViewData*> Map; typedef std::map<osg::ref_ptr<osg::Object>, ViewData*> Map;
Map mViews; Map mViews;
std::deque<ViewData*> mUnusedViews; float mReuseDistance;
float mExpiryDelay; // time in seconds for unused view to be removed
osg::ref_ptr<osg::Object> mDefaultViewer; std::deque<ViewData*> mUnusedViews;
}; };
} }

View File

@ -8,6 +8,7 @@
#include <atomic> #include <atomic>
#include <memory> #include <memory>
#include <set> #include <set>
#include <atomic>
#include "defs.hpp" #include "defs.hpp"
#include "cellborder.hpp" #include "cellborder.hpp"
@ -48,7 +49,7 @@ namespace Terrain
virtual ~View() {} virtual ~View() {}
/// Reset internal structure so that the next addition to the view will override the previous frame's contents. /// Reset internal structure so that the next addition to the view will override the previous frame's contents.
virtual void reset(unsigned int frame) = 0; virtual void reset() = 0;
}; };
/** /**
@ -81,7 +82,7 @@ namespace Terrain
/// @note Thread safe. /// @note Thread safe.
virtual void clearAssociatedCaches(); virtual void clearAssociatedCaches();
/// Load a terrain cell at maximum LOD and store it in the View for later use. /// Load a terrain cell and store it in the View for later use.
/// @note Thread safe. /// @note Thread safe.
virtual void cacheCell(View* view, int x, int y) {} virtual void cacheCell(View* view, int x, int y) {}
@ -102,13 +103,15 @@ namespace Terrain
virtual View* createView() { return nullptr; } virtual View* createView() { return nullptr; }
/// @note Thread safe, as long as you do not attempt to load into the same view from multiple threads. /// @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& eyePoint, std::atomic<bool>& abort) {}
virtual void preload(View* view, const osg::Vec3f& viewPoint, std::atomic<bool>& abort) {}
/// 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 void reportStats(unsigned int frameNumber, osg::Stats* stats) {} virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) {}
/// Set the default viewer (usually a Camera), used as viewpoint for any viewers that don't use their own viewpoint.
virtual void setDefaultViewer(osg::Object* obj) {}
virtual void setViewDistance(float distance) {} virtual void setViewDistance(float distance) {}
Storage* getStorage() { return mStorage; } Storage* getStorage() { return mStorage; }