Merge branch 'DistantLOD' into 'master'

Support for TES distant LOD

See merge request OpenMW/openmw!1861
crashfix_debugdraw
psi29a 2 years ago
commit 647b22e175

@ -14,6 +14,7 @@
Bug #6964: Nerasa Dralor Won't Follow
Bug #6974: Only harmful effects are reflected
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
------

@ -160,6 +160,8 @@ namespace MWBase
virtual const MWWorld::ESMStore& getStore() const = 0;
virtual const std::vector<int>& getESMVersions() const = 0;
virtual MWWorld::LocalScripts& getLocalScripts() = 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)
{
lod = static_cast<unsigned char>(lodFlags >> (4 * 4));
if (activeGrid && !mActiveGrid)
return nullptr;
@ -86,7 +87,7 @@ namespace MWRender
return static_cast<osg::Node*>(obj.get());
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());
return node;
}
@ -411,7 +412,7 @@ namespace MWRender
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));
@ -420,7 +421,8 @@ namespace MWRender
std::map<ESM::RefNum, ESM::CellRef> 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)
{
@ -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);
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> 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;
@ -75,6 +75,11 @@ namespace MWRender
std::mutex mSizeCacheMutex;
typedef std::map<ESM::RefNum, float> SizeCache;
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

@ -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<int>& 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")

@ -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<int>& esmVersions);
std::optional<int> getMasterFileFormat() const { return mMasterFileFormat; }
@ -35,6 +35,7 @@ struct EsmLoader : public ContentLoader
ToUTF8::Utf8Encoder* mEncoder;
ESM::Dialogue* mDialogue;
std::optional<int> mMasterFileFormat;
std::vector<int>& mESMVersions;
};
} /* namespace MWWorld */

@ -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<int>& 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);

@ -138,6 +138,8 @@ namespace MWWorld
float mSimulationTimeScale = 1.0;
std::vector<int> 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<int>& getESMVersions() const override;
LocalScripts& getLocalScripts() override;
bool hasCellChanged() const override;

@ -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;

@ -4,9 +4,13 @@
#include <string_view>
#include <algorithm>
#include <components/esm/common.hpp>
#include <components/misc/pathhelpers.hpp>
#include <components/misc/strings/lower.hpp>
#include <components/misc/strings/algorithm.hpp>
#include <components/settings/settings.hpp>
#include <components/vfs/manager.hpp>
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");
}
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
bool isHiddenMarker(std::string_view id);
std::string getLODMeshName(int esmVersion, std::string resPath, const VFS::Manager* vfs, unsigned char lod = 0);
}
}

Loading…
Cancel
Save