From 6a3d27ce82128e9b257868f7cd21450b8472cca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Wed, 31 Aug 2022 22:02:19 +0200 Subject: [PATCH 1/3] Add a dictionary to retrieve the LOD mesh name of a mesh cache --- apps/openmw/mwrender/objectpaging.cpp | 16 +++++++- apps/openmw/mwrender/objectpaging.hpp | 7 +++- components/misc/pathhelpers.hpp | 7 +++- components/misc/resourcehelpers.cpp | 37 +++++++++++++++++++ components/misc/resourcehelpers.hpp | 1 + .../reference/modding/settings/terrain.rst | 9 +++++ files/settings-default.cfg | 3 ++ 7 files changed, 76 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 53af609a71..563d2ce23c 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -76,6 +76,7 @@ namespace MWRender 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) { + lod = static_cast(lodFlags >> (4 * 4)); if (activeGrid && !mActiveGrid) return nullptr; @@ -86,7 +87,7 @@ namespace MWRender return static_cast(obj.get()); else { - osg::ref_ptr node = createChunk(size, center, activeGrid, viewPoint, compile); + osg::ref_ptr node = createChunk(size, center, activeGrid, viewPoint, compile, lod); mCache->addEntryToObjectCache(id, node.get()); return node; } @@ -411,7 +412,7 @@ namespace MWRender 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::ref_ptr ObjectPaging::createChunk(float size, const osg::Vec2f& center, bool activeGrid, const osg::Vec3f& viewPoint, bool compile, unsigned char lod) { osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size/2.f), std::floor(center.y() - size/2.f)); @@ -548,6 +549,17 @@ namespace MWRender } } + if (!activeGrid) + { + std::lock_guard lock(mLODNameCacheMutex); + LODNameCacheKey key{ model, lod }; + LODNameCache::const_iterator found = mLODNameCache.lower_bound(key); + if (found != mLODNameCache.end() && found->first == key) + model = found->second; + else + model = mLODNameCache.insert(found, { key, Misc::ResourceHelpers::getLODMeshName(model, mSceneManager->getVFS(), lod) })->second; + } + osg::ref_ptr cnode = mSceneManager->getTemplate(model, false); if (activeGrid) diff --git a/apps/openmw/mwrender/objectpaging.hpp b/apps/openmw/mwrender/objectpaging.hpp index 4820f150ab..246cef178f 100644 --- a/apps/openmw/mwrender/objectpaging.hpp +++ b/apps/openmw/mwrender/objectpaging.hpp @@ -29,7 +29,7 @@ namespace MWRender 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); + osg::ref_ptr createChunk(float size, const osg::Vec2f& center, bool activeGrid, const osg::Vec3f& viewPoint, bool compile, unsigned char lod); unsigned int getNodeMask() override; @@ -75,6 +75,11 @@ namespace MWRender std::mutex mSizeCacheMutex; typedef std::map SizeCache; SizeCache mSizeCache; + + std::mutex mLODNameCacheMutex; + typedef std::pair LODNameCacheKey; //Key: mesh name, lod level + typedef std::map LODNameCache; //Cache: key, mesh name to use + LODNameCache mLODNameCache; }; class RefnumMarker : public osg::Object diff --git a/components/misc/pathhelpers.hpp b/components/misc/pathhelpers.hpp index ee6ba8e0d5..5a6d3feb55 100644 --- a/components/misc/pathhelpers.hpp +++ b/components/misc/pathhelpers.hpp @@ -5,9 +5,14 @@ namespace Misc { + inline size_t findExtension(std::string_view file) + { + return file.find_last_of('.'); + } + inline std::string_view getFileExtension(std::string_view file) { - if (auto extPos = file.find_last_of('.'); extPos != std::string::npos) + if (auto extPos = findExtension(file); extPos != std::string::npos) { file.remove_prefix(extPos + 1); return file; diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 6c8a732c78..aa19855ae1 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -4,9 +4,11 @@ #include #include +#include #include #include +#include #include namespace @@ -164,3 +166,38 @@ bool Misc::ResourceHelpers::isHiddenMarker(std::string_view id) { return Misc::StringUtils::ciEqual(id, "prisonmarker") || Misc::StringUtils::ciEqual(id, "divinemarker") || Misc::StringUtils::ciEqual(id, "templemarker") || Misc::StringUtils::ciEqual(id, "northmarker"); } + +namespace +{ +std::string getLODMeshNameImpl(std::string resPath, const VFS::Manager* vfs, std::string_view pattern) +{ + if (auto w = Misc::findExtension(resPath); w != std::string::npos) + resPath.insert(w, pattern); + return vfs->normalizeFilename(resPath); +} + +std::string getBestLODMeshName(std::string const& resPath, const VFS::Manager* vfs, std::string_view pattern) +{ + if (const auto& result = getLODMeshNameImpl(resPath, vfs, pattern); vfs->exists(result)) + return result; + return resPath; +} +} + +std::string Misc::ResourceHelpers::getLODMeshName(std::string resPath, const VFS::Manager* vfs, unsigned char lod) +{ + static const std::string distantMeshPattern = Settings::Manager::getString("distant mesh pattern", "Terrain"); + std::string meshName = getBestLODMeshName(resPath, vfs, distantMeshPattern + "_" + std::to_string(lod)); + if (meshName != resPath) + return meshName; + + for (char l = lod; l >= 0; --l) + { + std::stringstream patern; + patern << distantMeshPattern << "_" << int(l); + meshName = getBestLODMeshName(resPath, vfs, patern.str()); + if (meshName != resPath) + return meshName; + } + return getBestLODMeshName(resPath, vfs, distantMeshPattern); +} diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index 18a69c9419..537c05c462 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -30,6 +30,7 @@ namespace Misc /// marker objects that have a hardcoded function in the game logic, should be hidden from the player bool isHiddenMarker(std::string_view id); + std::string getLODMeshName(std::string resPath, const VFS::Manager* vfs, unsigned char lod = 0); } } diff --git a/docs/source/reference/modding/settings/terrain.rst b/docs/source/reference/modding/settings/terrain.rst index 0aa7d9bd1b..dfcf225f1a 100644 --- a/docs/source/reference/modding/settings/terrain.rst +++ b/docs/source/reference/modding/settings/terrain.rst @@ -205,3 +205,12 @@ object paging min size cost multiplier This setting adjusts the calculated cost of merging an object used in the mentioned functionality. The larger this value is, the less expensive objects can be before they are discarded. See the formula above to figure out the math. + +distant mesh pattern +------------------ + +:Type: string +:Range: +:Default: _dist + +The mesh filename pattern to look for when detecting distant meshes (it will replace original mesh outside active grid) diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 8a6fc40fd7..f3cc953967 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -119,6 +119,9 @@ 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 +# The mesh filename pattern to look for when detecting distant meshes (it will replace original mesh outside active grid) +distant mesh pattern = _dist + [Fog] # If true, use extended fog parameters for distant terrain not controlled by From 160cbfa04e63edb6002d864d732bd255f1f771a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Sat, 3 Sep 2022 17:29:00 +0200 Subject: [PATCH 2/3] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 063eb1b877..7446026797 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Bug #6949: Sun Damage effect doesn't work in quasi exteriors Bug #6964: Nerasa Dralor Won't Follow Feature #6945: Support S3TC-compressed and BGR/BGRA NiPixelData + Feature #6979: Add support of loading and displaying LOD assets purely based on their filename extension 0.48.0 ------ From 83ee25711ebe396753066e4a72f8c3fcfdefa5ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Sat, 3 Sep 2022 18:50:38 +0200 Subject: [PATCH 3/3] Use the pattern according to the esm version --- apps/openmw/mwbase/world.hpp | 2 ++ apps/openmw/mwrender/objectpaging.cpp | 5 ++-- apps/openmw/mwworld/esmloader.cpp | 4 ++- apps/openmw/mwworld/esmloader.hpp | 3 ++- apps/openmw/mwworld/worldimp.cpp | 8 +++++- apps/openmw/mwworld/worldimp.hpp | 4 +++ components/misc/resourcehelpers.cpp | 27 ++++++++++++++----- components/misc/resourcehelpers.hpp | 2 +- .../reference/modding/settings/terrain.rst | 9 ------- files/settings-default.cfg | 3 --- 10 files changed, 42 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 54f5c16e65..ba52fb41ab 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -160,6 +160,8 @@ namespace MWBase virtual const MWWorld::ESMStore& getStore() const = 0; + virtual const std::vector& getESMVersions() const = 0; + virtual MWWorld::LocalScripts& getLocalScripts() = 0; virtual bool hasCellChanged() const = 0; diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 563d2ce23c..adbf1a202b 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -421,7 +421,8 @@ namespace MWRender std::map refs; ESM::ReadersCache readers; - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + const auto& world = MWBase::Environment::get().getWorld(); + const auto& store = world->getStore(); for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX) { @@ -557,7 +558,7 @@ namespace MWRender if (found != mLODNameCache.end() && found->first == key) model = found->second; else - model = mLODNameCache.insert(found, { key, Misc::ResourceHelpers::getLODMeshName(model, mSceneManager->getVFS(), lod) })->second; + model = mLODNameCache.insert(found, { key, Misc::ResourceHelpers::getLODMeshName(world->getESMVersions()[ref.mRefNum.mContentFile], model, mSceneManager->getVFS(), lod) })->second; } osg::ref_ptr cnode = mSceneManager->getTemplate(model, false); diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index 795b070dfa..1a215e2608 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -7,11 +7,12 @@ namespace MWWorld { -EsmLoader::EsmLoader(MWWorld::ESMStore& store, ESM::ReadersCache& readers, ToUTF8::Utf8Encoder* encoder) +EsmLoader::EsmLoader(MWWorld::ESMStore& store, ESM::ReadersCache& readers, ToUTF8::Utf8Encoder* encoder, std::vector& esmVersions) : mReaders(readers) , mStore(store) , mEncoder(encoder) , mDialogue(nullptr) // A content file containing INFO records without a DIAL record appends them to the previous file's dialogue + , mESMVersions(esmVersions) { } @@ -32,6 +33,7 @@ void EsmLoader::load(const boost::filesystem::path& filepath, int& index, Loadin + ", but it is not available or has been loaded in the wrong order. " "Please run the launcher to fix this issue."); + mESMVersions[index] = reader->getVer(); mStore.load(*reader, listener, mDialogue); if (!mMasterFileFormat.has_value() && (Misc::StringUtils::ciEndsWith(reader->getName(), ".esm") diff --git a/apps/openmw/mwworld/esmloader.hpp b/apps/openmw/mwworld/esmloader.hpp index c1cc406d92..a74a200d30 100644 --- a/apps/openmw/mwworld/esmloader.hpp +++ b/apps/openmw/mwworld/esmloader.hpp @@ -23,7 +23,7 @@ class ESMStore; struct EsmLoader : public ContentLoader { - explicit EsmLoader(MWWorld::ESMStore& store, ESM::ReadersCache& readers, ToUTF8::Utf8Encoder* encoder); + explicit EsmLoader(MWWorld::ESMStore& store, ESM::ReadersCache& readers, ToUTF8::Utf8Encoder* encoder, std::vector& esmVersions); std::optional getMasterFileFormat() const { return mMasterFileFormat; } @@ -35,6 +35,7 @@ struct EsmLoader : public ContentLoader ToUTF8::Utf8Encoder* mEncoder; ESM::Dialogue* mDialogue; std::optional mMasterFileFormat; + std::vector& mESMVersions; }; } /* namespace MWWorld */ diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 3af60c3420..6984fc3023 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -162,6 +162,7 @@ namespace MWWorld mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0), mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f) { + mESMVersions.resize(mContentFiles.size(), -1); Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); listener->loadingOn(); @@ -620,6 +621,11 @@ namespace MWWorld return *mPlayer; } + const std::vector& World::getESMVersions() const + { + return mESMVersions; + } + const MWWorld::ESMStore& World::getStore() const { return mStore; @@ -2933,7 +2939,7 @@ namespace MWWorld ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener) { GameContentLoader gameContentLoader; - EsmLoader esmLoader(mStore, mReaders, encoder); + EsmLoader esmLoader(mStore, mReaders, encoder, mESMVersions); gameContentLoader.addLoader(".esm", esmLoader); gameContentLoader.addLoader(".esp", esmLoader); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index d3d2ab82e1..f530d75132 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -138,6 +138,8 @@ namespace MWWorld float mSimulationTimeScale = 1.0; + std::vector mESMVersions; //the versions of esm files + // not implemented World (const World&); World& operator= (const World&); @@ -249,6 +251,8 @@ namespace MWWorld const MWWorld::ESMStore& getStore() const override; + const std::vector& getESMVersions() const override; + LocalScripts& getLocalScripts() override; bool hasCellChanged() const override; diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index aa19855ae1..3719e8e8c7 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include #include #include @@ -184,18 +186,29 @@ std::string getBestLODMeshName(std::string const& resPath, const VFS::Manager* v } } -std::string Misc::ResourceHelpers::getLODMeshName(std::string resPath, const VFS::Manager* vfs, unsigned char lod) +std::string Misc::ResourceHelpers::getLODMeshName(int esmVersion, std::string resPath, const VFS::Manager* vfs, unsigned char lod) { - static const std::string distantMeshPattern = Settings::Manager::getString("distant mesh pattern", "Terrain"); - std::string meshName = getBestLODMeshName(resPath, vfs, distantMeshPattern + "_" + std::to_string(lod)); - if (meshName != resPath) - return meshName; - + const std::string distantMeshPattern = [&esmVersion] { + switch (esmVersion) + { + case ESM::VER_120: + case ESM::VER_130: + return "_dist"; + case ESM::VER_080: + case ESM::VER_100: + return "_far"; + case ESM::VER_094: + case ESM::VER_170: + return "_lod"; + default: + return ""; + } + }(); for (char l = lod; l >= 0; --l) { std::stringstream patern; patern << distantMeshPattern << "_" << int(l); - meshName = getBestLODMeshName(resPath, vfs, patern.str()); + std::string const meshName = getBestLODMeshName(resPath, vfs, patern.str()); if (meshName != resPath) return meshName; } diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index 537c05c462..96a3a4a81b 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -30,7 +30,7 @@ namespace Misc /// marker objects that have a hardcoded function in the game logic, should be hidden from the player bool isHiddenMarker(std::string_view id); - std::string getLODMeshName(std::string resPath, const VFS::Manager* vfs, unsigned char lod = 0); + std::string getLODMeshName(int esmVersion, std::string resPath, const VFS::Manager* vfs, unsigned char lod = 0); } } diff --git a/docs/source/reference/modding/settings/terrain.rst b/docs/source/reference/modding/settings/terrain.rst index dfcf225f1a..0aa7d9bd1b 100644 --- a/docs/source/reference/modding/settings/terrain.rst +++ b/docs/source/reference/modding/settings/terrain.rst @@ -205,12 +205,3 @@ object paging min size cost multiplier This setting adjusts the calculated cost of merging an object used in the mentioned functionality. The larger this value is, the less expensive objects can be before they are discarded. See the formula above to figure out the math. - -distant mesh pattern ------------------- - -:Type: string -:Range: -:Default: _dist - -The mesh filename pattern to look for when detecting distant meshes (it will replace original mesh outside active grid) diff --git a/files/settings-default.cfg b/files/settings-default.cfg index f3cc953967..8a6fc40fd7 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -119,9 +119,6 @@ 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 -# The mesh filename pattern to look for when detecting distant meshes (it will replace original mesh outside active grid) -distant mesh pattern = _dist - [Fog] # If true, use extended fog parameters for distant terrain not controlled by