From 46e1ed660c6bb37517ed40e3425944aafd27f162 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 28 Feb 2019 22:10:13 +0400 Subject: [PATCH 1/7] Revert "Render default land texture for Wilderness cells with distant terrain" This reverts commit 888c2d9a33c27b764e55793ecc5cee7a4364d446. --- components/terrain/quadtreeworld.cpp | 37 +++++++++++++++++++--------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 8d54f62ce..a97aba68c 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -201,22 +201,35 @@ public: node->setLodCallback(parent->getLodCallback()); node->setViewDataMap(mViewDataMap); - if (node->getSize() > mMinSize) + if (center.x() - size > mMaxX + || center.x() + size < mMinX + || center.y() - size > mMaxY + || center.y() + size < mMinY ) + // Out of bounds of the actual terrain - this will happen because + // we rounded the size up to the next power of two { - addChildren(node); + // Still create and return an empty node so as to not break the assumption that each QuadTreeNode has either 4 or 0 children. return node; } - // We arrived at a leaf - float minZ, maxZ; - mStorage->getMinMaxHeights(size, center, minZ, maxZ); - - float cellWorldSize = mStorage->getCellWorldSize(); - osg::BoundingBox boundingBox(osg::Vec3f((center.x()-halfSize)*cellWorldSize, (center.y()-halfSize)*cellWorldSize, minZ), - osg::Vec3f((center.x()+halfSize)*cellWorldSize, (center.y()+halfSize)*cellWorldSize, maxZ)); - node->setBoundingBox(boundingBox); - - return node; + if (node->getSize() <= mMinSize) + { + // We arrived at a leaf + float minZ,maxZ; + if (mStorage->getMinMaxHeights(size, center, minZ, maxZ)) + { + float cellWorldSize = mStorage->getCellWorldSize(); + osg::BoundingBox boundingBox(osg::Vec3f((center.x()-size)*cellWorldSize, (center.y()-size)*cellWorldSize, minZ), + osg::Vec3f((center.x()+size)*cellWorldSize, (center.y()+size)*cellWorldSize, maxZ)); + node->setBoundingBox(boundingBox); + } + return node; + } + else + { + addChildren(node); + return node; + } } osg::ref_ptr getRootNode() From a6fd077537bb8c114ae2b1ad5a1254ef6af1a87c Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 28 Feb 2019 22:19:48 +0400 Subject: [PATCH 2/7] Render nearby default cells if the Distant Terrain is disabled --- components/terrain/quadtreeworld.cpp | 22 +++++++++++++++++++++- components/terrain/quadtreeworld.hpp | 7 ++++++- components/terrain/terraingrid.cpp | 5 +++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index a97aba68c..6a4ad6703 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -249,7 +249,7 @@ private: }; QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resource::ResourceSystem *resourceSystem, Storage *storage, int nodeMask, int preCompileMask, int borderMask, int compMapResolution, float compMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize) - : World(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask) + : TerrainGrid(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask) , mViewDataMap(new ViewDataMap) , mQuadTreeBuilt(false) , mLodFactor(lodFactor) @@ -515,5 +515,25 @@ void QuadTreeWorld::setDefaultViewer(osg::Object *obj) mViewDataMap->setDefaultViewer(obj); } +void QuadTreeWorld::loadCell(int x, int y) +{ + // fallback behavior only for undefined cells (every other is already handled in quadtree) + float dummy; + if (!mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy)) + TerrainGrid::loadCell(x,y); + else + World::loadCell(x,y); +} + +void QuadTreeWorld::unloadCell(int x, int y) +{ + // fallback behavior only for undefined cells (every other is already handled in quadtree) + float dummy; + if (!mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy)) + TerrainGrid::unloadCell(x,y); + else + World::unloadCell(x,y); +} + } diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index f724c44b1..f02f5df4a 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -2,6 +2,7 @@ #define COMPONENTS_TERRAIN_QUADTREEWORLD_H #include "world.hpp" +#include "terraingrid.hpp" #include @@ -16,7 +17,7 @@ namespace Terrain class ViewDataMap; /// @brief Terrain implementation that loads cells into a Quad Tree, with geometry LOD and texture LOD. The entire world is displayed at all times. - class QuadTreeWorld : public Terrain::World + class QuadTreeWorld : public TerrainGrid // note: derived from TerrainGrid is only to render default cells (see loadCell) { public: QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask, int borderMask, int compMapResolution, float comMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize); @@ -28,6 +29,10 @@ namespace Terrain virtual void enable(bool enabled); 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); diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 1888a02b3..a7d43f673 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -5,6 +5,7 @@ #include #include "chunkmanager.hpp" +#include "compositemaprenderer.hpp" namespace Terrain { @@ -61,6 +62,10 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu if (parent) parent->addChild(node); + osg::UserDataContainer* udc = node->getUserDataContainer(); + if (udc && udc->getUserData()) + mCompositeMapRenderer->setImmediate(static_cast(udc->getUserData())); + return node; } } From e0cf460ba3288cb3cc0c43386da6453f6f63da9c Mon Sep 17 00:00:00 2001 From: bzzt Date: Wed, 20 Feb 2019 13:37:00 +0000 Subject: [PATCH 3/7] Do not load terrain beyond the viewing distance --- apps/openmw/mwrender/renderingmanager.cpp | 4 ++ components/terrain/quadtreenode.cpp | 26 ++++++++++++- components/terrain/quadtreenode.hpp | 4 +- components/terrain/quadtreeworld.cpp | 45 ++++++----------------- components/terrain/quadtreeworld.hpp | 3 ++ components/terrain/world.hpp | 2 + 6 files changed, 48 insertions(+), 36 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index f54e423f1..42bd0c69b 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1198,6 +1198,10 @@ namespace MWRender mUniformNear->set(mNearClip); mUniformFar->set(mViewDistance); + + // Since our fog is not radial yet, we should take FOV in account, otherwise terrain near viewing distance may disappear. + float distanceMult = std::cos(osg::DegreesToRadians(mFieldOfView)/2.f); + mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f/distanceMult : 1.f)); } void RenderingManager::updateTextureFiltering() diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index f8237306e..0c157cd48 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -76,6 +76,30 @@ QuadTreeNode *QuadTreeNode::getNeighbour(Direction dir) return mNeighbours[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(); + } +} + void QuadTreeNode::initNeighbours() { for (int i=0; i<4; ++i) @@ -92,7 +116,7 @@ void QuadTreeNode::traverse(osg::NodeVisitor &nv) ViewData* vd = getView(nv); - if ((mLodCallback && mLodCallback->isSufficientDetail(this, vd->getEyePoint())) || !getNumChildren()) + if ((mLodCallback && mLodCallback->isSufficientDetail(this, distance(vd->getEyePoint()))) || !getNumChildren()) vd->add(this, true); else osg::Group::traverse(nv); diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp index 618429c5c..336a257fb 100644 --- a/components/terrain/quadtreenode.hpp +++ b/components/terrain/quadtreenode.hpp @@ -23,7 +23,7 @@ namespace Terrain public: virtual ~LodCallback() {} - virtual bool isSufficientDetail(QuadTreeNode *node, const osg::Vec3f& eyePoint) = 0; + virtual bool isSufficientDetail(QuadTreeNode *node, float dist) = 0; }; class ViewDataMap; @@ -49,6 +49,8 @@ namespace Terrain child->addParent(this); }; + float distance(const osg::Vec3f& v) const; + /// Returns our direction relative to the parent node, or Root if we are the root node. ChildDirection getDirection() { return mDirection; } diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 6a4ad6703..597073229 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -40,33 +40,6 @@ namespace return targetlevel; } - float distanceToBox(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(); - } - } - } namespace Terrain @@ -81,9 +54,8 @@ public: { } - virtual bool isSufficientDetail(QuadTreeNode* node, const osg::Vec3f& eyePoint) + virtual bool isSufficientDetail(QuadTreeNode* node, float dist) { - float dist = distanceToBox(node->getBoundingBox(), eyePoint); int nativeLodLevel = Log2(static_cast(node->getSize()/mMinSize)); int lodLevel = Log2(static_cast(dist/(Constants::CellSizeInUnits*mMinSize*mFactor))); @@ -254,6 +226,7 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour , mQuadTreeBuilt(false) , mLodFactor(lodFactor) , mVertexLodMod(vertexLodMod) + , mViewDistance(std::numeric_limits::max()) { // No need for culling on the Drawable / Transform level as the quad tree performs the culling already. mChunkManager->setCullingActive(false); @@ -269,7 +242,7 @@ QuadTreeWorld::~QuadTreeWorld() } -void traverse(QuadTreeNode* node, ViewData* vd, osg::NodeVisitor* nv, LodCallback* lodCallback, const osg::Vec3f& eyePoint, bool visible) +void traverse(QuadTreeNode* node, ViewData* vd, osg::NodeVisitor* nv, LodCallback* lodCallback, const osg::Vec3f& eyePoint, bool visible, float maxDist) { if (!node->hasValidBounds()) return; @@ -277,14 +250,18 @@ void traverse(QuadTreeNode* node, ViewData* vd, osg::NodeVisitor* nv, LodCallbac if (nv && nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR) visible = visible && !static_cast(nv)->isCulled(node->getBoundingBox()); - bool stopTraversal = (lodCallback && lodCallback->isSufficientDetail(node, eyePoint)) || !node->getNumChildren(); + float dist = node->distance(eyePoint); + if (dist > maxDist) + return; + + bool stopTraversal = (lodCallback && lodCallback->isSufficientDetail(node, dist)) || !node->getNumChildren(); if (stopTraversal) vd->add(node, visible); else { for (unsigned int i=0; igetNumChildren(); ++i) - traverse(node->getChild(i), vd, nv, lodCallback, eyePoint, visible); + traverse(node->getChild(i), vd, nv, lodCallback, eyePoint, visible, maxDist); } } @@ -416,7 +393,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) traverseToCell(mRootNode.get(), vd, x,y); } else - traverse(mRootNode.get(), vd, cv, mRootNode->getLodCallback(), cv->getViewPoint(), true); + traverse(mRootNode.get(), vd, cv, mRootNode->getLodCallback(), cv->getViewPoint(), true, mViewDistance); } else mRootNode->traverse(nv); @@ -496,7 +473,7 @@ void QuadTreeWorld::preload(View *view, const osg::Vec3f &eyePoint) ensureQuadTreeBuilt(); ViewData* vd = static_cast(view); - traverse(mRootNode.get(), vd, nullptr, mRootNode->getLodCallback(), eyePoint, false); + traverse(mRootNode.get(), vd, nullptr, mRootNode->getLodCallback(), eyePoint, false, mViewDistance); for (unsigned int i=0; igetNumEntries(); ++i) { diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index f02f5df4a..bb09048c2 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -28,6 +28,8 @@ namespace Terrain virtual void enable(bool enabled); + virtual void setViewDistance(float distance) { mViewDistance = distance; } + void cacheCell(View *view, int x, int y); /// @note Not thread safe. virtual void loadCell(int x, int y); @@ -52,6 +54,7 @@ namespace Terrain bool mQuadTreeBuilt; float mLodFactor; int mVertexLodMod; + float mViewDistance; }; } diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 4fb724ebb..83a2655fd 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -100,6 +100,8 @@ namespace Terrain /// 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) {} + Storage* getStorage() { return mStorage; } protected: From 36fa51b6ad8a947d70bea2e726bfca87976c2c4a Mon Sep 17 00:00:00 2001 From: bzzt Date: Wed, 20 Feb 2019 13:37:00 +0000 Subject: [PATCH 4/7] Fix bounding box calculation for terrain shapes --- components/terrain/chunkmanager.cpp | 2 +- components/terrain/quadtreeworld.cpp | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 80f414541..1cdfcf243 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -188,7 +188,7 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve geometry->setUseDisplayList(false); geometry->setUseVertexBufferObjects(true); - if (chunkSize <= 2.f) + if (chunkSize <= 1.f) geometry->setLightListCallback(new SceneUtil::LightListCallback); unsigned int numVerts = (mStorage->getCellVertices()-1) * chunkSize / (1 << lod) + 1; diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 597073229..efe0dd58a 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -173,10 +173,10 @@ public: node->setLodCallback(parent->getLodCallback()); node->setViewDataMap(mViewDataMap); - if (center.x() - size > mMaxX - || center.x() + size < mMinX - || center.y() - size > mMaxY - || center.y() + size < mMinY ) + if (center.x() - halfSize > mMaxX + || center.x() + halfSize < mMinX + || center.y() - halfSize > mMaxY + || center.y() + halfSize < mMinY ) // Out of bounds of the actual terrain - this will happen because // we rounded the size up to the next power of two { @@ -191,8 +191,8 @@ public: if (mStorage->getMinMaxHeights(size, center, minZ, maxZ)) { float cellWorldSize = mStorage->getCellWorldSize(); - osg::BoundingBox boundingBox(osg::Vec3f((center.x()-size)*cellWorldSize, (center.y()-size)*cellWorldSize, minZ), - osg::Vec3f((center.x()+size)*cellWorldSize, (center.y()+size)*cellWorldSize, maxZ)); + osg::BoundingBox boundingBox(osg::Vec3f((center.x()-halfSize)*cellWorldSize, (center.y()-halfSize)*cellWorldSize, minZ), + osg::Vec3f((center.x()+halfSize)*cellWorldSize, (center.y()+halfSize)*cellWorldSize, maxZ)); node->setBoundingBox(boundingBox); } return node; From 6029ed4eccd7adc97faa7718c582064c1f316119 Mon Sep 17 00:00:00 2001 From: bzzt Date: Wed, 20 Feb 2019 13:37:00 +0000 Subject: [PATCH 5/7] Reject empty quad tree nodes at the cell level without land data --- apps/openmw/mwrender/terrainstorage.cpp | 9 +++++++++ apps/openmw/mwrender/terrainstorage.hpp | 2 ++ components/terrain/quadtreeworld.cpp | 5 +++++ components/terrain/storage.hpp | 8 ++++++++ 4 files changed, 24 insertions(+) diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 7894a8393..528ce70ea 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -22,6 +22,15 @@ namespace MWRender mResourceSystem->removeResourceManager(mLandManager.get()); } + bool TerrainStorage::hasData(int cellX, int cellY) + { + const MWWorld::ESMStore &esmStore = + MWBase::Environment::get().getWorld()->getStore(); + + const ESM::Land* land = esmStore.get().search(cellX, cellY); + return land != nullptr; + } + void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY) { minX = 0, minY = 0, maxX = 0, maxY = 0; diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index 6f716e752..cae56d3aa 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -23,6 +23,8 @@ namespace MWRender virtual osg::ref_ptr getLand (int cellX, int cellY); virtual const ESM::LandTexture* getLandTexture(int index, short plugin); + virtual bool hasData(int cellX, int cellY) override; + /// Get bounds of the whole terrain in cell units virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index efe0dd58a..cf877142a 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -184,6 +184,11 @@ public: return node; } + // Do not add child nodes for default cells without data. + // size = 1 means that the single shape covers the whole cell. + if (node->getSize() == 1 && !mStorage->hasData(center.x()-0.5, center.y()-0.5)) + return node; + if (node->getSize() <= mMinSize) { // We arrived at a leaf diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp index ebac1148c..3bc20aa59 100644 --- a/components/terrain/storage.hpp +++ b/components/terrain/storage.hpp @@ -28,6 +28,14 @@ namespace Terrain /// Get bounds of the whole terrain in cell units virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) = 0; + /// Return true if there is land data for this cell + /// May be overriden for a faster implementation + virtual bool hasData(int cellX, int cellY) + { + float dummy; + return getMinMaxHeights(1, osg::Vec2f(cellX+0.5, cellY+0.5), dummy, dummy); + } + /// Get the minimum and maximum heights of a terrain region. /// @note Will only be called for chunks with size = minBatchSize, i.e. leafs of the quad tree. /// Larger chunks can simply merge AABB of children. From 540709fdae4147462430875cc7f37c6e2ef11a68 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 2 Mar 2019 19:01:25 +0400 Subject: [PATCH 6/7] Add a changelog entry for terrain optimization --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6c510fa1..92b2fa9ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ Feature #4887: Add openmw command option to set initial random seed Feature #4890: Make Distant Terrain configurable Task #4686: Upgrade media decoder to a more current FFmpeg API + Task #4695: Optimize Distant Terrain memory consumption 0.45.0 ------ From c6cb91ce614eefb32153b496e736edf9e419fb75 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 3 Mar 2019 21:29:51 +0400 Subject: [PATCH 7/7] Limit maximum FOV value --- apps/openmw/mwrender/renderingmanager.cpp | 10 +++++++--- docs/source/reference/modding/settings/camera.rst | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 42bd0c69b..5f35b401e 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -371,8 +371,10 @@ namespace MWRender mNearClip = Settings::Manager::getFloat("near clip", "Camera"); mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); - mFieldOfView = Settings::Manager::getFloat("field of view", "Camera"); - mFirstPersonFieldOfView = Settings::Manager::getFloat("first person field of view", "Camera"); + float fov = Settings::Manager::getFloat("field of view", "Camera"); + mFieldOfView = std::min(std::max(1.f, fov), 179.f); + float firstPersonFov = Settings::Manager::getFloat("first person field of view", "Camera"); + mFirstPersonFieldOfView = std::min(std::max(1.f, firstPersonFov), 179.f); mStateUpdater->setFogEnd(mViewDistance); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip)); @@ -1200,7 +1202,9 @@ namespace MWRender mUniformFar->set(mViewDistance); // Since our fog is not radial yet, we should take FOV in account, otherwise terrain near viewing distance may disappear. - float distanceMult = std::cos(osg::DegreesToRadians(mFieldOfView)/2.f); + // Limit FOV here just for sure, otherwise viewing distance can be too high. + fov = std::min(mFieldOfView, 140.f); + float distanceMult = std::cos(osg::DegreesToRadians(fov)/2.f); mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f/distanceMult : 1.f)); } diff --git a/docs/source/reference/modding/settings/camera.rst b/docs/source/reference/modding/settings/camera.rst index aed68658f..6f13e3305 100644 --- a/docs/source/reference/modding/settings/camera.rst +++ b/docs/source/reference/modding/settings/camera.rst @@ -94,7 +94,7 @@ field of view ------------- :Type: floating point -:Range: 0-360 +:Range: 1-179 :Default: 55.0 Sets the camera field of view in degrees. Recommended values range from 30 degrees to 110 degrees. @@ -109,7 +109,7 @@ first person field of view -------------------------- :Type: floating point -:Range: 0-360 +:Range: 1-179 :Default: 55.0 This setting controls the field of view for first person meshes such as the player's hands and held objects.