diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 8d890a7b2..c7886eed1 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -852,6 +852,9 @@ void OMW::Engine::go() { mViewer->eventTraversal(); mViewer->updateTraversal(); + + mEnvironment.getWorld()->updateWindowManager(); + mViewer->renderingTraversals(); } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 13f88243d..4070d76da 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -363,6 +363,8 @@ namespace MWBase virtual void update (float duration, bool paused) = 0; + virtual void updateWindowManager () = 0; + virtual MWWorld::Ptr placeObject (const MWWorld::ConstPtr& object, float cursorX, float cursorY, int amount) = 0; ///< copy and place an object into the gameworld at the specified cursor position /// @param object diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index b0899f47e..877c35bba 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -254,7 +254,10 @@ namespace MWRender sizeX = std::max(sizeX, 0); sizeY = std::max(sizeY, 0); - mCamera->setViewport(0, mSizeY-sizeY, std::min(mSizeX, sizeX), std::min(mSizeY, sizeY)); + // NB Camera::setViewport has threading issues + osg::ref_ptr stateset = new osg::StateSet; + stateset->setAttributeAndModes(new osg::Viewport(0, mSizeY-sizeY, std::min(mSizeX, sizeX), std::min(mSizeY, sizeY))); + mCamera->setStateSet(stateset); redraw(); } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6ee9b8ae8..4d4a36f6c 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -222,6 +222,7 @@ namespace MWRender mTerrain.reset(new Terrain::QuadTreeWorld(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile)); else mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile)); + mTerrain->setDefaultViewer(mViewer->getCamera()); mCamera.reset(new Camera(mViewer->getCamera())); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 398f91c00..2326607c5 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -528,6 +528,7 @@ namespace MWWorld , mPreloadExteriorGrid(Settings::Manager::getBool("preload exterior grid", "Cells")) , mPreloadDoors(Settings::Manager::getBool("preload doors", "Cells")) , mPreloadFastTravel(Settings::Manager::getBool("preload fast travel", "Cells")) + , mPredictionTime(Settings::Manager::getFloat("prediction time", "Cells")) { mPreloader.reset(new CellPreloader(rendering.getResourceSystem(), physics->getShapeManager(), rendering.getTerrain(), rendering.getLandManager())); mPreloader->setWorkQueue(mRendering.getWorkQueue()); @@ -750,7 +751,7 @@ namespace MWWorld const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); osg::Vec3f moved = playerPos - mLastPlayerPos; - osg::Vec3f predictedPos = playerPos + moved / dt; + osg::Vec3f predictedPos = playerPos + moved / dt * mPredictionTime; if (mCurrentCell->isExterior()) exteriorPositions.push_back(predictedPos); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 586ac4f67..e2fac6438 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -67,6 +67,7 @@ namespace MWWorld bool mPreloadExteriorGrid; bool mPreloadDoors; bool mPreloadFastTravel; + float mPredictionTime; osg::Vec3f mLastPlayerPos; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index c8319dcf7..452bfab0f 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1711,16 +1711,14 @@ namespace MWWorld if (!paused) doPhysics (duration); + updatePlayer(paused); + mPhysics->debugDraw(); mWorldScene->update (duration, paused); - updateWindowManager (); - updateSoundListener(); - updatePlayer(paused); - mSpellPreloadTimer -= duration; if (mSpellPreloadTimer <= 0.f) { diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 7490cb224..8dfa6b906 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -130,7 +130,6 @@ namespace MWWorld Ptr copyObjectToCell(const ConstPtr &ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos); void updateSoundListener(); - void updateWindowManager (); void updatePlayer(bool paused); void preloadSpells(); @@ -465,6 +464,8 @@ namespace MWWorld virtual void update (float duration, bool paused); + virtual void updateWindowManager (); + virtual MWWorld::Ptr placeObject (const MWWorld::ConstPtr& object, float cursorX, float cursorY, int amount); ///< copy and place an object into the gameworld at the specified cursor position /// @param object diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 438303c27..0394adea7 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -99,8 +99,10 @@ void QuadTreeNode::traverse(osg::NodeVisitor &nv) if (!hasValidBounds()) return; - if ((mLodCallback && mLodCallback->isSufficientDetail(this, nv.getEyePoint())) || !getNumChildren()) - getView(nv)->add(this, true); + ViewData* vd = getView(nv); + + if ((mLodCallback && mLodCallback->isSufficientDetail(this, vd->getEyePoint())) || !getNumChildren()) + vd->add(this, true); else osg::Group::traverse(nv); } @@ -130,11 +132,20 @@ ViewData* QuadTreeNode::getView(osg::NodeVisitor &nv) if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) { osgUtil::CullVisitor* cv = static_cast(&nv); - return mViewDataMap->getViewData(cv->getCurrentCamera(), true); + ViewData* vd = mViewDataMap->getViewData(cv->getCurrentCamera()); + vd->setEyePoint(nv.getEyePoint()); + return vd; } else // INTERSECTION_VISITOR { - return mViewDataMap->getViewData(&nv, (nv.referenceCount() > 0)); // if no referenceCount, the visitor was allocated on the stack + static osg::ref_ptr dummyObj = new osg::DummyObject; + ViewData* vd = mViewDataMap->getViewData(dummyObj.get()); + ViewData* defaultView = mViewDataMap->getDefaultView(); + if (defaultView->hasEyePoint()) + vd->setEyePoint(defaultView->getEyePoint()); + else + vd->setEyePoint(nv.getEyePoint()); + return vd; } } diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index f2a80d673..f31064805 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -456,5 +456,10 @@ void QuadTreeWorld::reportStats(unsigned int frameNumber, osg::Stats *stats) stats->setAttribute(frameNumber, "Composite", mCompositeMapRenderer->getCompileSetSize()); } +void QuadTreeWorld::setDefaultViewer(osg::Object *obj) +{ + mViewDataMap->setDefaultViewer(obj); +} + } diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index 8ec75917b..ef33f158e 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -33,6 +33,8 @@ namespace Terrain void reportStats(unsigned int frameNumber, osg::Stats* stats); + virtual void setDefaultViewer(osg::Object* obj); + private: void ensureQuadTreeBuilt(); diff --git a/components/terrain/viewdata.cpp b/components/terrain/viewdata.cpp index 7b3df56b9..5c70f65f2 100644 --- a/components/terrain/viewdata.cpp +++ b/components/terrain/viewdata.cpp @@ -7,6 +7,7 @@ ViewData::ViewData() : mNumEntries(0) , mFrameLastUsed(0) , mChanged(false) + , mHasEyePoint(false) { } @@ -43,6 +44,22 @@ bool ViewData::hasChanged() const return mChanged; } +bool ViewData::hasEyePoint() const +{ + return mHasEyePoint; +} + +void ViewData::setEyePoint(const osg::Vec3f &eye) +{ + mEyePoint = eye; + mHasEyePoint = true; +} + +const osg::Vec3f& ViewData::getEyePoint() const +{ + return mEyePoint; +} + void ViewData::reset(unsigned int frame) { // clear any unused entries @@ -95,14 +112,13 @@ bool ViewData::Entry::set(QuadTreeNode *node, bool visible) } } -ViewData *ViewDataMap::getViewData(osg::Object *viewer, bool ref) +ViewData *ViewDataMap::getViewData(osg::Object *viewer) { Map::const_iterator found = mViews.find(viewer); if (found == mViews.end()) { ViewData* vd = createOrReuseView(); - if (ref) - vd->setViewer(viewer); + vd->setViewer(viewer); mViews[viewer] = vd; return vd; } @@ -130,8 +146,7 @@ void ViewDataMap::clearUnusedViews(unsigned int frame) for (Map::iterator it = mViews.begin(); it != mViews.end(); ) { ViewData* vd = it->second; - if ((!vd->getViewer() // if no ref was held, always need to clear to avoid holding a dangling ref. - || vd->getFrameLastUsed() + 2 < frame)) + if (vd->getFrameLastUsed() + 2 < frame) { vd->setViewer(NULL); vd->clear(); @@ -150,5 +165,15 @@ void ViewDataMap::clear() mViewVector.clear(); } +void ViewDataMap::setDefaultViewer(osg::Object *viewer) +{ + mDefaultViewer = viewer; +} + +ViewData* ViewDataMap::getDefaultView() +{ + return getViewData(mDefaultViewer); +} + } diff --git a/components/terrain/viewdata.hpp b/components/terrain/viewdata.hpp index 30563d566..aaf5b788f 100644 --- a/components/terrain/viewdata.hpp +++ b/components/terrain/viewdata.hpp @@ -52,18 +52,25 @@ namespace Terrain /// @return Have any nodes changed since the last frame bool hasChanged() const; + bool hasEyePoint() const; + + void setEyePoint(const osg::Vec3f& eye); + const osg::Vec3f& getEyePoint() const; + private: std::vector mEntries; unsigned int mNumEntries; unsigned int mFrameLastUsed; bool mChanged; osg::ref_ptr mViewer; + osg::Vec3f mEyePoint; + bool mHasEyePoint; }; class ViewDataMap : public osg::Referenced { public: - ViewData* getViewData(osg::Object* viewer, bool ref); + ViewData* getViewData(osg::Object* viewer); ViewData* createOrReuseView(); @@ -71,6 +78,10 @@ namespace Terrain void clear(); + void setDefaultViewer(osg::Object* viewer); + + ViewData* getDefaultView(); + private: std::list mViewVector; @@ -78,6 +89,8 @@ namespace Terrain Map mViews; std::deque mUnusedViews; + + osg::ref_ptr mDefaultViewer; }; } diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 1eafc8bd7..d0576fbd3 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -14,6 +14,7 @@ namespace osg class Group; class Stats; class Node; + class Object; } namespace Resource @@ -87,6 +88,9 @@ namespace Terrain 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) {} + Storage* getStorage() { return mStorage; } protected: diff --git a/docs/source/reference/modding/settings/cells.rst b/docs/source/reference/modding/settings/cells.rst index bbde2fc32..d2e7cf5be 100644 --- a/docs/source/reference/modding/settings/cells.rst +++ b/docs/source/reference/modding/settings/cells.rst @@ -155,6 +155,19 @@ preload cell expiry delay The amount of time (in seconds) that a preloaded cell will stay in cache after it is no longer referenced or required, for example, after the player has moved away from a door without entering it. +prediction time +--------------- + +:Type: floating point +:Range: >=0 +:Default: 1 + +The amount of time (in seconds) in the future to predict the player position for. This predicted position is used to preload any cells and/or distant terrain required at that position. + +This setting will only have an effect if 'preload enabled' is set or the 'distant terrain' in the Terrain section is set. + +Increasing this setting from its default may help if your computer/hard disk is too slow to preload in time and you see loading screens and/or lag spikes. + cache expiry delay ------------------ diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 9c3667a6e..40c1ed099 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -73,6 +73,9 @@ preload cell cache max = 20 # How long to keep preloaded cells in cache after they're no longer referenced/required (in seconds) preload cell expiry delay = 5 +# The predicted position of the player N seconds in the future will be used for preloading cells and distant terrain +prediction time = 1 + # How long to keep models/textures/collision shapes in cache after they're no longer referenced/required (in seconds) cache expiry delay = 5