mirror of
https://github.com/OpenMW/openmw.git
synced 2025-06-20 21:41:35 +00:00
Merge branch 'DistantLOD' into 'master'
Support for TES distant LOD See merge request OpenMW/openmw!1861
This commit is contained in:
commit
647b22e175
11 changed files with 98 additions and 8 deletions
|
@ -14,6 +14,7 @@
|
||||||
Bug #6964: Nerasa Dralor Won't Follow
|
Bug #6964: Nerasa Dralor Won't Follow
|
||||||
Bug #6974: Only harmful effects are reflected
|
Bug #6974: Only harmful effects are reflected
|
||||||
Feature #6945: Support S3TC-compressed and BGR/BGRA NiPixelData
|
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
|
0.48.0
|
||||||
------
|
------
|
||||||
|
|
|
@ -160,6 +160,8 @@ namespace MWBase
|
||||||
|
|
||||||
virtual const MWWorld::ESMStore& getStore() const = 0;
|
virtual const MWWorld::ESMStore& getStore() const = 0;
|
||||||
|
|
||||||
|
virtual const std::vector<int>& getESMVersions() const = 0;
|
||||||
|
|
||||||
virtual MWWorld::LocalScripts& getLocalScripts() = 0;
|
virtual MWWorld::LocalScripts& getLocalScripts() = 0;
|
||||||
|
|
||||||
virtual bool hasCellChanged() const = 0;
|
virtual bool hasCellChanged() const = 0;
|
||||||
|
|
|
@ -76,6 +76,7 @@ namespace MWRender
|
||||||
|
|
||||||
osg::ref_ptr<osg::Node> ObjectPaging::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile)
|
osg::ref_ptr<osg::Node> 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<unsigned char>(lodFlags >> (4 * 4));
|
||||||
if (activeGrid && !mActiveGrid)
|
if (activeGrid && !mActiveGrid)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
|
@ -86,7 +87,7 @@ namespace MWRender
|
||||||
return static_cast<osg::Node*>(obj.get());
|
return static_cast<osg::Node*>(obj.get());
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
osg::ref_ptr<osg::Node> node = createChunk(size, center, activeGrid, viewPoint, compile);
|
osg::ref_ptr<osg::Node> node = createChunk(size, center, activeGrid, viewPoint, compile, lod);
|
||||||
mCache->addEntryToObjectCache(id, node.get());
|
mCache->addEntryToObjectCache(id, node.get());
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
@ -411,7 +412,7 @@ namespace MWRender
|
||||||
mMinSizeCostMultiplier = Settings::Manager::getFloat("object paging min size cost multiplier", "Terrain");
|
mMinSizeCostMultiplier = Settings::Manager::getFloat("object paging min size cost multiplier", "Terrain");
|
||||||
}
|
}
|
||||||
|
|
||||||
osg::ref_ptr<osg::Node> ObjectPaging::createChunk(float size, const osg::Vec2f& center, bool activeGrid, const osg::Vec3f& viewPoint, bool compile)
|
osg::ref_ptr<osg::Node> 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));
|
osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size/2.f), std::floor(center.y() - size/2.f));
|
||||||
|
|
||||||
|
@ -420,7 +421,8 @@ namespace MWRender
|
||||||
|
|
||||||
std::map<ESM::RefNum, ESM::CellRef> refs;
|
std::map<ESM::RefNum, ESM::CellRef> refs;
|
||||||
ESM::ReadersCache readers;
|
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)
|
for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX)
|
||||||
{
|
{
|
||||||
|
@ -548,6 +550,17 @@ namespace MWRender
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!activeGrid)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> 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(world->getESMVersions()[ref.mRefNum.mContentFile], model, mSceneManager->getVFS(), lod) })->second;
|
||||||
|
}
|
||||||
|
|
||||||
osg::ref_ptr<const osg::Node> cnode = mSceneManager->getTemplate(model, false);
|
osg::ref_ptr<const osg::Node> cnode = mSceneManager->getTemplate(model, false);
|
||||||
|
|
||||||
if (activeGrid)
|
if (activeGrid)
|
||||||
|
|
|
@ -29,7 +29,7 @@ namespace MWRender
|
||||||
|
|
||||||
osg::ref_ptr<osg::Node> 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<osg::Node> 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<osg::Node> createChunk(float size, const osg::Vec2f& center, bool activeGrid, const osg::Vec3f& viewPoint, bool compile);
|
osg::ref_ptr<osg::Node> createChunk(float size, const osg::Vec2f& center, bool activeGrid, const osg::Vec3f& viewPoint, bool compile, unsigned char lod);
|
||||||
|
|
||||||
unsigned int getNodeMask() override;
|
unsigned int getNodeMask() override;
|
||||||
|
|
||||||
|
@ -75,6 +75,11 @@ namespace MWRender
|
||||||
std::mutex mSizeCacheMutex;
|
std::mutex mSizeCacheMutex;
|
||||||
typedef std::map<ESM::RefNum, float> SizeCache;
|
typedef std::map<ESM::RefNum, float> SizeCache;
|
||||||
SizeCache mSizeCache;
|
SizeCache mSizeCache;
|
||||||
|
|
||||||
|
std::mutex mLODNameCacheMutex;
|
||||||
|
typedef std::pair<std::string, unsigned char> LODNameCacheKey; //Key: mesh name, lod level
|
||||||
|
typedef std::map<LODNameCacheKey, std::string> LODNameCache; //Cache: key, mesh name to use
|
||||||
|
LODNameCache mLODNameCache;
|
||||||
};
|
};
|
||||||
|
|
||||||
class RefnumMarker : public osg::Object
|
class RefnumMarker : public osg::Object
|
||||||
|
|
|
@ -7,11 +7,12 @@
|
||||||
namespace MWWorld
|
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<int>& esmVersions)
|
||||||
: mReaders(readers)
|
: mReaders(readers)
|
||||||
, mStore(store)
|
, mStore(store)
|
||||||
, mEncoder(encoder)
|
, mEncoder(encoder)
|
||||||
, mDialogue(nullptr) // A content file containing INFO records without a DIAL record appends them to the previous file's dialogue
|
, 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. "
|
+ ", but it is not available or has been loaded in the wrong order. "
|
||||||
"Please run the launcher to fix this issue.");
|
"Please run the launcher to fix this issue.");
|
||||||
|
|
||||||
|
mESMVersions[index] = reader->getVer();
|
||||||
mStore.load(*reader, listener, mDialogue);
|
mStore.load(*reader, listener, mDialogue);
|
||||||
|
|
||||||
if (!mMasterFileFormat.has_value() && (Misc::StringUtils::ciEndsWith(reader->getName(), ".esm")
|
if (!mMasterFileFormat.has_value() && (Misc::StringUtils::ciEndsWith(reader->getName(), ".esm")
|
||||||
|
|
|
@ -23,7 +23,7 @@ class ESMStore;
|
||||||
|
|
||||||
struct EsmLoader : public ContentLoader
|
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<int>& esmVersions);
|
||||||
|
|
||||||
std::optional<int> getMasterFileFormat() const { return mMasterFileFormat; }
|
std::optional<int> getMasterFileFormat() const { return mMasterFileFormat; }
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ struct EsmLoader : public ContentLoader
|
||||||
ToUTF8::Utf8Encoder* mEncoder;
|
ToUTF8::Utf8Encoder* mEncoder;
|
||||||
ESM::Dialogue* mDialogue;
|
ESM::Dialogue* mDialogue;
|
||||||
std::optional<int> mMasterFileFormat;
|
std::optional<int> mMasterFileFormat;
|
||||||
|
std::vector<int>& mESMVersions;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace MWWorld */
|
} /* namespace MWWorld */
|
||||||
|
|
|
@ -162,6 +162,7 @@ namespace MWWorld
|
||||||
mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0),
|
mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0),
|
||||||
mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f)
|
mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f)
|
||||||
{
|
{
|
||||||
|
mESMVersions.resize(mContentFiles.size(), -1);
|
||||||
Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
|
Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
|
||||||
listener->loadingOn();
|
listener->loadingOn();
|
||||||
|
|
||||||
|
@ -620,6 +621,11 @@ namespace MWWorld
|
||||||
return *mPlayer;
|
return *mPlayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::vector<int>& World::getESMVersions() const
|
||||||
|
{
|
||||||
|
return mESMVersions;
|
||||||
|
}
|
||||||
|
|
||||||
const MWWorld::ESMStore& World::getStore() const
|
const MWWorld::ESMStore& World::getStore() const
|
||||||
{
|
{
|
||||||
return mStore;
|
return mStore;
|
||||||
|
@ -2933,7 +2939,7 @@ namespace MWWorld
|
||||||
ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener)
|
ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener)
|
||||||
{
|
{
|
||||||
GameContentLoader gameContentLoader;
|
GameContentLoader gameContentLoader;
|
||||||
EsmLoader esmLoader(mStore, mReaders, encoder);
|
EsmLoader esmLoader(mStore, mReaders, encoder, mESMVersions);
|
||||||
|
|
||||||
gameContentLoader.addLoader(".esm", esmLoader);
|
gameContentLoader.addLoader(".esm", esmLoader);
|
||||||
gameContentLoader.addLoader(".esp", esmLoader);
|
gameContentLoader.addLoader(".esp", esmLoader);
|
||||||
|
|
|
@ -138,6 +138,8 @@ namespace MWWorld
|
||||||
|
|
||||||
float mSimulationTimeScale = 1.0;
|
float mSimulationTimeScale = 1.0;
|
||||||
|
|
||||||
|
std::vector<int> mESMVersions; //the versions of esm files
|
||||||
|
|
||||||
// not implemented
|
// not implemented
|
||||||
World (const World&);
|
World (const World&);
|
||||||
World& operator= (const World&);
|
World& operator= (const World&);
|
||||||
|
@ -249,6 +251,8 @@ namespace MWWorld
|
||||||
|
|
||||||
const MWWorld::ESMStore& getStore() const override;
|
const MWWorld::ESMStore& getStore() const override;
|
||||||
|
|
||||||
|
const std::vector<int>& getESMVersions() const override;
|
||||||
|
|
||||||
LocalScripts& getLocalScripts() override;
|
LocalScripts& getLocalScripts() override;
|
||||||
|
|
||||||
bool hasCellChanged() const override;
|
bool hasCellChanged() const override;
|
||||||
|
|
|
@ -5,9 +5,14 @@
|
||||||
|
|
||||||
namespace Misc
|
namespace Misc
|
||||||
{
|
{
|
||||||
|
inline size_t findExtension(std::string_view file)
|
||||||
|
{
|
||||||
|
return file.find_last_of('.');
|
||||||
|
}
|
||||||
|
|
||||||
inline std::string_view getFileExtension(std::string_view file)
|
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);
|
file.remove_prefix(extPos + 1);
|
||||||
return file;
|
return file;
|
||||||
|
|
|
@ -4,9 +4,13 @@
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <components/esm/common.hpp>
|
||||||
|
|
||||||
|
#include <components/misc/pathhelpers.hpp>
|
||||||
#include <components/misc/strings/lower.hpp>
|
#include <components/misc/strings/lower.hpp>
|
||||||
#include <components/misc/strings/algorithm.hpp>
|
#include <components/misc/strings/algorithm.hpp>
|
||||||
|
|
||||||
|
#include <components/settings/settings.hpp>
|
||||||
#include <components/vfs/manager.hpp>
|
#include <components/vfs/manager.hpp>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
|
@ -164,3 +168,49 @@ 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");
|
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(int esmVersion, std::string resPath, const VFS::Manager* vfs, unsigned char lod)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
std::string const meshName = getBestLODMeshName(resPath, vfs, patern.str());
|
||||||
|
if (meshName != resPath)
|
||||||
|
return meshName;
|
||||||
|
}
|
||||||
|
return getBestLODMeshName(resPath, vfs, distantMeshPattern);
|
||||||
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ namespace Misc
|
||||||
|
|
||||||
/// marker objects that have a hardcoded function in the game logic, should be hidden from the player
|
/// marker objects that have a hardcoded function in the game logic, should be hidden from the player
|
||||||
bool isHiddenMarker(std::string_view id);
|
bool isHiddenMarker(std::string_view id);
|
||||||
|
std::string getLODMeshName(int esmVersion, std::string resPath, const VFS::Manager* vfs, unsigned char lod = 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue