From 3bf641d3ce975fd0d39c22036822e800508146d0 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 29 Dec 2020 21:45:59 +0100 Subject: [PATCH 001/117] Show mesh origin --- CHANGELOG.md | 1 + apps/openmw/engine.cpp | 1 + apps/openmw/mwbase/environment.cpp | 12 ++++++++++++ apps/openmw/mwbase/environment.hpp | 10 ++++++++++ apps/openmw/mwscript/miscextensions.cpp | 15 ++++++++++++++- components/bsa/bsa_file.hpp | 5 +++++ components/vfs/archive.hpp | 5 +++++ components/vfs/bsaarchive.cpp | 17 +++++++++++++++++ components/vfs/bsaarchive.hpp | 2 ++ components/vfs/filesystemarchive.cpp | 15 +++++++++++++++ components/vfs/filesystemarchive.hpp | 3 +++ components/vfs/manager.cpp | 11 +++++++++++ components/vfs/manager.hpp | 1 + 13 files changed, 97 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce0cce952..141edcad2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -116,6 +116,7 @@ Feature #5672: Make stretch menu background configuration more accessible Feature #5692: Improve spell/magic item search to factor in magic effect names Feature #5730: Add graphic herbalism option to the launcher and documents + Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used. Task #5480: Drop Qt4 support Task #5520: Improve cell name autocompleter implementation diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 8f23f710d..81a97b0f2 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -749,6 +749,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) // Create dialog system mEnvironment.setJournal (new MWDialogue::Journal); mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mTranslationDataStorage)); + mEnvironment.setResourceSystem(mResourceSystem.get()); // scripts if (mCompileAll) diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index aca2685e0..edb10d945 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include "world.hpp" #include "scriptmanager.hpp" #include "dialoguemanager.hpp" @@ -76,6 +78,11 @@ void MWBase::Environment::setStateManager (StateManager *stateManager) mStateManager = stateManager; } +void MWBase::Environment::setResourceSystem (Resource::ResourceSystem *resourceSystem) +{ + mResourceSystem = resourceSystem; +} + void MWBase::Environment::setFrameDuration (float duration) { mFrameDuration = duration; @@ -158,6 +165,11 @@ MWBase::StateManager *MWBase::Environment::getStateManager() const return mStateManager; } +Resource::ResourceSystem *MWBase::Environment::getResourceSystem() const +{ + return mResourceSystem; +} + float MWBase::Environment::getFrameDuration() const { return mFrameDuration; diff --git a/apps/openmw/mwbase/environment.hpp b/apps/openmw/mwbase/environment.hpp index 80e6a6243..7871153cc 100644 --- a/apps/openmw/mwbase/environment.hpp +++ b/apps/openmw/mwbase/environment.hpp @@ -6,6 +6,11 @@ namespace osg class Stats; } +namespace Resource +{ + class ResourceSystem; +} + namespace MWBase { class World; @@ -37,6 +42,7 @@ namespace MWBase Journal *mJournal; InputManager *mInputManager; StateManager *mStateManager; + Resource::ResourceSystem *mResourceSystem; float mFrameDuration; float mFrameRateLimit; @@ -70,6 +76,8 @@ namespace MWBase void setStateManager (StateManager *stateManager); + void setResourceSystem (Resource::ResourceSystem *resourceSystem); + void setFrameDuration (float duration); ///< Set length of current frame in seconds. @@ -95,6 +103,8 @@ namespace MWBase StateManager *getStateManager() const; + Resource::ResourceSystem *getResourceSystem() const; + float getFrameDuration() const; void cleanup(); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index a288d6673..d78337a62 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -13,10 +13,15 @@ #include #include +#include + +#include #include #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/scriptmanager.hpp" @@ -1377,7 +1382,15 @@ namespace MWScript msg << "Grid: " << cell->getCell()->getGridX() << " " << cell->getCell()->getGridY() << std::endl; osg::Vec3f pos (ptr.getRefData().getPosition().asVec3()); msg << "Coordinates: " << pos.x() << " " << pos.y() << " " << pos.z() << std::endl; - msg << "Model: " << ptr.getClass().getModel(ptr) << std::endl; + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + std::string model = ::Misc::ResourceHelpers::correctActorModelPath(ptr.getClass().getModel(ptr), vfs); + msg << "Model: " << model << std::endl; + if(!model.empty()) + { + const std::string archive = vfs->getArchive(model); + if(!archive.empty()) + msg << "(" << archive << ")" << std::endl; + } if (!ptr.getClass().getScript(ptr).empty()) msg << "Script: " << ptr.getClass().getScript(ptr) << std::endl; } diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index 037802739..3e7538401 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -135,6 +135,11 @@ public: /// @note Thread safe. const FileList &getList() const { return mFiles; } + + const std::string& getFilename() const + { + return mFilename; + } }; } diff --git a/components/vfs/archive.hpp b/components/vfs/archive.hpp index b36c7117b..971ac15b3 100644 --- a/components/vfs/archive.hpp +++ b/components/vfs/archive.hpp @@ -23,6 +23,11 @@ namespace VFS /// List all resources contained in this archive, and run the resource names through the given normalize function. virtual void listResources(std::map& out, char (*normalize_function) (char)) = 0; + + /// True if this archive contains the provided normalized file. + virtual bool contains(const std::string& file, char (*normalize_function) (char)) const = 0; + + virtual std::string getDescription() const = 0; }; } diff --git a/components/vfs/bsaarchive.cpp b/components/vfs/bsaarchive.cpp index ac65c58a1..e6d779aab 100644 --- a/components/vfs/bsaarchive.cpp +++ b/components/vfs/bsaarchive.cpp @@ -39,6 +39,23 @@ void BsaArchive::listResources(std::map &out, char (*normal } } +bool BsaArchive::contains(const std::string& file, char (*normalize_function)(char)) const +{ + for (const auto& it : mResources) + { + std::string ent = it.mInfo->name; + std::transform(ent.begin(), ent.end(), ent.begin(), normalize_function); + if(file == ent) + return true; + } + return false; +} + +std::string BsaArchive::getDescription() const +{ + return std::string{"BSA: "} + mFile->getFilename(); +} + // ------------------------------------------------------------------------------ BsaArchiveFile::BsaArchiveFile(const Bsa::BSAFile::FileStruct *info, Bsa::BSAFile* bsa) diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 65a9db16c..c979b5ce7 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -24,6 +24,8 @@ namespace VFS BsaArchive(const std::string& filename); virtual ~BsaArchive(); void listResources(std::map& out, char (*normalize_function) (char)) override; + bool contains(const std::string& file, char (*normalize_function) (char)) const override; + std::string getDescription() const override; private: std::unique_ptr mFile; diff --git a/components/vfs/filesystemarchive.cpp b/components/vfs/filesystemarchive.cpp index ce4ff020e..17f3891ec 100644 --- a/components/vfs/filesystemarchive.cpp +++ b/components/vfs/filesystemarchive.cpp @@ -53,6 +53,21 @@ namespace VFS } } + bool FileSystemArchive::contains(const std::string& file, char (*normalize_function)(char)) const + { + for (const auto& it : mIndex) + { + if(it.first == file) + return true; + } + return false; + } + + std::string FileSystemArchive::getDescription() const + { + return std::string{"DIR: "} + mPath; + } + // ---------------------------------------------------------------------------------- FileSystemArchiveFile::FileSystemArchiveFile(const std::string &path) diff --git a/components/vfs/filesystemarchive.hpp b/components/vfs/filesystemarchive.hpp index d228ba87c..70463d32f 100644 --- a/components/vfs/filesystemarchive.hpp +++ b/components/vfs/filesystemarchive.hpp @@ -25,6 +25,9 @@ namespace VFS void listResources(std::map& out, char (*normalize_function) (char)) override; + bool contains(const std::string& file, char (*normalize_function) (char)) const override; + + std::string getDescription() const override; private: typedef std::map index; diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index c19882381..c7abc2483 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -96,4 +96,15 @@ namespace VFS normalize_path(name, mStrict); } + std::string Manager::getArchive(const std::string& name) const + { + std::string normalized = name; + normalize_path(normalized, mStrict); + for(const auto archive : mArchives) + { + if(archive->contains(normalized, mStrict ? &strict_normalize_char : &nonstrict_normalize_char)) + return archive->getDescription(); + } + return {}; + } } diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index c5f0a8fec..5a09a995e 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -58,6 +58,7 @@ namespace VFS /// @note May be called from any thread once the index has been built. Files::IStreamPtr getNormalized(const std::string& normalizedName) const; + std::string getArchive(const std::string& name) const; private: bool mStrict; From 630ec36d1fcaa68805731c4ac6bc4123b173a8f0 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 30 Dec 2020 10:35:51 +0100 Subject: [PATCH 002/117] iterate in reverse order --- components/vfs/manager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index c7abc2483..045fe3cf5 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -100,10 +100,10 @@ namespace VFS { std::string normalized = name; normalize_path(normalized, mStrict); - for(const auto archive : mArchives) + for(auto it = mArchives.rbegin(); it != mArchives.rend(); ++it) { - if(archive->contains(normalized, mStrict ? &strict_normalize_char : &nonstrict_normalize_char)) - return archive->getDescription(); + if((*it)->contains(normalized, mStrict ? &strict_normalize_char : &nonstrict_normalize_char)) + return (*it)->getDescription(); } return {}; } From 57c92673bc804653fd9502fa2b90c597f3620562 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 30 Dec 2020 16:09:12 +0100 Subject: [PATCH 003/117] Consider a path completed if it was non-empty --- apps/openmw/mwmechanics/aipackage.cpp | 2 +- apps/openmw/mwmechanics/pathfinding.cpp | 4 ++++ apps/openmw/mwmechanics/pathfinding.hpp | 9 +++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 8880820dd..8af2c71ed 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -158,7 +158,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& zTurn(actor, getZAngleToPoint(position, dest)); smoothTurn(actor, getXAngleToPoint(position, dest), 0); world->removeActorPath(actor); - return isDestReached; + return isDestReached || mPathFinder.pathWasPossible(); } world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest); diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index a82dcf717..77ab78fd7 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -318,6 +318,7 @@ namespace MWMechanics mPath.clear(); mPath.push_back(endPoint); mConstructed = true; + mPossible = true; } void PathFinder::buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, @@ -329,6 +330,7 @@ namespace MWMechanics buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath)); mConstructed = true; + mPossible = !mPath.empty(); } void PathFinder::buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, @@ -342,6 +344,7 @@ namespace MWMechanics mPath.push_back(endPoint); mConstructed = true; + mPossible = !mPath.empty(); } void PathFinder::buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, @@ -367,6 +370,7 @@ namespace MWMechanics mPath.push_back(endPoint); mConstructed = true; + mPossible = !mPath.empty(); } bool PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 5af822fee..ed786dc84 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -74,6 +74,7 @@ namespace MWMechanics public: PathFinder() : mConstructed(false) + , mPossible(false) , mCell(nullptr) { } @@ -81,6 +82,7 @@ namespace MWMechanics void clearPath() { mConstructed = false; + mPossible = false; mPath.clear(); mCell = nullptr; } @@ -109,6 +111,11 @@ namespace MWMechanics return mConstructed && mPath.empty(); } + bool pathWasPossible() const + { + return mPossible; + } + /// In radians float getZAngleToNext(float x, float y) const; @@ -137,6 +144,7 @@ namespace MWMechanics void addPointToPath(const osg::Vec3f& point) { mConstructed = true; + mPossible = true; mPath.push_back(point); } @@ -196,6 +204,7 @@ namespace MWMechanics private: bool mConstructed; + bool mPossible; std::deque mPath; const MWWorld::CellStore* mCell; From 2a583e2337f8003a8ba47ac08fd8ab65d03d149b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 8 Jan 2021 17:24:13 +0100 Subject: [PATCH 004/117] consider empty paths as not constructed --- apps/openmw/mwmechanics/aipackage.cpp | 4 +++- apps/openmw/mwmechanics/pathfinding.cpp | 10 +++------- apps/openmw/mwmechanics/pathfinding.hpp | 9 --------- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 8af2c71ed..ce5673909 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -158,8 +158,10 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& zTurn(actor, getZAngleToPoint(position, dest)); smoothTurn(actor, getXAngleToPoint(position, dest), 0); world->removeActorPath(actor); - return isDestReached || mPathFinder.pathWasPossible(); + return true; } + else if (mPathFinder.getPath().empty()) + return false; world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest); diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 77ab78fd7..276321b81 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -318,7 +318,6 @@ namespace MWMechanics mPath.clear(); mPath.push_back(endPoint); mConstructed = true; - mPossible = true; } void PathFinder::buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, @@ -329,8 +328,7 @@ namespace MWMechanics buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath)); - mConstructed = true; - mPossible = !mPath.empty(); + mConstructed = !mPath.empty(); } void PathFinder::buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, @@ -343,8 +341,7 @@ namespace MWMechanics if (!buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, areaCosts, std::back_inserter(mPath))) mPath.push_back(endPoint); - mConstructed = true; - mPossible = !mPath.empty(); + mConstructed = !mPath.empty(); } void PathFinder::buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, @@ -369,8 +366,7 @@ namespace MWMechanics if (!hasNavMesh && mPath.empty()) mPath.push_back(endPoint); - mConstructed = true; - mPossible = !mPath.empty(); + mConstructed = !mPath.empty(); } bool PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index ed786dc84..5af822fee 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -74,7 +74,6 @@ namespace MWMechanics public: PathFinder() : mConstructed(false) - , mPossible(false) , mCell(nullptr) { } @@ -82,7 +81,6 @@ namespace MWMechanics void clearPath() { mConstructed = false; - mPossible = false; mPath.clear(); mCell = nullptr; } @@ -111,11 +109,6 @@ namespace MWMechanics return mConstructed && mPath.empty(); } - bool pathWasPossible() const - { - return mPossible; - } - /// In radians float getZAngleToNext(float x, float y) const; @@ -144,7 +137,6 @@ namespace MWMechanics void addPointToPath(const osg::Vec3f& point) { mConstructed = true; - mPossible = true; mPath.push_back(point); } @@ -204,7 +196,6 @@ namespace MWMechanics private: bool mConstructed; - bool mPossible; std::deque mPath; const MWWorld::CellStore* mCell; From 7d551b0cfde472da8acb7e8a8f1a0b1899694ad9 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 8 Jan 2021 23:21:39 +0100 Subject: [PATCH 005/117] Split long osg log messages into lines. --- apps/openmw/main.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 89aa2b9fd..8eaac36e8 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -254,7 +254,19 @@ namespace level = Debug::Debug; } std::string_view s(msgCopy); - Log(level) << (s.back() == '\n' ? s.substr(0, s.size() - 1) : s); + if (s.size() < 1024) + Log(level) << (s.back() == '\n' ? s.substr(0, s.size() - 1) : s); + else + { + while (!s.empty()) + { + size_t lineSize = 1; + while (lineSize < s.size() && s[lineSize - 1] != '\n') + lineSize++; + Log(level) << s.substr(0, s[lineSize - 1] == '\n' ? lineSize - 1 : lineSize); + s = s.substr(lineSize); + } + } } }; } From 944033db4e4e3d7d0205480a8763f4d714652d59 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 18 Jul 2020 23:56:14 +0200 Subject: [PATCH 006/117] Separate sound buffer pool from sound manager --- apps/openmw/CMakeLists.txt | 1 + apps/openmw/mwsound/sound.hpp | 7 + apps/openmw/mwsound/sound_buffer.cpp | 154 +++++++++++++++++++++ apps/openmw/mwsound/sound_buffer.hpp | 91 +++++++++++-- apps/openmw/mwsound/sound_output.hpp | 9 +- apps/openmw/mwsound/soundmanagerimp.cpp | 169 +++--------------------- apps/openmw/mwsound/soundmanagerimp.hpp | 29 +--- 7 files changed, 276 insertions(+), 184 deletions(-) create mode 100644 apps/openmw/mwsound/sound_buffer.cpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index a4c3b9136..f32240a91 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -58,6 +58,7 @@ add_openmw_dir (mwscript add_openmw_dir (mwsound soundmanagerimp openal_output ffmpeg_decoder sound sound_buffer sound_decoder sound_output loudness movieaudiofactory alext efx efx-presets regionsoundselector watersoundupdater volumesettings + sound_buffer ) add_openmw_dir (mwworld diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 9d264e1b6..d2e65c989 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -7,6 +7,13 @@ namespace MWSound { + // Extra play flags, not intended for caller use + enum PlayModeEx + { + Play_2D = 0, + Play_3D = 1 << 31, + }; + // For testing individual PlayMode flags inline int operator&(int a, PlayMode b) { return a & static_cast(b); } inline int operator&(PlayMode a, PlayMode b) { return static_cast(a) & static_cast(b); } diff --git a/apps/openmw/mwsound/sound_buffer.cpp b/apps/openmw/mwsound/sound_buffer.cpp new file mode 100644 index 000000000..0e25ff601 --- /dev/null +++ b/apps/openmw/mwsound/sound_buffer.cpp @@ -0,0 +1,154 @@ +#include "sound_buffer.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwworld/esmstore.hpp" + +#include +#include +#include + +#include +#include + +namespace MWSound +{ + namespace + { + struct AudioParams + { + float mAudioDefaultMinDistance; + float mAudioDefaultMaxDistance; + float mAudioMinDistanceMult; + float mAudioMaxDistanceMult; + }; + + AudioParams makeAudioParams(const MWBase::World& world) + { + const auto& settings = world.getStore().get(); + AudioParams params; + params.mAudioDefaultMinDistance = settings.find("fAudioDefaultMinDistance")->mValue.getFloat(); + params.mAudioDefaultMaxDistance = settings.find("fAudioDefaultMaxDistance")->mValue.getFloat(); + params.mAudioMinDistanceMult = settings.find("fAudioMinDistanceMult")->mValue.getFloat(); + params.mAudioMaxDistanceMult = settings.find("fAudioMaxDistanceMult")->mValue.getFloat(); + return params; + } + } + + SoundBufferPool::SoundBufferPool(const VFS::Manager& vfs, Sound_Output& output) : + mVfs(&vfs), + mOutput(&output), + mBufferCacheMax(std::max(Settings::Manager::getInt("buffer cache max", "Sound"), 1) * 1024 * 1024), + mBufferCacheMin(std::min(static_cast(std::max(Settings::Manager::getInt("buffer cache min", "Sound"), 1)) * 1024 * 1024, mBufferCacheMax)) + { + } + + SoundBufferPool::~SoundBufferPool() + { + clear(); + } + + Sound_Buffer* SoundBufferPool::lookup(const std::string& soundId) const + { + const auto it = mBufferNameMap.find(soundId); + if (it != mBufferNameMap.end()) + { + Sound_Buffer* sfx = it->second; + if (sfx->getHandle() != nullptr) + return sfx; + } + return nullptr; + } + + Sound_Buffer* SoundBufferPool::load(const std::string& soundId) + { + if (mBufferNameMap.empty()) + { + for (const ESM::Sound& sound : MWBase::Environment::get().getWorld()->getStore().get()) + insertSound(Misc::StringUtils::lowerCase(sound.mId), sound); + } + + Sound_Buffer* sfx; + const auto it = mBufferNameMap.find(soundId); + if (it != mBufferNameMap.end()) + sfx = it->second; + else + { + const ESM::Sound *sound = MWBase::Environment::get().getWorld()->getStore().get().search(soundId); + if (sound == nullptr) + return {}; + sfx = insertSound(soundId, *sound); + } + + if (sfx->getHandle() == nullptr) + { + Sound_Handle handle; + size_t size; + std::tie(handle, size) = mOutput->loadSound(sfx->getResourceName()); + if (handle == nullptr) + return {}; + + sfx->mHandle = handle; + + mBufferCacheSize += size; + if (mBufferCacheSize > mBufferCacheMax) + { + unloadUnused(); + if (!mUnusedBuffers.empty() && mBufferCacheSize > mBufferCacheMax) + Log(Debug::Warning) << "No unused sound buffers to free, using " << mBufferCacheSize << " bytes!"; + } + mUnusedBuffers.push_front(sfx); + } + + return sfx; + } + + void SoundBufferPool::clear() + { + for (auto &sfx : mSoundBuffers) + { + if(sfx.mHandle) + mOutput->unloadSound(sfx.mHandle); + sfx.mHandle = nullptr; + } + mUnusedBuffers.clear(); + } + + Sound_Buffer* SoundBufferPool::insertSound(const std::string& soundId, const ESM::Sound& sound) + { + static const AudioParams audioParams = makeAudioParams(*MWBase::Environment::get().getWorld()); + + float volume = static_cast(std::pow(10.0, (sound.mData.mVolume / 255.0 * 3348.0 - 3348.0) / 2000.0)); + float min = sound.mData.mMinRange; + float max = sound.mData.mMaxRange; + if (min == 0 && max == 0) + { + min = audioParams.mAudioDefaultMinDistance; + max = audioParams.mAudioDefaultMaxDistance; + } + + min *= audioParams.mAudioMinDistanceMult; + max *= audioParams.mAudioMaxDistanceMult; + min = std::max(min, 1.0f); + max = std::max(min, max); + + Sound_Buffer& sfx = mSoundBuffers.emplace_back("Sound/" + sound.mSound, volume, min, max); + mVfs->normalizeFilename(sfx.mResourceName); + + mBufferNameMap.emplace(soundId, &sfx); + return &sfx; + } + + void SoundBufferPool::unloadUnused() + { + while (!mUnusedBuffers.empty() && mBufferCacheSize > mBufferCacheMin) + { + Sound_Buffer* const unused = mUnusedBuffers.back(); + + mBufferCacheSize -= mOutput->unloadSound(unused->getHandle()); + unused->mHandle = nullptr; + + mUnusedBuffers.pop_back(); + } + } +} diff --git a/apps/openmw/mwsound/sound_buffer.hpp b/apps/openmw/mwsound/sound_buffer.hpp index 83b08d6be..e62392300 100644 --- a/apps/openmw/mwsound/sound_buffer.hpp +++ b/apps/openmw/mwsound/sound_buffer.hpp @@ -1,27 +1,100 @@ #ifndef GAME_SOUND_SOUND_BUFFER_H #define GAME_SOUND_SOUND_BUFFER_H +#include #include +#include +#include #include "sound_output.hpp" +namespace ESM +{ + struct Sound; +} + +namespace VFS +{ + class Manager; +} + namespace MWSound { + class SoundBufferPool; + class Sound_Buffer { - public: - std::string mResourceName; + public: + Sound_Buffer(std::string resname, float volume, float mindist, float maxdist) + : mResourceName(std::move(resname)), mVolume(volume), mMinDist(mindist), mMaxDist(maxdist) + {} - float mVolume; - float mMinDist, mMaxDist; + const std::string& getResourceName() const noexcept { return mResourceName; } - Sound_Handle mHandle; + Sound_Handle getHandle() const noexcept { return mHandle; } - size_t mUses; + float getVolume() const noexcept { return mVolume; } - Sound_Buffer(std::string resname, float volume, float mindist, float maxdist) - : mResourceName(resname), mVolume(volume), mMinDist(mindist), mMaxDist(maxdist), mHandle(nullptr), mUses(0) - { } + float getMinDist() const noexcept { return mMinDist; } + + float getMaxDist() const noexcept { return mMaxDist; } + + private: + std::string mResourceName; + float mVolume = 0; + float mMinDist = 0; + float mMaxDist = 0; + Sound_Handle mHandle = nullptr; + std::size_t mUses = 0; + + friend class SoundBufferPool; + }; + + class SoundBufferPool + { + public: + SoundBufferPool(const VFS::Manager& vfs, Sound_Output& output); + + SoundBufferPool(const SoundBufferPool&) = delete; + + ~SoundBufferPool(); + + Sound_Buffer* lookup(const std::string& soundId) const; + + Sound_Buffer* load(const std::string& soundId); + + void use(Sound_Buffer& sfx) + { + if (sfx.mUses++ == 0) + { + const auto it = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), &sfx); + if (it != mUnusedBuffers.end()) + mUnusedBuffers.erase(it); + } + } + + void release(Sound_Buffer& sfx) + { + if (--sfx.mUses == 0) + mUnusedBuffers.push_front(&sfx); + } + + void clear(); + + private: + const VFS::Manager* const mVfs; + Sound_Output* mOutput; + std::deque mSoundBuffers; + std::unordered_map mBufferNameMap; + std::size_t mBufferCacheMax; + std::size_t mBufferCacheMin; + std::size_t mBufferCacheSize = 0; + // NOTE: unused buffers are stored in front-newest order. + std::deque mUnusedBuffers; + + inline Sound_Buffer* insertSound(const std::string& soundId, const ESM::Sound& sound); + + inline void unloadUnused(); }; } diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 4075e36cc..9ec8b17dc 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -5,7 +5,7 @@ #include #include -#include "soundmanagerimp.hpp" +#include "../mwbase/soundmanager.hpp" namespace MWSound { @@ -25,6 +25,12 @@ namespace MWSound Auto }; + enum Environment + { + Env_Normal, + Env_Underwater + }; + class Sound_Output { SoundManager &mManager; @@ -81,6 +87,7 @@ namespace MWSound friend class OpenAL_Output; friend class SoundManager; + friend class SoundBufferPool; }; } diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 37ba80820..03ad58088 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -56,8 +56,7 @@ namespace MWSound : mVFS(vfs) , mOutput(new DEFAULT_OUTPUT(*this)) , mWaterSoundUpdater(makeWaterSoundUpdaterSettings()) - , mSoundBuffers(new SoundBufferList::element_type()) - , mBufferCacheSize(0) + , mSoundBuffers(*vfs, *mOutput) , mListenerUnderwater(false) , mListenerPos(0,0,0) , mListenerDir(1,0,0) @@ -69,11 +68,6 @@ namespace MWSound , mLastCell(nullptr) , mCurrentRegionSound(nullptr) { - mBufferCacheMin = std::max(Settings::Manager::getInt("buffer cache min", "Sound"), 1); - mBufferCacheMax = std::max(Settings::Manager::getInt("buffer cache max", "Sound"), 1); - mBufferCacheMax *= 1024*1024; - mBufferCacheMin = std::min(mBufferCacheMin*1024*1024, mBufferCacheMax); - if(!useSound) { Log(Debug::Info) << "Sound disabled."; @@ -116,13 +110,7 @@ namespace MWSound SoundManager::~SoundManager() { clear(); - for(Sound_Buffer &sfx : *mSoundBuffers) - { - if(sfx.mHandle) - mOutput->unloadSound(sfx.mHandle); - sfx.mHandle = nullptr; - } - mUnusedBuffers.clear(); + mSoundBuffers.clear(); mOutput.reset(); } @@ -132,112 +120,18 @@ namespace MWSound return DecoderPtr(new DEFAULT_DECODER (mVFS)); } - Sound_Buffer *SoundManager::insertSound(const std::string &soundId, const ESM::Sound *sound) - { - MWBase::World* world = MWBase::Environment::get().getWorld(); - static const float fAudioDefaultMinDistance = world->getStore().get().find("fAudioDefaultMinDistance")->mValue.getFloat(); - static const float fAudioDefaultMaxDistance = world->getStore().get().find("fAudioDefaultMaxDistance")->mValue.getFloat(); - static const float fAudioMinDistanceMult = world->getStore().get().find("fAudioMinDistanceMult")->mValue.getFloat(); - static const float fAudioMaxDistanceMult = world->getStore().get().find("fAudioMaxDistanceMult")->mValue.getFloat(); - float volume, min, max; - - volume = static_cast(pow(10.0, (sound->mData.mVolume / 255.0*3348.0 - 3348.0) / 2000.0)); - min = sound->mData.mMinRange; - max = sound->mData.mMaxRange; - if (min == 0 && max == 0) - { - min = fAudioDefaultMinDistance; - max = fAudioDefaultMaxDistance; - } - - min *= fAudioMinDistanceMult; - max *= fAudioMaxDistanceMult; - min = std::max(min, 1.0f); - max = std::max(min, max); - - Sound_Buffer *sfx = &*mSoundBuffers->insert(mSoundBuffers->end(), - Sound_Buffer("Sound/"+sound->mSound, volume, min, max) - ); - mVFS->normalizeFilename(sfx->mResourceName); - - mBufferNameMap.insert(std::make_pair(soundId, sfx)); - - return sfx; - } - // Lookup a soundId for its sound data (resource name, local volume, // minRange, and maxRange) Sound_Buffer *SoundManager::lookupSound(const std::string &soundId) const { - NameBufferMap::const_iterator snd = mBufferNameMap.find(soundId); - if(snd != mBufferNameMap.end()) - { - Sound_Buffer *sfx = snd->second; - if(sfx->mHandle) return sfx; - } - return nullptr; + return mSoundBuffers.lookup(soundId); } // Lookup a soundId for its sound data (resource name, local volume, // minRange, and maxRange), and ensure it's ready for use. Sound_Buffer *SoundManager::loadSound(const std::string &soundId) { -#ifdef __GNUC__ -#define LIKELY(x) __builtin_expect((bool)(x), true) -#define UNLIKELY(x) __builtin_expect((bool)(x), false) -#else -#define LIKELY(x) (bool)(x) -#define UNLIKELY(x) (bool)(x) -#endif - if(UNLIKELY(mBufferNameMap.empty())) - { - MWBase::World *world = MWBase::Environment::get().getWorld(); - for(const ESM::Sound &sound : world->getStore().get()) - insertSound(Misc::StringUtils::lowerCase(sound.mId), &sound); - } - - Sound_Buffer *sfx; - NameBufferMap::const_iterator snd = mBufferNameMap.find(soundId); - if(LIKELY(snd != mBufferNameMap.end())) - sfx = snd->second; - else - { - MWBase::World *world = MWBase::Environment::get().getWorld(); - const ESM::Sound *sound = world->getStore().get().search(soundId); - if(!sound) return nullptr; - sfx = insertSound(soundId, sound); - } -#undef LIKELY -#undef UNLIKELY - - if(!sfx->mHandle) - { - size_t size; - std::tie(sfx->mHandle, size) = mOutput->loadSound(sfx->mResourceName); - if(!sfx->mHandle) return nullptr; - - mBufferCacheSize += size; - if(mBufferCacheSize > mBufferCacheMax) - { - do { - if(mUnusedBuffers.empty()) - { - Log(Debug::Warning) << "No unused sound buffers to free, using " << mBufferCacheSize << " bytes!"; - break; - } - Sound_Buffer *unused = mUnusedBuffers.back(); - - size = mOutput->unloadSound(unused->mHandle); - mBufferCacheSize -= size; - unused->mHandle = nullptr; - - mUnusedBuffers.pop_back(); - } while(mBufferCacheSize > mBufferCacheMin); - } - mUnusedBuffers.push_front(sfx); - } - - return sfx; + return mSoundBuffers.load(soundId); } DecoderPtr SoundManager::loadVoice(const std::string &voicefile) @@ -624,23 +518,18 @@ namespace MWSound SoundPtr sound = getSoundRef(); sound->init([&] { SoundParams params; - params.mVolume = volume * sfx->mVolume; + params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); params.mPitch = pitch; params.mFlags = mode | type | Play_2D; return params; } ()); - if(!mOutput->playSound(sound.get(), sfx->mHandle, offset)) + if(!mOutput->playSound(sound.get(), sfx->getHandle(), offset)) return nullptr; - if(sfx->mUses++ == 0) - { - SoundList::iterator iter = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), sfx); - if(iter != mUnusedBuffers.end()) - mUnusedBuffers.erase(iter); - } Sound* result = sound.get(); mActiveSounds[MWWorld::ConstPtr()].emplace_back(std::move(sound), sfx); + mSoundBuffers.use(*sfx); return result; } @@ -668,40 +557,35 @@ namespace MWSound { sound->init([&] { SoundParams params; - params.mVolume = volume * sfx->mVolume; + params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); params.mPitch = pitch; params.mFlags = mode | type | Play_2D; return params; } ()); - played = mOutput->playSound(sound.get(), sfx->mHandle, offset); + played = mOutput->playSound(sound.get(), sfx->getHandle(), offset); } else { sound->init([&] { SoundParams params; params.mPos = objpos; - params.mVolume = volume * sfx->mVolume; + params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); params.mPitch = pitch; - params.mMinDistance = sfx->mMinDist; - params.mMaxDistance = sfx->mMaxDist; + params.mMinDistance = sfx->getMinDist(); + params.mMaxDistance = sfx->getMaxDist(); params.mFlags = mode | type | Play_3D; return params; } ()); - played = mOutput->playSound3D(sound.get(), sfx->mHandle, offset); + played = mOutput->playSound3D(sound.get(), sfx->getHandle(), offset); } if(!played) return nullptr; - if(sfx->mUses++ == 0) - { - SoundList::iterator iter = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), sfx); - if(iter != mUnusedBuffers.end()) - mUnusedBuffers.erase(iter); - } Sound* result = sound.get(); mActiveSounds[ptr].emplace_back(std::move(sound), sfx); + mSoundBuffers.use(*sfx); return result; } @@ -720,25 +604,20 @@ namespace MWSound sound->init([&] { SoundParams params; params.mPos = initialPos; - params.mVolume = volume * sfx->mVolume; + params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); params.mPitch = pitch; - params.mMinDistance = sfx->mMinDist; - params.mMaxDistance = sfx->mMaxDist; + params.mMinDistance = sfx->getMinDist(); + params.mMaxDistance = sfx->getMaxDist(); params.mFlags = mode | type | Play_3D; return params; } ()); - if(!mOutput->playSound3D(sound.get(), sfx->mHandle, offset)) + if(!mOutput->playSound3D(sound.get(), sfx->getHandle(), offset)) return nullptr; - if(sfx->mUses++ == 0) - { - SoundList::iterator iter = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), sfx); - if(iter != mUnusedBuffers.end()) - mUnusedBuffers.erase(iter); - } Sound* result = sound.get(); mActiveSounds[MWWorld::ConstPtr()].emplace_back(std::move(sound), sfx); + mSoundBuffers.use(*sfx); return result; } @@ -921,7 +800,7 @@ namespace MWSound case WaterSoundAction::DoNothing: break; case WaterSoundAction::SetVolume: - mNearWaterSound->setVolume(update.mVolume * sfx->mVolume); + mNearWaterSound->setVolume(update.mVolume * sfx->getVolume()); break; case WaterSoundAction::FinishSound: mOutput->finishSound(mNearWaterSound); @@ -1028,7 +907,6 @@ namespace MWSound while(sndidx != snditer->second.end()) { Sound *sound = sndidx->first.get(); - Sound_Buffer *sfx = sndidx->second; if(!ptr.isEmpty() && sound->getIs3D()) { @@ -1050,8 +928,7 @@ namespace MWSound mUnderwaterSound = nullptr; if (sound == mNearWaterSound) mNearWaterSound = nullptr; - if(sfx->mUses-- == 1) - mUnusedBuffers.push_front(sfx); + mSoundBuffers.release(*sndidx->second); sndidx = snditer->second.erase(sndidx); } else @@ -1313,9 +1190,7 @@ namespace MWSound for(SoundBufferRefPair &sndbuf : snd.second) { mOutput->finishSound(sndbuf.first.get()); - Sound_Buffer *sfx = sndbuf.second; - if(sfx->mUses-- == 1) - mUnusedBuffers.push_front(sfx); + mSoundBuffers.release(*sndbuf.second); } } mActiveSounds.clear(); diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index f69171a09..21004dc94 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -18,6 +17,7 @@ #include "watersoundupdater.hpp" #include "type.hpp" #include "volumesettings.hpp" +#include "sound_buffer.hpp" namespace VFS { @@ -36,17 +36,6 @@ namespace MWSound struct Sound_Decoder; class Sound; class Stream; - class Sound_Buffer; - - enum Environment { - Env_Normal, - Env_Underwater - }; - // Extra play flags, not intended for caller use - enum PlayModeEx { - Play_2D = 0, - Play_3D = 1<<31 - }; using SoundPtr = Misc::ObjectPtr; using StreamPtr = Misc::ObjectPtr; @@ -66,21 +55,7 @@ namespace MWSound WaterSoundUpdater mWaterSoundUpdater; - typedef std::unique_ptr > SoundBufferList; - // List of sound buffers, grown as needed. New enties are added to the - // back, allowing existing Sound_Buffer references/pointers to remain - // valid. - SoundBufferList mSoundBuffers; - size_t mBufferCacheMin; - size_t mBufferCacheMax; - size_t mBufferCacheSize; - - typedef std::unordered_map NameBufferMap; - NameBufferMap mBufferNameMap; - - // NOTE: unused buffers are stored in front-newest order. - typedef std::deque SoundList; - SoundList mUnusedBuffers; + SoundBufferPool mSoundBuffers; Misc::ObjectPool mSounds; From 799bd3379c36d14635bf200f28f8fe7938bb7e1a Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 8 Jan 2021 19:33:51 +0400 Subject: [PATCH 007/117] Move screenshots handling to the separate class --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwinput/actionmanager.cpp | 10 +- apps/openmw/mwrender/renderingmanager.cpp | 290 +----------------- apps/openmw/mwrender/renderingmanager.hpp | 9 +- apps/openmw/mwrender/screenshotmanager.cpp | 324 +++++++++++++++++++++ apps/openmw/mwrender/screenshotmanager.hpp | 40 +++ apps/openmw/mwworld/worldimp.cpp | 6 +- apps/openmw/mwworld/worldimp.hpp | 2 +- 9 files changed, 386 insertions(+), 299 deletions(-) create mode 100644 apps/openmw/mwrender/screenshotmanager.cpp create mode 100644 apps/openmw/mwrender/screenshotmanager.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index a4c3b9136..f96ddb27f 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -19,7 +19,7 @@ source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender actors objects renderingmanager animation rotatecontroller sky npcanimation vismask - creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation + creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager bulletdebugdraw globalmap characterpreview camera viewovershoulder localmap water terrainstorage ripplesimulation renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging ) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 958fcfb0e..a3b035a8d 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -497,7 +497,7 @@ namespace MWBase /// \todo this does not belong here virtual void screenshot (osg::Image* image, int w, int h) = 0; - virtual bool screenshot360 (osg::Image* image, std::string settingStr) = 0; + virtual bool screenshot360 (osg::Image* image) = 0; /// Find default position inside exterior cell specified by name /// \return false if exterior with given name not exists, true otherwise diff --git a/apps/openmw/mwinput/actionmanager.cpp b/apps/openmw/mwinput/actionmanager.cpp index b29aa58a2..e0fcc5ccf 100644 --- a/apps/openmw/mwinput/actionmanager.cpp +++ b/apps/openmw/mwinput/actionmanager.cpp @@ -333,12 +333,8 @@ namespace MWInput void ActionManager::screenshot() { - bool regularScreenshot = true; - - std::string settingStr; - - settingStr = Settings::Manager::getString("screenshot type","Video"); - regularScreenshot = settingStr.size() == 0 || settingStr.compare("regular") == 0; + const std::string& settingStr = Settings::Manager::getString("screenshot type", "Video"); + bool regularScreenshot = settingStr.size() == 0 || settingStr.compare("regular") == 0; if (regularScreenshot) { @@ -349,7 +345,7 @@ namespace MWInput { osg::ref_ptr screenshot (new osg::Image); - if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get(), settingStr)) + if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get())) { (*mScreenCaptureOperation) (*(screenshot.get()), 0); // FIXME: mScreenCaptureHandler->getCaptureOperation() causes crash for some reason diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 217f0b73c..6ce431d2e 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -2,8 +2,6 @@ #include #include -#include -#include #include #include @@ -13,25 +11,20 @@ #include #include #include -#include -#include #include -#include - #include #include #include -#include - #include #include #include #include + #include #include @@ -74,7 +67,7 @@ #include "recastmesh.hpp" #include "fogmanager.hpp" #include "objectpaging.hpp" - +#include "screenshotmanager.hpp" namespace MWRender { @@ -311,6 +304,8 @@ namespace MWRender if (Settings::Manager::getBool("view over shoulder", "Camera")) mViewOverShoulderController.reset(new ViewOverShoulderController(mCamera.get())); + mScreenshotManager.reset(new ScreenshotManager(viewer, mRootNode, sceneRoot, mResourceSystem, mWater.get())); + mViewer->setLightingMode(osgViewer::View::NO_LIGHT); osg::ref_ptr source = new osg::LightSource; @@ -695,298 +690,31 @@ namespace MWRender mSky->setWaterHeight(height); } - class NotifyDrawCompletedCallback : public osg::Camera::DrawCallback + void RenderingManager::screenshot(osg::Image* image, int w, int h) { - public: - NotifyDrawCompletedCallback(unsigned int frame) - : mDone(false), mFrame(frame) - { - } + mScreenshotManager->screenshot(image, w, h); + } - void operator () (osg::RenderInfo& renderInfo) const override - { - std::lock_guard lock(mMutex); - if (renderInfo.getState()->getFrameStamp()->getFrameNumber() >= mFrame) - { - mDone = true; - mCondition.notify_one(); - } - } - - void waitTillDone() - { - std::unique_lock lock(mMutex); - if (mDone) - return; - mCondition.wait(lock); - } - - mutable std::condition_variable mCondition; - mutable std::mutex mMutex; - mutable bool mDone; - unsigned int mFrame; - }; - - bool RenderingManager::screenshot360(osg::Image* image, std::string settingStr) + bool RenderingManager::screenshot360(osg::Image* image) { - int screenshotW = mViewer->getCamera()->getViewport()->width(); - int screenshotH = mViewer->getCamera()->getViewport()->height(); - int screenshotMapping = 0; - - std::vector settingArgs; - Misc::StringUtils::split(settingStr, settingArgs); - - if (settingArgs.size() > 0) - { - std::string typeStrings[4] = {"spherical","cylindrical","planet","cubemap"}; - bool found = false; - - for (int i = 0; i < 4; ++i) - if (settingArgs[0].compare(typeStrings[i]) == 0) - { - screenshotMapping = i; - found = true; - break; - } - - if (!found) - { - Log(Debug::Warning) << "Wrong screenshot type: " << settingArgs[0] << "."; - return false; - } - } - - // planet mapping needs higher resolution - int cubeSize = screenshotMapping == 2 ? screenshotW : screenshotW / 2; - - if (settingArgs.size() > 1) - screenshotW = std::min(10000,std::atoi(settingArgs[1].c_str())); - - if (settingArgs.size() > 2) - screenshotH = std::min(10000,std::atoi(settingArgs[2].c_str())); - - if (settingArgs.size() > 3) - cubeSize = std::min(5000,std::atoi(settingArgs[3].c_str())); - if (mCamera->isVanityOrPreviewModeEnabled()) { Log(Debug::Warning) << "Spherical screenshots are not allowed in preview mode."; return false; } - bool rawCubemap = screenshotMapping == 3; - - if (rawCubemap) - screenshotW = cubeSize * 6; // the image will consist of 6 cube sides in a row - else if (screenshotMapping == 2) - screenshotH = screenshotW; // use square resolution for planet mapping - - std::vector> images; - - for (int i = 0; i < 6; ++i) - images.push_back(new osg::Image); - - osg::Vec3 directions[6] = { - rawCubemap ? osg::Vec3(1,0,0) : osg::Vec3(0,0,1), - osg::Vec3(0,0,-1), - osg::Vec3(-1,0,0), - rawCubemap ? osg::Vec3(0,0,1) : osg::Vec3(1,0,0), - osg::Vec3(0,1,0), - osg::Vec3(0,-1,0)}; - - double rotations[] = { - -osg::PI / 2.0, - osg::PI / 2.0, - osg::PI, - 0, - osg::PI / 2.0, - osg::PI / 2.0}; - - double fovBackup = mFieldOfView; - mFieldOfView = 90.0; // each cubemap side sees 90 degrees - int maskBackup = mPlayerAnimation->getObjectRoot()->getNodeMask(); if (mCamera->isFirstPerson()) mPlayerAnimation->getObjectRoot()->setNodeMask(0); - for (int i = 0; i < 6; ++i) // for each cubemap side - { - osg::Matrixd transform = osg::Matrixd::rotate(osg::Vec3(0,0,-1),directions[i]); - - if (!rawCubemap) - transform *= osg::Matrixd::rotate(rotations[i],osg::Vec3(0,0,-1)); - - osg::Image *sideImage = images[i].get(); - screenshot(sideImage,cubeSize,cubeSize,transform); - - if (!rawCubemap) - sideImage->flipHorizontal(); - } + mScreenshotManager->screenshot360(image); mPlayerAnimation->getObjectRoot()->setNodeMask(maskBackup); - mFieldOfView = fovBackup; - - if (rawCubemap) // for raw cubemap don't run on GPU, just merge the images - { - image->allocateImage(cubeSize * 6,cubeSize,images[0]->r(),images[0]->getPixelFormat(),images[0]->getDataType()); - - for (int i = 0; i < 6; ++i) - osg::copyImage(images[i].get(),0,0,0,images[i]->s(),images[i]->t(),images[i]->r(),image,i * cubeSize,0,0); - - return true; - } - - // run on GPU now: - - osg::ref_ptr cubeTexture (new osg::TextureCubeMap); - cubeTexture->setResizeNonPowerOfTwoHint(false); - - cubeTexture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::NEAREST); - cubeTexture->setFilter(osg::Texture::MAG_FILTER,osg::Texture::NEAREST); - - cubeTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - cubeTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - - for (int i = 0; i < 6; ++i) - cubeTexture->setImage(i,images[i].get()); - - osg::ref_ptr screenshotCamera (new osg::Camera); - osg::ref_ptr quad (new osg::ShapeDrawable(new osg::Box(osg::Vec3(0,0,0),2.0))); - - std::map defineMap; - - Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); - osg::ref_ptr fragmentShader (shaderMgr.getShader("s360_fragment.glsl",defineMap,osg::Shader::FRAGMENT)); - osg::ref_ptr vertexShader (shaderMgr.getShader("s360_vertex.glsl", defineMap, osg::Shader::VERTEX)); - osg::ref_ptr stateset = new osg::StateSet; - - osg::ref_ptr program (new osg::Program); - program->addShader(fragmentShader); - program->addShader(vertexShader); - stateset->setAttributeAndModes(program, osg::StateAttribute::ON); - - stateset->addUniform(new osg::Uniform("cubeMap",0)); - stateset->addUniform(new osg::Uniform("mapping",screenshotMapping)); - stateset->setTextureAttributeAndModes(0,cubeTexture,osg::StateAttribute::ON); - - quad->setStateSet(stateset); - quad->setUpdateCallback(nullptr); - - screenshotCamera->addChild(quad); - - renderCameraToImage(screenshotCamera,image,screenshotW,screenshotH); return true; } - void RenderingManager::renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h) - { - camera->setNodeMask(Mask_RenderToTexture); - camera->attach(osg::Camera::COLOR_BUFFER, image); - camera->setRenderOrder(osg::Camera::PRE_RENDER); - camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); - camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT,osg::Camera::PIXEL_BUFFER_RTT); - - camera->setViewport(0, 0, w, h); - - osg::ref_ptr texture (new osg::Texture2D); - texture->setInternalFormat(GL_RGB); - texture->setTextureSize(w,h); - texture->setResizeNonPowerOfTwoHint(false); - texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - camera->attach(osg::Camera::COLOR_BUFFER,texture); - - image->setDataType(GL_UNSIGNED_BYTE); - image->setPixelFormat(texture->getInternalFormat()); - - mRootNode->addChild(camera); - - // The draw needs to complete before we can copy back our image. - osg::ref_ptr callback (new NotifyDrawCompletedCallback(0)); - camera->setFinalDrawCallback(callback); - - MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOn(false); - - mViewer->eventTraversal(); - mViewer->updateTraversal(); - mViewer->renderingTraversals(); - callback->waitTillDone(); - - MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOff(); - - // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed - mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); - - camera->removeChildren(0, camera->getNumChildren()); - mRootNode->removeChild(camera); - } - - class ReadImageFromFramebufferCallback : public osg::Drawable::DrawCallback - { - public: - ReadImageFromFramebufferCallback(osg::Image* image, int width, int height) - : mWidth(width), mHeight(height), mImage(image) - { - } - void drawImplementation(osg::RenderInfo& renderInfo,const osg::Drawable* /*drawable*/) const override - { - int screenW = renderInfo.getCurrentCamera()->getViewport()->width(); - int screenH = renderInfo.getCurrentCamera()->getViewport()->height(); - double imageaspect = (double)mWidth/(double)mHeight; - int leftPadding = std::max(0, static_cast(screenW - screenH * imageaspect) / 2); - int topPadding = std::max(0, static_cast(screenH - screenW / imageaspect) / 2); - int width = screenW - leftPadding*2; - int height = screenH - topPadding*2; - mImage->readPixels(leftPadding, topPadding, width, height, GL_RGB, GL_UNSIGNED_BYTE); - mImage->scaleImage(mWidth, mHeight, 1); - } - private: - int mWidth; - int mHeight; - osg::ref_ptr mImage; - }; - - void RenderingManager::screenshotFramebuffer(osg::Image* image, int w, int h) - { - osg::Camera* camera = mViewer->getCamera(); - osg::ref_ptr tempDrw = new osg::Drawable; - tempDrw->setDrawCallback(new ReadImageFromFramebufferCallback(image, w, h)); - tempDrw->setCullingActive(false); - tempDrw->getOrCreateStateSet()->setRenderBinDetails(100, "RenderBin", osg::StateSet::USE_RENDERBIN_DETAILS); // so its after all scene bins but before POST_RENDER gui camera - camera->addChild(tempDrw); - osg::ref_ptr callback (new NotifyDrawCompletedCallback(mViewer->getFrameStamp()->getFrameNumber())); - camera->setFinalDrawCallback(callback); - mViewer->eventTraversal(); - mViewer->updateTraversal(); - mViewer->renderingTraversals(); - callback->waitTillDone(); - // now that we've "used up" the current frame, get a fresh frame number for the next frame() following after the screenshot is completed - mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); - camera->removeChild(tempDrw); - camera->setFinalDrawCallback(nullptr); - } - - void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Matrixd cameraTransform) - { - osg::ref_ptr rttCamera (new osg::Camera); - rttCamera->setProjectionMatrixAsPerspective(mFieldOfView, w/float(h), mNearClip, mViewDistance); - rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * cameraTransform); - - rttCamera->setUpdateCallback(new NoTraverseCallback); - rttCamera->addChild(mSceneRoot); - - rttCamera->addChild(mWater->getReflectionCamera()); - rttCamera->addChild(mWater->getRefractionCamera()); - - rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); - - rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - renderCameraToImage(rttCamera.get(),image,w,h); - } - osg::Vec4f RenderingManager::getScreenBounds(const osg::BoundingBox &worldbb) { if (!worldbb.valid()) return osg::Vec4f(); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index ef28cf544..39d1a0194 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -74,6 +74,7 @@ namespace MWRender class StateUpdater; class EffectManager; + class ScreenshotManager; class FogManager; class SkyManager; class NpcAnimation; @@ -148,9 +149,8 @@ namespace MWRender void setWaterHeight(float level); /// Take a screenshot of w*h onto the given image, not including the GUI. - void screenshot(osg::Image* image, int w, int h, osg::Matrixd cameraTransform=osg::Matrixd()); // make a new render at given size - void screenshotFramebuffer(osg::Image* image, int w, int h); // copy directly from framebuffer and scale to given size - bool screenshot360(osg::Image* image, std::string settingStr); + void screenshot(osg::Image* image, int w, int h); + bool screenshot360(osg::Image* image); struct RayResult { @@ -248,8 +248,6 @@ namespace MWRender void reportStats() const; - void renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h); - void updateNavMesh(); void updateRecastMesh(); @@ -281,6 +279,7 @@ namespace MWRender std::unique_ptr mObjectPaging; std::unique_ptr mSky; std::unique_ptr mFog; + std::unique_ptr mScreenshotManager; std::unique_ptr mEffectManager; std::unique_ptr mShadowManager; osg::ref_ptr mPlayerAnimation; diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp new file mode 100644 index 000000000..89b225da4 --- /dev/null +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -0,0 +1,324 @@ +#include "screenshotmanager.hpp" + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "../mwgui/loadingscreen.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" + +#include "util.hpp" +#include "vismask.hpp" +#include "water.hpp" + +namespace MWRender +{ + enum Screenshot360Type + { + Spherical, + Cylindrical, + Planet, + RawCubemap + }; + + class NotifyDrawCompletedCallback : public osg::Camera::DrawCallback + { + public: + NotifyDrawCompletedCallback(unsigned int frame) + : mDone(false), mFrame(frame) + { + } + + void operator () (osg::RenderInfo& renderInfo) const override + { + std::lock_guard lock(mMutex); + if (renderInfo.getState()->getFrameStamp()->getFrameNumber() >= mFrame) + { + mDone = true; + mCondition.notify_one(); + } + } + + void waitTillDone() + { + std::unique_lock lock(mMutex); + if (mDone) + return; + mCondition.wait(lock); + } + + mutable std::condition_variable mCondition; + mutable std::mutex mMutex; + mutable bool mDone; + unsigned int mFrame; + }; + + class ReadImageFromFramebufferCallback : public osg::Drawable::DrawCallback + { + public: + ReadImageFromFramebufferCallback(osg::Image* image, int width, int height) + : mWidth(width), mHeight(height), mImage(image) + { + } + void drawImplementation(osg::RenderInfo& renderInfo,const osg::Drawable* /*drawable*/) const override + { + int screenW = renderInfo.getCurrentCamera()->getViewport()->width(); + int screenH = renderInfo.getCurrentCamera()->getViewport()->height(); + double imageaspect = (double)mWidth/(double)mHeight; + int leftPadding = std::max(0, static_cast(screenW - screenH * imageaspect) / 2); + int topPadding = std::max(0, static_cast(screenH - screenW / imageaspect) / 2); + int width = screenW - leftPadding*2; + int height = screenH - topPadding*2; + mImage->readPixels(leftPadding, topPadding, width, height, GL_RGB, GL_UNSIGNED_BYTE); + mImage->scaleImage(mWidth, mHeight, 1); + } + private: + int mWidth; + int mHeight; + osg::ref_ptr mImage; + }; + + ScreenshotManager::ScreenshotManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, osg::ref_ptr sceneRoot, Resource::ResourceSystem* resourceSystem, Water* water) + : mViewer(viewer) + , mRootNode(rootNode) + , mSceneRoot(sceneRoot) + , mResourceSystem(resourceSystem) + , mWater(water) + { + } + + void ScreenshotManager::screenshot(osg::Image* image, int w, int h) + { + osg::Camera* camera = mViewer->getCamera(); + osg::ref_ptr tempDrw = new osg::Drawable; + tempDrw->setDrawCallback(new ReadImageFromFramebufferCallback(image, w, h)); + tempDrw->setCullingActive(false); + tempDrw->getOrCreateStateSet()->setRenderBinDetails(100, "RenderBin", osg::StateSet::USE_RENDERBIN_DETAILS); // so its after all scene bins but before POST_RENDER gui camera + camera->addChild(tempDrw); + osg::ref_ptr callback (new NotifyDrawCompletedCallback(mViewer->getFrameStamp()->getFrameNumber())); + camera->setFinalDrawCallback(callback); + mViewer->eventTraversal(); + mViewer->updateTraversal(); + mViewer->renderingTraversals(); + callback->waitTillDone(); + // now that we've "used up" the current frame, get a fresh frame number for the next frame() following after the screenshot is completed + mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); + camera->removeChild(tempDrw); + camera->setFinalDrawCallback(nullptr); + } + + bool ScreenshotManager::screenshot360(osg::Image* image) + { + int screenshotW = mViewer->getCamera()->getViewport()->width(); + int screenshotH = mViewer->getCamera()->getViewport()->height(); + Screenshot360Type screenshotMapping = Spherical; + + const std::string& settingStr = Settings::Manager::getString("screenshot type", "Video"); + std::vector settingArgs; + Misc::StringUtils::split(settingStr, settingArgs); + + if (settingArgs.size() > 0) + { + std::string typeStrings[4] = {"spherical", "cylindrical", "planet", "cubemap"}; + bool found = false; + + for (int i = 0; i < 4; ++i) + { + if (settingArgs[0].compare(typeStrings[i]) == 0) + { + screenshotMapping = static_cast(i); + found = true; + break; + } + } + + if (!found) + { + Log(Debug::Warning) << "Wrong screenshot type: " << settingArgs[0] << "."; + return false; + } + } + + // planet mapping needs higher resolution + int cubeSize = screenshotMapping == Planet ? screenshotW : screenshotW / 2; + + if (settingArgs.size() > 1) + screenshotW = std::min(10000, std::atoi(settingArgs[1].c_str())); + + if (settingArgs.size() > 2) + screenshotH = std::min(10000, std::atoi(settingArgs[2].c_str())); + + if (settingArgs.size() > 3) + cubeSize = std::min(5000, std::atoi(settingArgs[3].c_str())); + + bool rawCubemap = screenshotMapping == RawCubemap; + + if (rawCubemap) + screenshotW = cubeSize * 6; // the image will consist of 6 cube sides in a row + else if (screenshotMapping == Planet) + screenshotH = screenshotW; // use square resolution for planet mapping + + std::vector> images; + + for (int i = 0; i < 6; ++i) + images.push_back(new osg::Image); + + osg::Vec3 directions[6] = { + rawCubemap ? osg::Vec3(1,0,0) : osg::Vec3(0,0,1), + osg::Vec3(0,0,-1), + osg::Vec3(-1,0,0), + rawCubemap ? osg::Vec3(0,0,1) : osg::Vec3(1,0,0), + osg::Vec3(0,1,0), + osg::Vec3(0,-1,0)}; + + double rotations[] = { + -osg::PI / 2.0, + osg::PI / 2.0, + osg::PI, + 0, + osg::PI / 2.0, + osg::PI / 2.0 }; + + for (int i = 0; i < 6; ++i) // for each cubemap side + { + osg::Matrixd transform = osg::Matrixd::rotate(osg::Vec3(0,0,-1), directions[i]); + + if (!rawCubemap) + transform *= osg::Matrixd::rotate(rotations[i],osg::Vec3(0,0,-1)); + + osg::Image *sideImage = images[i].get(); + makeCubemapScreenshot(sideImage, cubeSize, cubeSize, transform); + + if (!rawCubemap) + sideImage->flipHorizontal(); + } + + if (rawCubemap) // for raw cubemap don't run on GPU, just merge the images + { + image->allocateImage(cubeSize * 6,cubeSize,images[0]->r(),images[0]->getPixelFormat(),images[0]->getDataType()); + + for (int i = 0; i < 6; ++i) + osg::copyImage(images[i].get(),0,0,0,images[i]->s(),images[i]->t(),images[i]->r(),image,i * cubeSize,0,0); + + return true; + } + + // run on GPU now: + osg::ref_ptr cubeTexture (new osg::TextureCubeMap); + cubeTexture->setResizeNonPowerOfTwoHint(false); + + cubeTexture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::NEAREST); + cubeTexture->setFilter(osg::Texture::MAG_FILTER,osg::Texture::NEAREST); + + cubeTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + cubeTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + + for (int i = 0; i < 6; ++i) + cubeTexture->setImage(i, images[i].get()); + + osg::ref_ptr screenshotCamera(new osg::Camera); + osg::ref_ptr quad(new osg::ShapeDrawable(new osg::Box(osg::Vec3(0,0,0), 2.0))); + + std::map defineMap; + + Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); + osg::ref_ptr fragmentShader(shaderMgr.getShader("s360_fragment.glsl", defineMap,osg::Shader::FRAGMENT)); + osg::ref_ptr vertexShader(shaderMgr.getShader("s360_vertex.glsl", defineMap, osg::Shader::VERTEX)); + osg::ref_ptr stateset = new osg::StateSet; + + osg::ref_ptr program(new osg::Program); + program->addShader(fragmentShader); + program->addShader(vertexShader); + stateset->setAttributeAndModes(program, osg::StateAttribute::ON); + + stateset->addUniform(new osg::Uniform("cubeMap", 0)); + stateset->addUniform(new osg::Uniform("mapping", screenshotMapping)); + stateset->setTextureAttributeAndModes(0, cubeTexture, osg::StateAttribute::ON); + + quad->setStateSet(stateset); + quad->setUpdateCallback(nullptr); + + screenshotCamera->addChild(quad); + + renderCameraToImage(screenshotCamera, image, screenshotW, screenshotH); + + return true; + } + + void ScreenshotManager::renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h) + { + camera->setNodeMask(Mask_RenderToTexture); + camera->attach(osg::Camera::COLOR_BUFFER, image); + camera->setRenderOrder(osg::Camera::PRE_RENDER); + camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT,osg::Camera::PIXEL_BUFFER_RTT); + + camera->setViewport(0, 0, w, h); + + osg::ref_ptr texture (new osg::Texture2D); + texture->setInternalFormat(GL_RGB); + texture->setTextureSize(w,h); + texture->setResizeNonPowerOfTwoHint(false); + texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + camera->attach(osg::Camera::COLOR_BUFFER,texture); + + image->setDataType(GL_UNSIGNED_BYTE); + image->setPixelFormat(texture->getInternalFormat()); + + mRootNode->addChild(camera); + + // The draw needs to complete before we can copy back our image. + osg::ref_ptr callback (new NotifyDrawCompletedCallback(0)); + camera->setFinalDrawCallback(callback); + + MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOn(false); + + mViewer->eventTraversal(); + mViewer->updateTraversal(); + mViewer->renderingTraversals(); + callback->waitTillDone(); + + MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOff(); + + // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed + mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); + + camera->removeChildren(0, camera->getNumChildren()); + mRootNode->removeChild(camera); + } + + void ScreenshotManager::makeCubemapScreenshot(osg::Image *image, int w, int h, osg::Matrixd cameraTransform) + { + osg::ref_ptr rttCamera (new osg::Camera); + float nearClip = Settings::Manager::getFloat("near clip", "Camera"); + float viewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); + // each cubemap side sees 90 degrees + rttCamera->setProjectionMatrixAsPerspective(90.0, w/float(h), nearClip, viewDistance); + rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * cameraTransform); + + rttCamera->setUpdateCallback(new NoTraverseCallback); + rttCamera->addChild(mSceneRoot); + + rttCamera->addChild(mWater->getReflectionCamera()); + rttCamera->addChild(mWater->getRefractionCamera()); + + rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); + + rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + renderCameraToImage(rttCamera.get(),image,w,h); + } +} diff --git a/apps/openmw/mwrender/screenshotmanager.hpp b/apps/openmw/mwrender/screenshotmanager.hpp new file mode 100644 index 000000000..2ac50bdf0 --- /dev/null +++ b/apps/openmw/mwrender/screenshotmanager.hpp @@ -0,0 +1,40 @@ +#ifndef MWRENDER_SCREENSHOTMANAGER_H +#define MWRENDER_SCREENSHOTMANAGER_H + +#include + +#include +#include + +#include + +namespace Resource +{ + class ResourceSystem; +} + +namespace MWRender +{ + class Water; + + class ScreenshotManager + { + public: + ScreenshotManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, osg::ref_ptr sceneRoot, Resource::ResourceSystem* resourceSystem, Water* water); + + void screenshot(osg::Image* image, int w, int h); + bool screenshot360(osg::Image* image); + + private: + osg::ref_ptr mViewer; + osg::ref_ptr mRootNode; + osg::ref_ptr mSceneRoot; + Resource::ResourceSystem* mResourceSystem; + Water* mWater; + + void renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h); + void makeCubemapScreenshot(osg::Image* image, int w, int h, osg::Matrixd cameraTransform=osg::Matrixd()); + }; +} + +#endif diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 8b95ee122..bf5b6db73 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2536,12 +2536,12 @@ namespace MWWorld void World::screenshot(osg::Image* image, int w, int h) { - mRendering->screenshotFramebuffer(image, w, h); + mRendering->screenshot(image, w, h); } - bool World::screenshot360(osg::Image* image, std::string settingStr) + bool World::screenshot360(osg::Image* image) { - return mRendering->screenshot360(image,settingStr); + return mRendering->screenshot360(image); } void World::activateDoor(const MWWorld::Ptr& door) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 9f0501413..79c8a4980 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -597,7 +597,7 @@ namespace MWWorld /// \todo this does not belong here void screenshot (osg::Image* image, int w, int h) override; - bool screenshot360 (osg::Image* image, std::string settingStr) override; + bool screenshot360 (osg::Image* image) override; /// Find center of exterior cell above land surface /// \return false if exterior with given name not exists, true otherwise From c5a36ad440a55ebf999a2e9d452e5437b62180ec Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 13:19:41 +0400 Subject: [PATCH 008/117] Do not cast enums to booleans --- apps/launcher/advancedpage.cpp | 2 +- apps/launcher/graphicspage.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 6f59ade7a..54fbbdfe0 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -108,7 +108,7 @@ bool Launcher::AdvancedPage::loadSettings() loadSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); connect(animSourcesCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotAnimSourcesToggled(bool))); loadSettingBool(animSourcesCheckBox, "use additional anim sources", "Game"); - if (animSourcesCheckBox->checkState()) + if (animSourcesCheckBox->checkState() != Qt::Unchecked) { loadSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game"); loadSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game"); diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index d1cf3aa6f..01205043e 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -206,7 +206,7 @@ void Launcher::GraphicsPage::saveSettings() if (cScreen != mEngineSettings.getInt("screen", "Video")) mEngineSettings.setInt("screen", "Video", cScreen); - if (framerateLimitCheckBox->checkState()) + if (framerateLimitCheckBox->checkState() != Qt::Unchecked) { float cFpsLimit = framerateLimitSpinBox->value(); if (cFpsLimit != mEngineSettings.getFloat("framerate limit", "Video")) @@ -217,7 +217,7 @@ void Launcher::GraphicsPage::saveSettings() mEngineSettings.setFloat("framerate limit", "Video", 0); } - int cShadowDist = shadowDistanceCheckBox->checkState() ? shadowDistanceSpinBox->value() : 0; + int cShadowDist = shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0; if (mEngineSettings.getInt("maximum shadow map distance", "Shadows") != cShadowDist) mEngineSettings.setInt("maximum shadow map distance", "Shadows", cShadowDist); float cFadeStart = fadeStartSpinBox->value(); From 801e2d6ad0f66ce9646c06ff6b73793c54349ce4 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 13:36:40 +0400 Subject: [PATCH 009/117] Avoid to use uninitialized variables --- apps/opencs/model/world/refidadapterimp.cpp | 2 +- apps/opencs/model/world/refidadapterimp.hpp | 2 +- components/files/constrainedfilestream.cpp | 2 +- components/nif/data.hpp | 10 +++++----- components/sceneutil/mwshadowtechnique.cpp | 2 ++ 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index d85fcc068..d944adc23 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -10,7 +10,7 @@ #include "nestedtablewrapper.hpp" CSMWorld::PotionColumns::PotionColumns (const InventoryColumns& columns) -: InventoryColumns (columns) {} +: InventoryColumns (columns), mEffects(nullptr) {} CSMWorld::PotionRefIdAdapter::PotionRefIdAdapter (const PotionColumns& columns, const RefIdColumn *autoCalc) diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 7695e9ace..a73f76af9 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -115,7 +115,7 @@ namespace CSMWorld { const RefIdColumn *mModel; - ModelColumns (const BaseColumns& base) : BaseColumns (base) {} + ModelColumns (const BaseColumns& base) : BaseColumns (base), mModel(nullptr) {} }; /// \brief Adapter for IDs with models (all but levelled lists) diff --git a/components/files/constrainedfilestream.cpp b/components/files/constrainedfilestream.cpp index baab1b081..b9968038d 100644 --- a/components/files/constrainedfilestream.cpp +++ b/components/files/constrainedfilestream.cpp @@ -21,7 +21,7 @@ namespace Files LowLevelFile mFile; - char mBuffer[sBufferSize]; + char mBuffer[sBufferSize]{0}; public: ConstrainedFileStreamBuf(const std::string &fname, size_t start, size_t length) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 66a391afc..efbe138c5 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -71,7 +71,7 @@ struct NiParticlesData : public NiGeometryData { int numParticles{0}; - int activeCount; + int activeCount{0}; std::vector particleRadii, sizes, rotationAngles; std::vector rotations; @@ -119,14 +119,14 @@ struct NiPixelData : public Record NIPXFMT_DXT5, NIPXFMT_DXT5_ALT }; - Format fmt; + Format fmt{NIPXFMT_RGB8}; - unsigned int colorMask[4]; - unsigned int bpp, pixelTiling{0}; + unsigned int colorMask[4]{0}; + unsigned int bpp{0}, pixelTiling{0}; bool sRGB{false}; NiPalettePtr palette; - unsigned int numberOfMipmaps; + unsigned int numberOfMipmaps{0}; struct Mipmap { diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index c49a14777..294780cfd 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -785,6 +785,7 @@ MWShadowTechnique::MWShadowTechnique(): _debugHud(nullptr) { _shadowRecievingPlaceholderStateSet = new osg::StateSet; + mSetDummyStateWhenDisabled = false; } MWShadowTechnique::MWShadowTechnique(const MWShadowTechnique& vdsm, const osg::CopyOp& copyop): @@ -792,6 +793,7 @@ MWShadowTechnique::MWShadowTechnique(const MWShadowTechnique& vdsm, const osg::C { _shadowRecievingPlaceholderStateSet = new osg::StateSet; _enableShadows = vdsm._enableShadows; + mSetDummyStateWhenDisabled = vdsm.mSetDummyStateWhenDisabled; } MWShadowTechnique::~MWShadowTechnique() From 7fc4c9f3f69d81be16d2294a7c390859ae63e278 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 13:52:01 +0400 Subject: [PATCH 010/117] Avoid dead code --- apps/opencs/model/filter/parser.cpp | 6 ------ apps/opencs/view/render/terraintexturemode.cpp | 8 ++------ apps/openmw/mwgui/journalwindow.cpp | 2 +- apps/openmw/mwmechanics/character.cpp | 4 ++-- components/compiler/scanner.hpp | 11 ++++------- extern/oics/ICSInputControlSystem_joystick.cpp | 2 +- 6 files changed, 10 insertions(+), 23 deletions(-) diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp index d2a4f2a35..d363b4849 100644 --- a/apps/opencs/model/filter/parser.cpp +++ b/apps/opencs/model/filter/parser.cpp @@ -325,12 +325,6 @@ std::shared_ptr CSMFilter::Parser::parseNAry (const Token& keyw break; } - if (nodes.empty()) - { - error(); - return std::shared_ptr(); - } - switch (keyword.mType) { case Token::Type_Keyword_And: return std::shared_ptr (new AndNode (nodes)); diff --git a/apps/opencs/view/render/terraintexturemode.cpp b/apps/opencs/view/render/terraintexturemode.cpp index c8d63f32e..09d6b135a 100644 --- a/apps/opencs/view/render/terraintexturemode.cpp +++ b/apps/opencs/view/render/terraintexturemode.cpp @@ -429,12 +429,8 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe { int distanceX(0); int distanceY(0); - if (i_cell < cellX) distanceX = xHitInCell + landTextureSize * abs(i_cell-cellX) - i; - if (j_cell < cellY) distanceY = yHitInCell + landTextureSize * abs(j_cell-cellY) - j; - if (i_cell > cellX) distanceX = -xHitInCell + landTextureSize* abs(i_cell-cellX) + i; - if (j_cell > cellY) distanceY = -yHitInCell + landTextureSize * abs(j_cell-cellY) + j; - if (i_cell == cellX) distanceX = abs(i-xHitInCell); - if (j_cell == cellY) distanceY = abs(j-yHitInCell); + distanceX = abs(i-xHitInCell); + distanceY = abs(j-yHitInCell); float distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2))); float rf = static_cast(mBrushSize) / 2; if (distance < rf) newTerrain[j*landTextureSize+i] = brushInt; diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index 1474becf0..96c42549a 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -561,7 +561,7 @@ namespace if (mAllQuests) { SetNamesInactive setInactive(list); - mModel->visitQuestNames(!mAllQuests, setInactive); + mModel->visitQuestNames(false, setInactive); } MWBase::Environment::get().getWindowManager()->playSound("book page"); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index b9bfe6bc0..b9ef50bf7 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -446,9 +446,9 @@ std::string CharacterController::fallbackShortWeaponGroup(const std::string& bas const ESM::WeaponType* weapInfo = getWeaponType(mWeaponType); // For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones - if (isRealWeapon && weapInfo->mFlags & ESM::WeaponType::TwoHanded && weapInfo->mWeaponClass == ESM::WeaponType::Melee) + if (weapInfo->mFlags & ESM::WeaponType::TwoHanded && weapInfo->mWeaponClass == ESM::WeaponType::Melee) groupName += twoHandFallback; - else if (isRealWeapon) + else groupName += oneHandFallback; // Special case for crossbows - we shouls apply 1h animations a fallback only for lower body diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index 2139f04b2..9c7bd656e 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -140,15 +140,12 @@ namespace Compiler for (int i = 0; i <= length; i++) { - if (length >= i) - { - in.get (ch); + in.get (ch); - if (!in.good()) - return false; + if (!in.good()) + return false; - mData[i] = ch; - } + mData[i] = ch; } mLength = length; diff --git a/extern/oics/ICSInputControlSystem_joystick.cpp b/extern/oics/ICSInputControlSystem_joystick.cpp index 38199436b..697d0ed3d 100644 --- a/extern/oics/ICSInputControlSystem_joystick.cpp +++ b/extern/oics/ICSInputControlSystem_joystick.cpp @@ -286,7 +286,7 @@ namespace ICS //ControlAxisBinderItem joystickBinderItem = mControlsJoystickAxisBinderMap[ evt.which ][ axis ]; // joystic axis start at 0 index //Control* ctrl = joystickBinderItem.control; //if(ctrl && ctrl->isAxisBindable()) - if(mDetectingBindingControl && mDetectingBindingControl->isAxisBindable()) + if(mDetectingBindingControl->isAxisBindable()) { if( abs( evt.value ) > ICS_JOYSTICK_AXIS_BINDING_MARGIN) { From 8283ec6cadef7bcafdc4a830f2d60d00b26b6c9b Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 14:03:48 +0400 Subject: [PATCH 011/117] Do not use & for boolean arguments --- apps/opencs/view/doc/view.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index be3fe5142..6fe01dc27 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -438,8 +438,8 @@ void CSVDoc::View::updateActions() for (std::vector::iterator iter (mEditingActions.begin()); iter!=mEditingActions.end(); ++iter) (*iter)->setEnabled (editing); - mUndo->setEnabled (editing & mDocument->getUndoStack().canUndo()); - mRedo->setEnabled (editing & mDocument->getUndoStack().canRedo()); + mUndo->setEnabled (editing && mDocument->getUndoStack().canUndo()); + mRedo->setEnabled (editing && mDocument->getUndoStack().canRedo()); mSave->setEnabled (!(mDocument->getState() & CSMDoc::State_Saving) && !running); mVerify->setEnabled (!(mDocument->getState() & CSMDoc::State_Verifying)); From 56666c60d44fa7809f8e9ab811827c4c908ce6d9 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 14:17:59 +0400 Subject: [PATCH 012/117] Remove dead code --- apps/openmw/mwmechanics/weapontype.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/openmw/mwmechanics/weapontype.cpp b/apps/openmw/mwmechanics/weapontype.cpp index 07345557f..2f8e45f7f 100644 --- a/apps/openmw/mwmechanics/weapontype.cpp +++ b/apps/openmw/mwmechanics/weapontype.cpp @@ -4,8 +4,6 @@ namespace MWMechanics { - static const ESM::WeaponType *sWeaponTypeListEnd = &sWeaponTypeList[sizeof(sWeaponTypeList)/sizeof(sWeaponTypeList[0])]; - MWWorld::ContainerStoreIterator getActiveWeapon(MWWorld::Ptr actor, int *weaptype) { MWWorld::InventoryStore &inv = actor.getClass().getInventoryStore(actor); From c1512b8b6cc47a8615c8400d91ed913e0ae35a82 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 14:18:38 +0400 Subject: [PATCH 013/117] Convert loop to condition --- apps/openmw/mwworld/localscripts.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index 42914d4ac..1661d6b9f 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -76,7 +76,7 @@ void MWWorld::LocalScripts::startIteration() bool MWWorld::LocalScripts::getNext(std::pair& script) { - while (mIter!=mScripts.end()) + if (mIter!=mScripts.end()) { std::list >::iterator iter = mIter++; script = *iter; From 33648313a68efad40eebf4d218261b5d00845ea1 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 14:21:57 +0400 Subject: [PATCH 014/117] Initialize variables --- apps/openmw/mwphysics/mtphysics.cpp | 3 +++ components/nif/node.hpp | 4 ++-- components/nif/property.hpp | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 0d1e5962a..3e4ed9b47 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -152,6 +152,9 @@ namespace MWPhysics , mNextLOS(0) , mFrameNumber(0) , mTimer(osg::Timer::instance()) + , mTimeBegin(0) + , mTimeEnd(0) + , mFrameStart(0) { mNumThreads = Config::computeNumThreads(mThreadSafeBullet); diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 1d082b8f9..406a4d454 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -177,7 +177,7 @@ struct Node : public Named // NiNodes (or types derived from NiNodes) can be parents. NiNode *parent; - bool isBone; + bool isBone{false}; void setBone() { @@ -378,7 +378,7 @@ struct NiCamera : Node struct NiSwitchNode : public NiNode { unsigned int switchFlags{0}; - unsigned int initialIndex; + unsigned int initialIndex{0}; void read(NIFStream *nif) override { diff --git a/components/nif/property.hpp b/components/nif/property.hpp index eccb442f7..008e84515 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -61,7 +61,7 @@ struct NiTexturingProperty : public Property 3 - hilight // These two are for PS2 only? 4 - hilight2 */ - unsigned int apply; + unsigned int apply{0}; /* * The textures in this list are as follows: @@ -193,7 +193,7 @@ struct S_MaterialProperty // The vector components are R,G,B osg::Vec3f ambient{1.f,1.f,1.f}, diffuse{1.f,1.f,1.f}; osg::Vec3f specular, emissive; - float glossiness, alpha; + float glossiness{0.f}, alpha{0.f}; void read(NIFStream *nif); }; From c9b885ffd4e3f01943d08488722abd7fbb539e61 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 14:24:04 +0400 Subject: [PATCH 015/117] Avoid possible null dereferencing --- apps/opencs/view/world/dragdroputils.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/world/dragdroputils.cpp b/apps/opencs/view/world/dragdroputils.cpp index 789d4f33d..808125a60 100644 --- a/apps/opencs/view/world/dragdroputils.cpp +++ b/apps/opencs/view/world/dragdroputils.cpp @@ -20,7 +20,8 @@ CSMWorld::UniversalId CSVWorld::DragDropUtils::getAcceptedData(const QDropEvent { if (canAcceptData(event, type)) { - return getTableMimeData(event)->returnMatching(type); + if (const CSMWorld::TableMimeData *data = getTableMimeData(event)) + return data->returnMatching(type); } return CSMWorld::UniversalId::Type_None; } From 8e5f26c109da6e1b2b5d7126e3e66b6a61ce073d Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 14:41:10 +0400 Subject: [PATCH 016/117] Code cleanup --- apps/opencs/view/world/dragrecordtable.cpp | 12 ++++-------- apps/openmw/mwgui/hud.cpp | 2 +- apps/openmw/mwrender/globalmap.cpp | 2 +- components/terrain/terraindrawable.cpp | 2 +- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/apps/opencs/view/world/dragrecordtable.cpp b/apps/opencs/view/world/dragrecordtable.cpp index d795bd5de..f84bf639d 100644 --- a/apps/opencs/view/world/dragrecordtable.cpp +++ b/apps/opencs/view/world/dragrecordtable.cpp @@ -19,14 +19,10 @@ void CSVWorld::DragRecordTable::startDragFromTable (const CSVWorld::DragRecordTa } CSMWorld::TableMimeData* mime = new CSMWorld::TableMimeData (records, mDocument); - - if (mime) - { - QDrag* drag = new QDrag (this); - drag->setMimeData (mime); - drag->setPixmap (QString::fromUtf8 (mime->getIcon().c_str())); - drag->exec (Qt::CopyAction); - } + QDrag* drag = new QDrag (this); + drag->setMimeData (mime); + drag->setPixmap (QString::fromUtf8 (mime->getIcon().c_str())); + drag->exec (Qt::CopyAction); } CSVWorld::DragRecordTable::DragRecordTable (CSMDoc::Document& document, QWidget* parent) : diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index a4ab20fd6..45defe9a5 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -587,7 +587,7 @@ namespace MWGui // effect box can have variable width -> variable left coordinate int effectsDx = 0; if (!mMinimapBox->getVisible ()) - effectsDx = (viewSize.width - mMinimapBoxBaseRight) - (viewSize.width - mEffectBoxBaseRight); + effectsDx = mEffectBoxBaseRight - mMinimapBoxBaseRight; mMapVisible = mMinimapBox->getVisible (); if (!mMapVisible) diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index ba300accb..366da6439 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -583,7 +583,7 @@ namespace MWRender } mOverlayImage->copySubImage(imageDest.mX, imageDest.mY, 0, imageDest.mImage); - it = mPendingImageDest.erase(it); + mPendingImageDest.erase(it); return true; } } diff --git a/components/terrain/terraindrawable.cpp b/components/terrain/terraindrawable.cpp index 0d82be4ff..746534abb 100644 --- a/components/terrain/terraindrawable.cpp +++ b/components/terrain/terraindrawable.cpp @@ -78,7 +78,7 @@ void TerrainDrawable::cull(osgUtil::CullVisitor *cv) osg::RefMatrix& matrix = *cv->getModelViewMatrix(); - if (cv->getComputeNearFarMode() && bb.valid()) + if (cv->getComputeNearFarMode() != osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR && bb.valid()) { if (!cv->updateCalculatedNearFar(matrix, *this, false)) return; From a80ee7a76a6ad94bc17fbda46b53affeb96968a6 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 14:43:00 +0400 Subject: [PATCH 017/117] Avoid possible memory leak in the mInterMessageBoxe field --- apps/openmw/mwgui/messagebox.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index d64ec9c37..5bd8ceb5a 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -28,10 +28,7 @@ namespace MWGui MessageBoxManager::~MessageBoxManager () { - for (MessageBox* messageBox : mMessageBoxes) - { - delete messageBox; - } + MessageBoxManager::clear(); } int MessageBoxManager::getMessagesCount() From 24f8a2db27acf5ecb8c38b8022f4ba4006aee4d8 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Jan 2021 14:07:30 +0100 Subject: [PATCH 018/117] Use perfect forwarding in Sound_Buffer ctor --- apps/openmw/mwsound/sound_buffer.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwsound/sound_buffer.hpp b/apps/openmw/mwsound/sound_buffer.hpp index e62392300..02e3c499b 100644 --- a/apps/openmw/mwsound/sound_buffer.hpp +++ b/apps/openmw/mwsound/sound_buffer.hpp @@ -25,8 +25,9 @@ namespace MWSound class Sound_Buffer { public: - Sound_Buffer(std::string resname, float volume, float mindist, float maxdist) - : mResourceName(std::move(resname)), mVolume(volume), mMinDist(mindist), mMaxDist(maxdist) + template + Sound_Buffer(T&& resname, float volume, float mindist, float maxdist) + : mResourceName(std::forward(resname)), mVolume(volume), mMinDist(mindist), mMaxDist(maxdist) {} const std::string& getResourceName() const noexcept { return mResourceName; } From 90c8e77e2ce75860ed7d674e3e068e164cec7a39 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Jan 2021 14:08:54 +0100 Subject: [PATCH 019/117] Remove redundant default Sound_Buffer fields initialization --- apps/openmw/mwsound/sound_buffer.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwsound/sound_buffer.hpp b/apps/openmw/mwsound/sound_buffer.hpp index 02e3c499b..61d569e13 100644 --- a/apps/openmw/mwsound/sound_buffer.hpp +++ b/apps/openmw/mwsound/sound_buffer.hpp @@ -42,9 +42,9 @@ namespace MWSound private: std::string mResourceName; - float mVolume = 0; - float mMinDist = 0; - float mMaxDist = 0; + float mVolume; + float mMinDist; + float mMaxDist; Sound_Handle mHandle = nullptr; std::size_t mUses = 0; From e4cce88142f383b8ef9dd086c23a545db57ce121 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Jan 2021 14:09:27 +0100 Subject: [PATCH 020/117] Replace std::tie by structured binding --- apps/openmw/mwsound/sound_buffer.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/openmw/mwsound/sound_buffer.cpp b/apps/openmw/mwsound/sound_buffer.cpp index 0e25ff601..cb71cb56d 100644 --- a/apps/openmw/mwsound/sound_buffer.cpp +++ b/apps/openmw/mwsound/sound_buffer.cpp @@ -82,9 +82,7 @@ namespace MWSound if (sfx->getHandle() == nullptr) { - Sound_Handle handle; - size_t size; - std::tie(handle, size) = mOutput->loadSound(sfx->getResourceName()); + auto [handle, size] = mOutput->loadSound(sfx->getResourceName()); if (handle == nullptr) return {}; From a6aba83741218241db323590cb2042232b2e7f9d Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Jan 2021 14:11:49 +0100 Subject: [PATCH 021/117] Remove redundant SoundManager::lookupSound and loadSound --- apps/openmw/mwsound/sound_buffer.hpp | 4 ++++ apps/openmw/mwsound/soundmanagerimp.cpp | 28 +++++++------------------ apps/openmw/mwsound/soundmanagerimp.hpp | 3 --- 3 files changed, 11 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwsound/sound_buffer.hpp b/apps/openmw/mwsound/sound_buffer.hpp index 61d569e13..5c45ac08a 100644 --- a/apps/openmw/mwsound/sound_buffer.hpp +++ b/apps/openmw/mwsound/sound_buffer.hpp @@ -60,8 +60,12 @@ namespace MWSound ~SoundBufferPool(); + /// Lookup a soundId for its sound data (resource name, local volume, + /// minRange, and maxRange) Sound_Buffer* lookup(const std::string& soundId) const; + /// Lookup a soundId for its sound data (resource name, local volume, + /// minRange, and maxRange), and ensure it's ready for use. Sound_Buffer* load(const std::string& soundId); void use(Sound_Buffer& sfx) diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 03ad58088..44465e5a7 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -120,20 +120,6 @@ namespace MWSound return DecoderPtr(new DEFAULT_DECODER (mVFS)); } - // Lookup a soundId for its sound data (resource name, local volume, - // minRange, and maxRange) - Sound_Buffer *SoundManager::lookupSound(const std::string &soundId) const - { - return mSoundBuffers.lookup(soundId); - } - - // Lookup a soundId for its sound data (resource name, local volume, - // minRange, and maxRange), and ensure it's ready for use. - Sound_Buffer *SoundManager::loadSound(const std::string &soundId) - { - return mSoundBuffers.load(soundId); - } - DecoderPtr SoundManager::loadVoice(const std::string &voicefile) { try @@ -509,7 +495,7 @@ namespace MWSound if(!mOutput->isInitialized()) return nullptr; - Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); + Sound_Buffer *sfx = mSoundBuffers.load(Misc::StringUtils::lowerCase(soundId)); if(!sfx) return nullptr; // Only one copy of given sound can be played at time, so stop previous copy @@ -545,7 +531,7 @@ namespace MWSound return nullptr; // Look up the sound in the ESM data - Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); + Sound_Buffer *sfx = mSoundBuffers.load(Misc::StringUtils::lowerCase(soundId)); if(!sfx) return nullptr; // Only one copy of given sound can be played at time on ptr, so stop previous copy @@ -597,7 +583,7 @@ namespace MWSound return nullptr; // Look up the sound in the ESM data - Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); + Sound_Buffer *sfx = mSoundBuffers.load(Misc::StringUtils::lowerCase(soundId)); if(!sfx) return nullptr; SoundPtr sound = getSoundRef(); @@ -645,7 +631,7 @@ namespace MWSound if(!mOutput->isInitialized()) return; - Sound_Buffer *sfx = lookupSound(Misc::StringUtils::lowerCase(soundId)); + Sound_Buffer *sfx = mSoundBuffers.lookup(Misc::StringUtils::lowerCase(soundId)); if (!sfx) return; stopSound(sfx, ptr); @@ -697,7 +683,7 @@ namespace MWSound SoundMap::iterator snditer = mActiveSounds.find(ptr); if(snditer != mActiveSounds.end()) { - Sound_Buffer *sfx = lookupSound(Misc::StringUtils::lowerCase(soundId)); + Sound_Buffer *sfx = mSoundBuffers.lookup(Misc::StringUtils::lowerCase(soundId)); if (sfx == nullptr) return; for(SoundBufferRefPair &sndbuf : snditer->second) @@ -713,7 +699,7 @@ namespace MWSound SoundMap::const_iterator snditer = mActiveSounds.find(ptr); if(snditer != mActiveSounds.end()) { - Sound_Buffer *sfx = lookupSound(Misc::StringUtils::lowerCase(soundId)); + Sound_Buffer *sfx = mSoundBuffers.lookup(Misc::StringUtils::lowerCase(soundId)); return std::find_if(snditer->second.cbegin(), snditer->second.cend(), [this,sfx](const SoundBufferRefPair &snd) -> bool { return snd.second == sfx && mOutput->isSoundPlaying(snd.first.get()); } @@ -826,7 +812,7 @@ namespace MWSound bool soundIdChanged = false; - Sound_Buffer* sfx = lookupSound(update.mId); + Sound_Buffer* sfx = mSoundBuffers.lookup(update.mId); if (mLastCell != cell) { const auto snditer = mActiveSounds.find(MWWorld::ConstPtr()); diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 21004dc94..6b9de800f 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -99,9 +99,6 @@ namespace MWSound Sound_Buffer *insertSound(const std::string &soundId, const ESM::Sound *sound); - Sound_Buffer *lookupSound(const std::string &soundId) const; - Sound_Buffer *loadSound(const std::string &soundId); - // returns a decoder to start streaming, or nullptr if the sound was not found DecoderPtr loadVoice(const std::string &voicefile); From 33da0af1d1760f5cd6b79a853aa0a6fe3fe848ff Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 18:25:48 +0400 Subject: [PATCH 022/117] Use explicit calls for virtual methods in constructors --- apps/openmw/mwrender/water.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index b9018e0a2..e786ce937 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -135,7 +135,7 @@ public: mClipNodeTransform = new osg::Group; mClipNodeTransform->addCullCallback(new FlipCallback(&mPlane)); - addChild(mClipNodeTransform); + osg::Group::addChild(mClipNodeTransform); mClipNode = new osg::ClipNode; @@ -240,7 +240,7 @@ public: setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); setReferenceFrame(osg::Camera::RELATIVE_RF); setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); - setName("RefractionCamera"); + osg::Camera::setName("RefractionCamera"); setCullCallback(new InheritViewPointCallback); setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); @@ -261,7 +261,7 @@ public: getOrCreateStateSet()->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); mClipCullNode = new ClipCullNode; - addChild(mClipCullNode); + osg::Camera::addChild(mClipCullNode); mRefractionTexture = new osg::Texture2D; mRefractionTexture->setTextureSize(rttSize, rttSize); @@ -335,7 +335,7 @@ public: setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); setReferenceFrame(osg::Camera::RELATIVE_RF); setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); - setName("ReflectionCamera"); + osg::Camera::setName("ReflectionCamera"); setCullCallback(new InheritViewPointCallback); setInterior(isInterior); @@ -364,7 +364,7 @@ public: getOrCreateStateSet()->setAttributeAndModes(frontFace, osg::StateAttribute::ON); mClipCullNode = new ClipCullNode; - addChild(mClipCullNode); + osg::Camera::addChild(mClipCullNode); SceneUtil::ShadowManager::disableShadowsForStateSet(getOrCreateStateSet()); } From 80ee1b55ea636184ec38718fa2d7dd928e1c5306 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 18:28:26 +0400 Subject: [PATCH 023/117] Protect assignment operator from this == &src case --- apps/openmw/mwworld/inventorystore.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 38007c1cb..fd0595703 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -120,6 +120,9 @@ MWWorld::InventoryStore::InventoryStore (const InventoryStore& store) MWWorld::InventoryStore& MWWorld::InventoryStore::operator= (const InventoryStore& store) { + if (this == &store) + return *this; + mListener = store.mListener; mInventoryListener = store.mInventoryListener; mMagicEffects = store.mMagicEffects; From a2d8a0b61a18da0c85a55930d480fbec8fb202dd Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 9 Jan 2021 14:44:15 +0000 Subject: [PATCH 024/117] engine.cpp typos --- apps/openmw/engine.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 8f23f710d..6f501e5ad 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -589,8 +589,8 @@ void OMW::Engine::createWindow(Settings::Manager& settings) Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->green << " bit green channel."; if (traits->blue < 8) Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->blue << " bit blue channel."; - if (traits->depth < 8) - Log(Debug::Warning) << "Warning: Framebuffer only has " << traits->red << " bits of depth precision."; + if (traits->depth < 24) + Log(Debug::Warning) << "Warning: Framebuffer only has " << traits->depth << " bits of depth precision."; traits->alpha = 0; // set to 0 to stop ScreenCaptureHandler reading the alpha channel } From 874348fb4681d4608d403e984ed9363972a19f4f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 19:19:38 +0400 Subject: [PATCH 025/117] Remove redundant code --- apps/openmw/mwmechanics/aiwander.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 68bcddf22..8e718061e 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -99,7 +99,6 @@ namespace MWMechanics { AiPackage::Options options; options.mUseVariableSpeed = true; - options.mRepeat = false; return options; } From 50e4600b160174024654a8d4fe8f82b0bb1cc99b Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 20:00:51 +0400 Subject: [PATCH 026/117] Reduce code duplication --- apps/opencs/view/world/util.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index ba9f40847..58d3d49e4 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -261,16 +261,10 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO return dsb; } + /// \todo implement size limit. QPlainTextEdit does not support a size limit. case CSMWorld::ColumnBase::Display_LongString: - { - QPlainTextEdit *edit = new QPlainTextEdit(parent); - edit->setUndoRedoEnabled (false); - return edit; - } - case CSMWorld::ColumnBase::Display_LongString256: { - /// \todo implement size limit. QPlainTextEdit does not support a size limit. QPlainTextEdit *edit = new QPlainTextEdit(parent); edit->setUndoRedoEnabled (false); return edit; From 1930f8f37d45c246186d996fc9edc30c74d5fab3 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 20:03:12 +0400 Subject: [PATCH 027/117] Fix copy-paste error --- apps/opencs/model/world/refidadapterimp.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index a73f76af9..0a29afcad 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -1858,18 +1858,18 @@ namespace CSMWorld break; // always save case 16: if (content.mType == ESM::AI_Travel) - content.mTravel.mZ = value.toFloat(); + content.mTravel.mX = value.toFloat(); else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) - content.mTarget.mZ = value.toFloat(); + content.mTarget.mX = value.toFloat(); else return; // return without saving break; // always save case 17: if (content.mType == ESM::AI_Travel) - content.mTravel.mZ = value.toFloat(); + content.mTravel.mY = value.toFloat(); else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) - content.mTarget.mZ = value.toFloat(); + content.mTarget.mY = value.toFloat(); else return; // return without saving From 564a0d7d559622d4d8ff7baebdbf9e4c950eb160 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 9 Jan 2021 18:25:46 +0100 Subject: [PATCH 028/117] Don't nuke fog when bounds have changed --- CHANGELOG.md | 1 + apps/openmw/mwrender/localmap.cpp | 126 +++++++++++++++++------------- 2 files changed, 74 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa3572282..3dce266d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ Bug #5441: Enemies can't push a player character when in critical strike stance Bug #5451: Magic projectiles don't disappear with the caster Bug #5452: Autowalk is being included in savegames + Bug #5469: Local map is reset when re-entering certain cells Bug #5472: Mistify mod causes CTD in 0.46 on Mac Bug #5479: NPCs who should be walking around town are standing around without walking Bug #5484: Zero value items shouldn't be able to be bought or sold for 1 gold diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 401e21ae4..5fa1a0e29 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -65,6 +65,15 @@ namespace return val*val; } + std::pair divideIntoSegments(const osg::BoundingBox& bounds, float mapSize) + { + osg::Vec2f min(bounds.xMin(), bounds.yMin()); + osg::Vec2f max(bounds.xMax(), bounds.yMax()); + osg::Vec2f length = max - min; + const int segsX = static_cast(std::ceil(length.x() / mapSize)); + const int segsY = static_cast(std::ceil(length.y() / mapSize)); + return {segsX, segsY}; + } } namespace MWRender @@ -127,12 +136,7 @@ void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) } else { - // FIXME: segmenting code duplicated from requestMap - osg::Vec2f min(mBounds.xMin(), mBounds.yMin()); - osg::Vec2f max(mBounds.xMax(), mBounds.yMax()); - osg::Vec2f length = max-min; - const int segsX = static_cast(std::ceil(length.x() / mMapWorldSize)); - const int segsY = static_cast(std::ceil(length.y() / mMapWorldSize)); + auto segments = divideIntoSegments(mBounds, mMapWorldSize); std::unique_ptr fog (new ESM::FogState()); @@ -142,11 +146,11 @@ void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) fog->mBounds.mMaxY = mBounds.yMax(); fog->mNorthMarkerAngle = mAngle; - fog->mFogTextures.reserve(segsX*segsY); + fog->mFogTextures.reserve(segments.first * segments.second); - for (int x=0; x> segmentMappings; if (cell->getFog()) { ESM::FogState* fog = cell->getFog(); - osg::Vec3f newMin (fog->mBounds.mMinX, fog->mBounds.mMinY, zMin); - osg::Vec3f newMax (fog->mBounds.mMaxX, fog->mBounds.mMaxY, zMax); - - osg::Vec3f minDiff = newMin - mBounds._min; - osg::Vec3f maxDiff = newMax - mBounds._max; - - if (std::abs(minDiff.x()) > padding || std::abs(minDiff.y()) > padding - || std::abs(maxDiff.x()) > padding || std::abs(maxDiff.y()) > padding - || std::abs(mAngle - fog->mNorthMarkerAngle) > osg::DegreesToRadians(5.f)) + if (std::abs(mAngle - fog->mNorthMarkerAngle) < osg::DegreesToRadians(5.f)) { - // Nuke it - cellHasValidFog = false; - } - else - { - // Looks sane, use it - mBounds = osg::BoundingBox(newMin, newMax); + // Expand mBounds so the saved textures fit the same grid + int xOffset = 0; + int yOffset = 0; + if(fog->mBounds.mMinX < mBounds.xMin()) + { + mBounds.xMin() = fog->mBounds.mMinX; + } + else if(fog->mBounds.mMinX > mBounds.xMin()) + { + float diff = fog->mBounds.mMinX - mBounds.xMin(); + xOffset += diff / mMapWorldSize; + xOffset++; + mBounds.xMin() = fog->mBounds.mMinX - xOffset * mMapWorldSize; + } + if(fog->mBounds.mMinY < mBounds.yMin()) + { + mBounds.yMin() = fog->mBounds.mMinY; + } + else if(fog->mBounds.mMinY > mBounds.yMin()) + { + float diff = fog->mBounds.mMinY - mBounds.yMin(); + yOffset += diff / mMapWorldSize; + yOffset++; + mBounds.yMin() = fog->mBounds.mMinY - yOffset * mMapWorldSize; + } + mBounds.xMax() = std::max(mBounds.xMax(), fog->mBounds.mMaxX); + mBounds.yMax() = std::max(mBounds.yMax(), fog->mBounds.mMaxY); + + if(xOffset != 0 || yOffset != 0) + Log(Debug::Warning) << "Warning: expanding fog by " << xOffset << ", " << yOffset; + + const auto& textures = fog->mFogTextures; + segmentMappings.reserve(textures.size()); + osg::BoundingBox savedBounds{ + fog->mBounds.mMinX, fog->mBounds.mMinY, 0, + fog->mBounds.mMaxX, fog->mBounds.mMaxY, 0 + }; + auto segments = divideIntoSegments(savedBounds, mMapWorldSize); + for (int x = 0; x < segments.first; ++x) + for (int y = 0; y < segments.second; ++y) + segmentMappings.emplace_back(std::make_pair(x + xOffset, y + yOffset)); + mAngle = fog->mNorthMarkerAngle; - cellHasValidFog = true; } } osg::Vec2f min(mBounds.xMin(), mBounds.yMin()); - osg::Vec2f max(mBounds.xMax(), mBounds.yMax()); - osg::Vec2f length = max-min; + osg::Vec2f center(mBounds.center().x(), mBounds.center().y()); + osg::Quat cameraOrient (mAngle, osg::Vec3d(0,0,-1)); - osg::Vec2f center(bounds.center().x(), bounds.center().y()); - - // divide into segments - const int segsX = static_cast(std::ceil(length.x() / mMapWorldSize)); - const int segsY = static_cast(std::ceil(length.y() / mMapWorldSize)); - - int i = 0; - for (int x=0; xgetFog(); - - // We are using the same bounds and angle as we were using when the textures were originally made. Segments should come out the same. - if (i >= int(fog->mFogTextures.size())) + if(segmentMappings[index] == coords) { - Log(Debug::Warning) << "Warning: fog texture count mismatch"; + ESM::FogState* fog = cell->getFog(); + segment.loadFogOfWar(fog->mFogTextures[index]); + loaded = true; break; } - - segment.loadFogOfWar(fog->mFogTextures[i]); } + if(!loaded) + segment.initFogOfWar(); } - ++i; } } } From ad101de733b383c4f708079c31abe42fedc840ec Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 22:58:54 +0400 Subject: [PATCH 029/117] Merge declaration and initialization --- apps/opencs/view/render/terraintexturemode.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/render/terraintexturemode.cpp b/apps/opencs/view/render/terraintexturemode.cpp index 09d6b135a..f4a3f461c 100644 --- a/apps/opencs/view/render/terraintexturemode.cpp +++ b/apps/opencs/view/render/terraintexturemode.cpp @@ -427,10 +427,8 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe { if (i_cell == cellX && j_cell == cellY && abs(i-xHitInCell) < r && abs(j-yHitInCell) < r) { - int distanceX(0); - int distanceY(0); - distanceX = abs(i-xHitInCell); - distanceY = abs(j-yHitInCell); + int distanceX = abs(i-xHitInCell); + int distanceY = abs(j-yHitInCell); float distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2))); float rf = static_cast(mBrushSize) / 2; if (distance < rf) newTerrain[j*landTextureSize+i] = brushInt; From 9bc687e20992cf5b7941343d7bce379f2c66b252 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 9 Jan 2021 20:56:20 +0100 Subject: [PATCH 030/117] Avoid a rare but possible deadlock around mCollisionWorldMutex. What happened is that the last handle to an Actor shared_ptr was a promoted weak_ptr. When the shared_ptr goes out of scope, the Actor dtor is invoked. That involves removing the Actor collision object after exclusively locking mCollisionWorldMutex. In this case, the lock was already held in the outter scope of the promoted weak_ptr. Reduce the scope of the mCollisionWorldMutex to never encompass the lifetime of a promoted weak_ptr. --- apps/openmw/mwphysics/mtphysics.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 0d1e5962a..c77bc6acd 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -357,7 +357,6 @@ namespace MWPhysics { if (!mDeferAabbUpdate || immediate) { - std::unique_lock lock(mCollisionWorldMutex); updatePtrAabb(ptr); } else @@ -410,7 +409,7 @@ namespace MWPhysics void PhysicsTaskScheduler::updateAabbs() { - std::scoped_lock lock(mCollisionWorldMutex, mUpdateAabbMutex); + std::scoped_lock lock(mUpdateAabbMutex); std::for_each(mUpdateAabb.begin(), mUpdateAabb.end(), [this](const std::weak_ptr& ptr) { updatePtrAabb(ptr); }); mUpdateAabb.clear(); @@ -420,6 +419,7 @@ namespace MWPhysics { if (const auto p = ptr.lock()) { + std::scoped_lock lock(mCollisionWorldMutex); if (const auto actor = std::dynamic_pointer_cast(p)) { actor->updateCollisionObjectPosition(); @@ -451,9 +451,11 @@ namespace MWPhysics int job = 0; while (mRemainingSteps && (job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) { - MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet); if(const auto actor = mActorsFrameData[job].mActor.lock()) + { + MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet); MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData); + } } mPostStepBarrier->wait(); @@ -478,13 +480,13 @@ namespace MWPhysics void PhysicsTaskScheduler::updateActorsPositions() { - std::unique_lock lock(mCollisionWorldMutex); for (auto& actorData : mActorsFrameData) { if(const auto actor = actorData.mActor.lock()) { if (actor->setPosition(actorData.mPosition)) { + std::scoped_lock lock(mCollisionWorldMutex); actor->updateCollisionObjectPosition(); mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); } From 60f66f5e29a43f8ef566d3855b5abc699e24527a Mon Sep 17 00:00:00 2001 From: fredzio Date: Thu, 7 Jan 2021 11:00:57 +0100 Subject: [PATCH 031/117] Remove never used parameter from CharacterController:update() --- apps/openmw/mwmechanics/character.cpp | 13 ++++++------- apps/openmw/mwmechanics/character.hpp | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index b9bfe6bc0..5c50c547c 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1898,7 +1898,7 @@ void CharacterController::updateAnimQueue() mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1); } -void CharacterController::update(float duration, bool animationOnly) +void CharacterController::update(float duration) { MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Class &cls = mPtr.getClass(); @@ -2386,10 +2386,10 @@ void CharacterController::update(float duration, bool animationOnly) world->rotateObject(mPtr, rot.x(), rot.y(), 0.0f, true); } - if (!animationOnly && !mMovementAnimationControlled) + if (!mMovementAnimationControlled) world->queueMovement(mPtr, vec); } - else if (!animationOnly) + else // We must always queue movement, even if there is none, to apply gravity. world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); @@ -2414,8 +2414,7 @@ void CharacterController::update(float duration, bool animationOnly) playDeath(1.f, mDeathState); } // We must always queue movement, even if there is none, to apply gravity. - if (!animationOnly) - world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); + world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); } bool isPersist = isPersistentAnimPlaying(); @@ -2452,10 +2451,10 @@ void CharacterController::update(float duration, bool animationOnly) { moved.z() = 1.0; } - } + } // Update movement - if(!animationOnly && mMovementAnimationControlled && mPtr.getClass().isActor()) + if(mMovementAnimationControlled && mPtr.getClass().isActor()) world->queueMovement(mPtr, moved); mSkipAnim = false; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 2308ba971..0821b3225 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -248,7 +248,7 @@ public: void updatePtr(const MWWorld::Ptr &ptr); - void update(float duration, bool animationOnly=false); + void update(float duration); bool onOpen(); void onClose(); From 313e8959129c05145a16194c0893282cb27523ad Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sat, 9 Jan 2021 21:35:07 +0000 Subject: [PATCH 032/117] [OpenMW-CS] Cube and sphere instance selection --- CHANGELOG.md | 1 + CHANGELOG_PR.md | 2 +- apps/opencs/model/prefs/state.cpp | 15 + apps/opencs/view/render/cell.cpp | 47 +++ apps/opencs/view/render/cell.hpp | 7 + apps/opencs/view/render/instancedragmodes.hpp | 18 + apps/opencs/view/render/instancemode.cpp | 106 +++++- apps/opencs/view/render/instancemode.hpp | 16 +- .../view/render/instanceselectionmode.cpp | 347 +++++++++++++++++- .../view/render/instanceselectionmode.hpp | 32 +- .../view/render/pagedworldspacewidget.cpp | 16 + .../view/render/pagedworldspacewidget.hpp | 5 + apps/opencs/view/render/selectionmode.cpp | 21 +- .../view/render/unpagedworldspacewidget.cpp | 10 + .../view/render/unpagedworldspacewidget.hpp | 4 + apps/opencs/view/render/worldspacewidget.hpp | 5 + 16 files changed, 627 insertions(+), 25 deletions(-) create mode 100644 apps/opencs/view/render/instancedragmodes.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index ce0cce952..fa33b6647 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -94,6 +94,7 @@ Feature #2386: Distant Statics in the form of Object Paging Feature #2404: Levelled List can not be placed into a container Feature #2686: Timestamps in openmw.log + Feature #3171: OpenMW-CS: Instance drag selection Feature #4894: Consider actors as obstacles for pathfinding Feature #5043: Head Bobbing Feature #5199: Improve Scene Colors diff --git a/CHANGELOG_PR.md b/CHANGELOG_PR.md index 69ad0cc1b..fdf27dc93 100644 --- a/CHANGELOG_PR.md +++ b/CHANGELOG_PR.md @@ -21,7 +21,7 @@ New Features: - Basics of Collada animations are now supported via osgAnimation plugin (#5456) New Editor Features: -- ? +- Instance selection modes are now implemented (centred cube, corner-dragged cube, sphere) with four user-configurable actions (select only, add to selection, remove from selection, invert selection) (#3171) Bug Fixes: - NiParticleColorModifier in NIF files is now properly handled which solves issues regarding particle effects, e.g., smoke and fire (#1952, #3676) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 588be9ccb..32b1ee33f 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -247,6 +247,15 @@ void CSMPrefs::State::declare() EnumValues landeditOutsideVisibleCell; landeditOutsideVisibleCell.add (showAndLandEdit).add (dontLandEdit); + EnumValue SelectOnly ("Select only"); + EnumValue SelectAdd ("Add to selection"); + EnumValue SelectRemove ("Remove from selection"); + EnumValue selectInvert ("Invert selection"); + EnumValues primarySelectAction; + primarySelectAction.add (SelectOnly).add (SelectAdd).add (SelectRemove).add (selectInvert); + EnumValues secondarySelectAction; + secondarySelectAction.add (SelectOnly).add (SelectAdd).add (SelectRemove).add (selectInvert); + declareCategory ("3D Scene Editing"); declareInt ("distance", "Drop Distance", 50). setTooltip ("If an instance drop can not be placed against another object at the " @@ -276,6 +285,12 @@ void CSMPrefs::State::declare() declareBool ("open-list-view", "Open displays list view", false). setTooltip ("When opening a reference from the scene view, it will open the" " instance list view instead of the individual instance record view."); + declareEnum ("primary-select-action", "Action for primary select", SelectOnly). + setTooltip("Selection can be chosen between select only, add to selection, remove from selection and invert selection."). + addValues (primarySelectAction); + declareEnum ("secondary-select-action", "Action for secondary select", SelectAdd). + setTooltip("Selection can be chosen between select only, add to selection, remove from selection and invert selection."). + addValues (secondarySelectAction); declareCategory ("Key Bindings"); diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 75d83cf63..2502dc1fd 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -1,5 +1,7 @@ #include "cell.hpp" +#include + #include #include #include @@ -25,6 +27,7 @@ #include "pathgrid.hpp" #include "terrainstorage.hpp" #include "object.hpp" +#include "instancedragmodes.hpp" namespace CSVRender { @@ -496,6 +499,50 @@ void CSVRender::Cell::selectAllWithSameParentId (int elementMask) } } +void CSVRender::Cell::handleSelectDrag(Object* object, DragMode dragMode) +{ + if (dragMode == DragMode_Select_Only || dragMode == DragMode_Select_Add) + object->setSelected(true); + + else if (dragMode == DragMode_Select_Remove) + object->setSelected(false); + + else if (dragMode == DragMode_Select_Invert) + object->setSelected (!object->getSelected()); +} + +void CSVRender::Cell::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) +{ + for (auto& object : mObjects) + { + if (dragMode == DragMode_Select_Only) object.second->setSelected (false); + + if ( ( object.second->getPosition().pos[0] > pointA[0] && object.second->getPosition().pos[0] < pointB[0] ) || + ( object.second->getPosition().pos[0] > pointB[0] && object.second->getPosition().pos[0] < pointA[0] )) + { + if ( ( object.second->getPosition().pos[1] > pointA[1] && object.second->getPosition().pos[1] < pointB[1] ) || + ( object.second->getPosition().pos[1] > pointB[1] && object.second->getPosition().pos[1] < pointA[1] )) + { + if ( ( object.second->getPosition().pos[2] > pointA[2] && object.second->getPosition().pos[2] < pointB[2] ) || + ( object.second->getPosition().pos[2] > pointB[2] && object.second->getPosition().pos[2] < pointA[2] )) + handleSelectDrag(object.second, dragMode); + } + + } + } +} + +void CSVRender::Cell::selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) +{ + for (auto& object : mObjects) + { + if (dragMode == DragMode_Select_Only) object.second->setSelected (false); + + float distanceFromObject = (point - object.second->getPosition().asVec3()).length(); + if (distanceFromObject < distance) handleSelectDrag(object.second, dragMode); + } +} + void CSVRender::Cell::setCellArrows (int mask) { for (int i=0; i<4; ++i) diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 281ac6735..5998a4ee6 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -10,6 +10,7 @@ #include "../../model/world/cellcoordinates.hpp" #include "terrainstorage.hpp" +#include "instancedragmodes.hpp" class QModelIndex; @@ -152,6 +153,12 @@ namespace CSVRender // already selected void selectAllWithSameParentId (int elementMask); + void handleSelectDrag(Object* object, DragMode dragMode); + + void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode); + + void selectWithinDistance(const osg::Vec3d& pointA, float distance, DragMode dragMode); + void setCellArrows (int mask); /// \brief Set marker for this cell. diff --git a/apps/opencs/view/render/instancedragmodes.hpp b/apps/opencs/view/render/instancedragmodes.hpp new file mode 100644 index 000000000..01547545a --- /dev/null +++ b/apps/opencs/view/render/instancedragmodes.hpp @@ -0,0 +1,18 @@ +#ifndef CSV_WIDGET_INSTANCEDRAGMODES_H +#define CSV_WIDGET_INSTANCEDRAGMODES_H + +namespace CSVRender +{ + enum DragMode + { + DragMode_None, + DragMode_Move, + DragMode_Rotate, + DragMode_Scale, + DragMode_Select_Only, + DragMode_Select_Add, + DragMode_Select_Remove, + DragMode_Select_Invert + }; +} +#endif diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 19018fae6..4f6759cdb 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -96,6 +96,33 @@ osg::Vec3f CSVRender::InstanceMode::getScreenCoords(const osg::Vec3f& pos) return pos * combined; } +osg::Vec3f CSVRender::InstanceMode::getProjectionSpaceCoords(const osg::Vec3f& pos) +{ + osg::Matrix viewMatrix = getWorldspaceWidget().getCamera()->getViewMatrix(); + osg::Matrix projMatrix = getWorldspaceWidget().getCamera()->getProjectionMatrix(); + osg::Matrix combined = viewMatrix * projMatrix; + + return pos * combined; +} + +osg::Vec3f CSVRender::InstanceMode::getMousePlaneCoords(const QPoint& point, const osg::Vec3d& dragStart) +{ + osg::Matrix viewMatrix; + viewMatrix.invert(getWorldspaceWidget().getCamera()->getViewMatrix()); + osg::Matrix projMatrix; + projMatrix.invert(getWorldspaceWidget().getCamera()->getProjectionMatrix()); + osg::Matrix combined = projMatrix * viewMatrix; + + /* calculate viewport normalized coordinates + note: is there a reason to use getCamera()->getViewport()->computeWindowMatrix() instead? */ + float x = (point.x() * 2) / getWorldspaceWidget().getCamera()->getViewport()->width() - 1.0f; + float y = 1.0f - (point.y() * 2) / getWorldspaceWidget().getCamera()->getViewport()->height(); + + osg::Vec3f mousePlanePoint = osg::Vec3f(x, y, dragStart.z()) * combined; + + return mousePlanePoint; +} + CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, osg::ref_ptr parentNode, QWidget *parent) : EditMode (worldspaceWidget, QIcon (":scenetoolbar/editing-instance"), Mask_Reference | Mask_Terrain, "Instance editing", parent), mSubMode (nullptr), mSubModeId ("move"), mSelectionMode (nullptr), mDragMode (DragMode_None), @@ -146,7 +173,7 @@ void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar) } if (!mSelectionMode) - mSelectionMode = new InstanceSelectionMode (toolbar, getWorldspaceWidget()); + mSelectionMode = new InstanceSelectionMode (toolbar, getWorldspaceWidget(), mParentNode); mDragMode = DragMode_None; @@ -322,6 +349,42 @@ bool CSVRender::InstanceMode::secondaryEditStartDrag (const QPoint& pos) return false; } +bool CSVRender::InstanceMode::primarySelectStartDrag (const QPoint& pos) +{ + if (mDragMode!=DragMode_None || mLocked) + return false; + + std::string primarySelectAction = CSMPrefs::get()["3D Scene Editing"]["primary-select-action"].toString(); + + if ( primarySelectAction == "Select only" ) mDragMode = DragMode_Select_Only; + else if ( primarySelectAction == "Add to selection" ) mDragMode = DragMode_Select_Add; + else if ( primarySelectAction == "Remove from selection" ) mDragMode = DragMode_Select_Remove; + else if ( primarySelectAction == "Invert selection" ) mDragMode = DragMode_Select_Invert; + + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + mSelectionMode->setDragStart(hit.worldPos); + + return true; +} + +bool CSVRender::InstanceMode::secondarySelectStartDrag (const QPoint& pos) +{ + if (mDragMode!=DragMode_None || mLocked) + return false; + + std::string secondarySelectAction = CSMPrefs::get()["3D Scene Editing"]["secondary-select-action"].toString(); + + if ( secondarySelectAction == "Select only" ) mDragMode = DragMode_Select_Only; + else if ( secondarySelectAction == "Add to selection" ) mDragMode = DragMode_Select_Add; + else if ( secondarySelectAction == "Remove from selection" ) mDragMode = DragMode_Select_Remove; + else if ( secondarySelectAction == "Invert selection" ) mDragMode = DragMode_Select_Invert; + + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + mSelectionMode->setDragStart(hit.worldPos); + + return true; +} + void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor) { osg::Vec3f offset; @@ -432,6 +495,24 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou // Only uniform scaling is currently supported offset = osg::Vec3f(scale, scale, scale); } + else if (mSelectionMode->getCurrentId() == "cube-centre") + { + osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart())); + mSelectionMode->drawSelectionCubeCentre (mousePlanePoint); + return; + } + else if (mSelectionMode->getCurrentId() == "cube-corner") + { + osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart())); + mSelectionMode->drawSelectionCubeCorner (mousePlanePoint); + return; + } + else if (mSelectionMode->getCurrentId() == "sphere") + { + osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart())); + mSelectionMode->drawSelectionSphere (mousePlanePoint); + return; + } // Apply for (std::vector >::iterator iter (selection.begin()); iter!=selection.end(); ++iter) @@ -495,6 +576,22 @@ void CSVRender::InstanceMode::dragCompleted(const QPoint& pos) case DragMode_Move: description = "Move Instances"; break; case DragMode_Rotate: description = "Rotate Instances"; break; case DragMode_Scale: description = "Scale Instances"; break; + case DragMode_Select_Only : + handleSelectDrag(pos); + return; + break; + case DragMode_Select_Add : + handleSelectDrag(pos); + return; + break; + case DragMode_Select_Remove : + handleSelectDrag(pos); + return; + break; + case DragMode_Select_Invert : + handleSelectDrag(pos); + return; + break; case DragMode_None: break; } @@ -680,6 +777,13 @@ void CSVRender::InstanceMode::subModeChanged (const std::string& id) getWorldspaceWidget().setSubMode (getSubModeFromId (id), Mask_Reference); } +void CSVRender::InstanceMode::handleSelectDrag(const QPoint& pos) +{ + osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart())); + mSelectionMode->dragEnded (mousePlanePoint, mDragMode); + mDragMode = DragMode_None; +} + void CSVRender::InstanceMode::deleteSelectedInstances(bool active) { std::vector > selection = getWorldspaceWidget().getSelection (Mask_Reference); diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 32dd4ac67..0a4f2e478 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -9,6 +9,7 @@ #include #include "editmode.hpp" +#include "instancedragmodes.hpp" namespace CSVWidget { @@ -25,14 +26,6 @@ namespace CSVRender { Q_OBJECT - enum DragMode - { - DragMode_None, - DragMode_Move, - DragMode_Rotate, - DragMode_Scale - }; - enum DropMode { Collision, @@ -57,6 +50,9 @@ namespace CSVRender osg::Vec3f getSelectionCenter(const std::vector >& selection) const; osg::Vec3f getScreenCoords(const osg::Vec3f& pos); + osg::Vec3f getProjectionSpaceCoords(const osg::Vec3f& pos); + osg::Vec3f getMousePlaneCoords(const QPoint& point, const osg::Vec3d& dragStart); + void handleSelectDrag(const QPoint& pos); void dropInstance(DropMode dropMode, CSVRender::Object* object, float objectHeight); float getDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight); @@ -84,6 +80,10 @@ namespace CSVRender bool secondaryEditStartDrag (const QPoint& pos) override; + bool primarySelectStartDrag(const QPoint& pos) override; + + bool secondarySelectStartDrag(const QPoint& pos) override; + void drag (const QPoint& pos, int diffX, int diffY, double speedFactor) override; void dragCompleted(const QPoint& pos) override; diff --git a/apps/opencs/view/render/instanceselectionmode.cpp b/apps/opencs/view/render/instanceselectionmode.cpp index bf8ede0eb..9b5fb759c 100644 --- a/apps/opencs/view/render/instanceselectionmode.cpp +++ b/apps/opencs/view/render/instanceselectionmode.cpp @@ -2,17 +2,24 @@ #include #include +#include + +#include +#include +#include +#include #include "../../model/world/idtable.hpp" #include "../../model/world/commands.hpp" +#include "instancedragmodes.hpp" #include "worldspacewidget.hpp" #include "object.hpp" namespace CSVRender { - InstanceSelectionMode::InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget) - : SelectionMode(parent, worldspaceWidget, Mask_Reference) + InstanceSelectionMode::InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, osg::Group *cellNode) + : SelectionMode(parent, worldspaceWidget, Mask_Reference), mParentNode(cellNode) { mSelectSame = new QAction("Extend selection to instances with same object ID", this); mDeleteSelection = new QAction("Delete selected instances", this); @@ -21,6 +28,342 @@ namespace CSVRender connect(mDeleteSelection, SIGNAL(triggered()), this, SLOT(deleteSelection())); } + InstanceSelectionMode::~InstanceSelectionMode() + { + mParentNode->removeChild(mBaseNode); + } + + void InstanceSelectionMode::setDragStart(const osg::Vec3d& dragStart) + { + mDragStart = dragStart; + } + + const osg::Vec3d& InstanceSelectionMode::getDragStart() + { + return mDragStart; + } + + void InstanceSelectionMode::dragEnded(const osg::Vec3d& dragEndPoint, DragMode dragMode) + { + float dragDistance = (mDragStart - dragEndPoint).length(); + if (mBaseNode) mParentNode->removeChild (mBaseNode); + if (getCurrentId() == "cube-centre") + { + osg::Vec3d pointA(mDragStart[0] - dragDistance, mDragStart[1] - dragDistance, mDragStart[2] - dragDistance); + osg::Vec3d pointB(mDragStart[0] + dragDistance, mDragStart[1] + dragDistance, mDragStart[2] + dragDistance); + getWorldspaceWidget().selectInsideCube(pointA, pointB, dragMode); + } + else if (getCurrentId() == "cube-corner") + { + getWorldspaceWidget().selectInsideCube(mDragStart, dragEndPoint, dragMode); + } + else if (getCurrentId() == "sphere") + { + getWorldspaceWidget().selectWithinDistance(mDragStart, dragDistance, dragMode); + } + } + + void InstanceSelectionMode::drawSelectionCubeCentre(const osg::Vec3f& mousePlanePoint) + { + float dragDistance = (mDragStart - mousePlanePoint).length(); + drawSelectionCube(mDragStart, dragDistance); + } + + void InstanceSelectionMode::drawSelectionCubeCorner(const osg::Vec3f& mousePlanePoint) + { + drawSelectionBox(mDragStart, mousePlanePoint); + } + + void InstanceSelectionMode::drawSelectionBox(const osg::Vec3d& pointA, const osg::Vec3d& pointB) + { + if (mBaseNode) mParentNode->removeChild (mBaseNode); + mBaseNode = new osg::PositionAttitudeTransform; + mBaseNode->setPosition(pointA); + + osg::ref_ptr geometry (new osg::Geometry); + + osg::Vec3Array *vertices = new osg::Vec3Array; + vertices->push_back (osg::Vec3f (0.0f, 0.0f, 0.0f)); + vertices->push_back (osg::Vec3f (0.0f, 0.0f, pointB[2] - pointA[2])); + vertices->push_back (osg::Vec3f (0.0f, pointB[1] - pointA[1], 0.0f)); + vertices->push_back (osg::Vec3f (0.0f, pointB[1] - pointA[1], pointB[2] - pointA[2])); + + vertices->push_back (osg::Vec3f (pointB[0] - pointA[0], 0.0f, 0.0f)); + vertices->push_back (osg::Vec3f (pointB[0] - pointA[0], 0.0f, pointB[2] - pointA[2])); + vertices->push_back (osg::Vec3f (pointB[0] - pointA[0], pointB[1] - pointA[1], 0.0f)); + vertices->push_back (osg::Vec3f (pointB[0] - pointA[0], pointB[1] - pointA[1], pointB[2] - pointA[2])); + + geometry->setVertexArray (vertices); + + osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0); + + // top + primitives->push_back (2); + primitives->push_back (1); + primitives->push_back (0); + + primitives->push_back (3); + primitives->push_back (1); + primitives->push_back (2); + + // bottom + primitives->push_back (4); + primitives->push_back (5); + primitives->push_back (6); + + primitives->push_back (6); + primitives->push_back (5); + primitives->push_back (7); + + // sides + primitives->push_back (1); + primitives->push_back (4); + primitives->push_back (0); + + primitives->push_back (4); + primitives->push_back (1); + primitives->push_back (5); + + primitives->push_back (4); + primitives->push_back (2); + primitives->push_back (0); + + primitives->push_back (6); + primitives->push_back (2); + primitives->push_back (4); + + primitives->push_back (6); + primitives->push_back (3); + primitives->push_back (2); + + primitives->push_back (7); + primitives->push_back (3); + primitives->push_back (6); + + primitives->push_back (1); + primitives->push_back (3); + primitives->push_back (5); + + primitives->push_back (5); + primitives->push_back (3); + primitives->push_back (7); + + geometry->addPrimitiveSet (primitives); + + osg::Vec4Array *colours = new osg::Vec4Array; + + colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.5f, 0.2f)); + colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); + colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); + colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); + colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + + geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); + + geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setMode (GL_BLEND, osg::StateAttribute::ON); + geometry->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + + mBaseNode->addChild (geometry); + mParentNode->addChild(mBaseNode); + } + + void InstanceSelectionMode::drawSelectionCube(const osg::Vec3d& point, float radius) + { + if (mBaseNode) mParentNode->removeChild (mBaseNode); + mBaseNode = new osg::PositionAttitudeTransform; + mBaseNode->setPosition(point); + + osg::ref_ptr geometry (new osg::Geometry); + + osg::Vec3Array *vertices = new osg::Vec3Array; + for (int i = 0; i < 2; ++i) + { + float height = i ? -radius : radius; + vertices->push_back (osg::Vec3f (height, -radius, -radius)); + vertices->push_back (osg::Vec3f (height, -radius, radius)); + vertices->push_back (osg::Vec3f (height, radius, -radius)); + vertices->push_back (osg::Vec3f (height, radius, radius)); + } + + geometry->setVertexArray (vertices); + + osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0); + + // top + primitives->push_back (2); + primitives->push_back (1); + primitives->push_back (0); + + primitives->push_back (3); + primitives->push_back (1); + primitives->push_back (2); + + // bottom + primitives->push_back (4); + primitives->push_back (5); + primitives->push_back (6); + + primitives->push_back (6); + primitives->push_back (5); + primitives->push_back (7); + + // sides + primitives->push_back (1); + primitives->push_back (4); + primitives->push_back (0); + + primitives->push_back (4); + primitives->push_back (1); + primitives->push_back (5); + + primitives->push_back (4); + primitives->push_back (2); + primitives->push_back (0); + + primitives->push_back (6); + primitives->push_back (2); + primitives->push_back (4); + + primitives->push_back (6); + primitives->push_back (3); + primitives->push_back (2); + + primitives->push_back (7); + primitives->push_back (3); + primitives->push_back (6); + + primitives->push_back (1); + primitives->push_back (3); + primitives->push_back (5); + + primitives->push_back (5); + primitives->push_back (3); + primitives->push_back (7); + + geometry->addPrimitiveSet (primitives); + + osg::Vec4Array *colours = new osg::Vec4Array; + + colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.5f, 0.2f)); + colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); + colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); + colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); + colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + + geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); + + geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setMode (GL_BLEND, osg::StateAttribute::ON); + geometry->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + + mBaseNode->addChild (geometry); + mParentNode->addChild(mBaseNode); + } + + void InstanceSelectionMode::drawSelectionSphere(const osg::Vec3f& mousePlanePoint) + { + float dragDistance = (mDragStart - mousePlanePoint).length(); + drawSelectionSphere(mDragStart, dragDistance); + } + + void InstanceSelectionMode::drawSelectionSphere(const osg::Vec3d& point, float radius) + { + if (mBaseNode) mParentNode->removeChild (mBaseNode); + mBaseNode = new osg::PositionAttitudeTransform; + mBaseNode->setPosition(point); + + osg::ref_ptr geometry (new osg::Geometry); + + osg::Vec3Array *vertices = new osg::Vec3Array; + int resolution = 32; + float radiusPerResolution = radius / resolution; + float reciprocalResolution = 1.0f / resolution; + float doubleReciprocalRes = reciprocalResolution * 2; + + osg::Vec4Array *colours = new osg::Vec4Array; + + for (float i = 0.0; i <= resolution; i += 2) + { + float iShifted = (static_cast(i) - resolution / 2.0f); // i - 16 = -16 ... 16 + float xPercentile = iShifted * doubleReciprocalRes; + float x = xPercentile * radius; + float thisRadius = sqrt (radius * radius - x * x); + + //the next row + float iShifted2 = (static_cast(i + 1) - resolution / 2.0f); + float xPercentile2 = iShifted2 * doubleReciprocalRes; + float x2 = xPercentile2 * radius; + float thisRadius2 = sqrt (radius * radius - x2 * x2); + + for (int j = 0; j < resolution; ++j) + { + float vertexX = thisRadius * sin(j * reciprocalResolution * osg::PI * 2); + float vertexY = i * radiusPerResolution * 2 - radius; + float vertexZ = thisRadius * cos(j * reciprocalResolution * osg::PI * 2); + float heightPercentage = (vertexZ + radius) / (radius * 2); + vertices->push_back (osg::Vec3f (vertexX, vertexY, vertexZ)); + colours->push_back (osg::Vec4f (heightPercentage, heightPercentage, heightPercentage, 0.3f)); + + float vertexNextRowX = thisRadius2 * sin(j * reciprocalResolution * osg::PI * 2); + float vertexNextRowY = (i + 1) * radiusPerResolution * 2 - radius; + float vertexNextRowZ = thisRadius2 * cos(j * reciprocalResolution * osg::PI * 2); + float heightPercentageNextRow = (vertexZ + radius) / (radius * 2); + vertices->push_back (osg::Vec3f (vertexNextRowX, vertexNextRowY, vertexNextRowZ)); + colours->push_back (osg::Vec4f (heightPercentageNextRow, heightPercentageNextRow, heightPercentageNextRow, 0.3f)); + } + } + + geometry->setVertexArray (vertices); + + osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLE_STRIP, 0); + + for (int i = 0; i < resolution; ++i) + { + //Even + for (int j = 0; j < resolution * 2; ++j) + { + if (i * resolution * 2 + j > static_cast(vertices->size()) - 1) continue; + primitives->push_back (i * resolution * 2 + j); + } + if (i * resolution * 2 > static_cast(vertices->size()) - 1) continue; + primitives->push_back (i * resolution * 2); + primitives->push_back (i * resolution * 2 + 1); + + //Odd + for (int j = 1; j < resolution * 2 - 2; j += 2) + { + if ((i + 1) * resolution * 2 + j - 1 > static_cast(vertices->size()) - 1) continue; + primitives->push_back ((i + 1) * resolution * 2 + j - 1); + primitives->push_back (i * resolution * 2 + j + 2); + } + if ((i + 2) * resolution * 2 - 2 > static_cast(vertices->size()) - 1) continue; + primitives->push_back ((i + 2) * resolution * 2 - 2); + primitives->push_back (i * resolution * 2 + 1); + primitives->push_back ((i + 1) * resolution * 2); + } + + geometry->addPrimitiveSet (primitives); + + geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); + + geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setMode (GL_BLEND, osg::StateAttribute::ON); + geometry->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + + mBaseNode->addChild (geometry); + mParentNode->addChild(mBaseNode); + } + bool InstanceSelectionMode::createContextMenu(QMenu* menu) { if (menu) diff --git a/apps/opencs/view/render/instanceselectionmode.hpp b/apps/opencs/view/render/instanceselectionmode.hpp index a23811671..81795d5d3 100644 --- a/apps/opencs/view/render/instanceselectionmode.hpp +++ b/apps/opencs/view/render/instanceselectionmode.hpp @@ -1,7 +1,13 @@ #ifndef CSV_RENDER_INSTANCE_SELECTION_MODE_H #define CSV_RENDER_INSTANCE_SELECTION_MODE_H +#include + +#include +#include + #include "selectionmode.hpp" +#include "instancedragmodes.hpp" namespace CSVRender { @@ -11,8 +17,25 @@ namespace CSVRender public: - InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget); + InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, osg::Group *cellNode); + ~InstanceSelectionMode(); + + /// Store the worldspace-coordinate when drag begins + void setDragStart(const osg::Vec3d& dragStart); + + /// Store the worldspace-coordinate when drag begins + const osg::Vec3d& getDragStart(); + + /// Store the screen-coordinate when drag begins + void setScreenDragStart(const QPoint& dragStartPoint); + + /// Apply instance selection changes + void dragEnded(const osg::Vec3d& dragEndPoint, DragMode dragMode); + + void drawSelectionCubeCentre(const osg::Vec3f& mousePlanePoint ); + void drawSelectionCubeCorner(const osg::Vec3f& mousePlanePoint ); + void drawSelectionSphere(const osg::Vec3f& mousePlanePoint ); protected: /// Add context menu items to \a menu. @@ -25,8 +48,15 @@ namespace CSVRender private: + void drawSelectionBox(const osg::Vec3d& pointA, const osg::Vec3d& pointB); + void drawSelectionCube(const osg::Vec3d& point, float radius); + void drawSelectionSphere(const osg::Vec3d& point, float radius); + QAction* mDeleteSelection; QAction* mSelectSame; + osg::Vec3d mDragStart; + osg::Group* mParentNode; + osg::ref_ptr mBaseNode; private slots: diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index dca5549af..ed3558422 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -768,6 +768,22 @@ void CSVRender::PagedWorldspaceWidget::selectAllWithSameParentId (int elementMas flagAsModified(); } +void CSVRender::PagedWorldspaceWidget::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) +{ + for (auto& cell : mCells) + { + cell.second->selectInsideCube (pointA, pointB, dragMode); + } +} + +void CSVRender::PagedWorldspaceWidget::selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) +{ + for (auto& cell : mCells) + { + cell.second->selectWithinDistance (point, distance, dragMode); + } +} + std::string CSVRender::PagedWorldspaceWidget::getCellId (const osg::Vec3f& point) const { CSMWorld::CellCoordinates cellCoordinates ( diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index d17670cfa..beab0c575 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -7,6 +7,7 @@ #include "worldspacewidget.hpp" #include "cell.hpp" +#include "instancedragmodes.hpp" namespace CSVWidget { @@ -120,6 +121,10 @@ namespace CSVRender /// \param elementMask Elements to be affected by the select operation void selectAllWithSameParentId (int elementMask) override; + void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) override; + + void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) override; + std::string getCellId (const osg::Vec3f& point) const override; Cell* getCell(const osg::Vec3d& point) const override; diff --git a/apps/opencs/view/render/selectionmode.cpp b/apps/opencs/view/render/selectionmode.cpp index b5ccda5ad..e7e7d47b5 100644 --- a/apps/opencs/view/render/selectionmode.cpp +++ b/apps/opencs/view/render/selectionmode.cpp @@ -15,30 +15,27 @@ namespace CSVRender { addButton(":scenetoolbar/selection-mode-cube", "cube-centre", "Centred cube" - "
  • Drag with {scene-select-primary} (make instances the selection) or {scene-select-secondary} " - "(invert selection state) from the centre of the selection cube outwards
  • " + "
    • Drag with {scene-select-primary} for primary select or {scene-select-secondary} for secondary select " + "from the centre of the selection cube outwards.
    • " "
    • The selection cube is aligned to the word space axis
    • " "
    • If context selection mode is enabled, a drag with {scene-edit-primary} or {scene-edit-secondary} not " "starting on an instance will have the same effect
    • " - "
    " - "Not implemented yet"); + "
"); addButton(":scenetoolbar/selection-mode-cube-corner", "cube-corner", "Cube corner to corner" - "
  • Drag with {scene-select-primary} (make instances the selection) or {scene-select-secondary} " - "(invert selection state) from one corner of the selection cube to the opposite corner
  • " + "
    • Drag with {scene-select-primary} for primary select or {scene-select-secondary} for secondary select " + "from one corner of the selection cube to the opposite corner
    • " "
    • The selection cube is aligned to the word space axis
    • " "
    • If context selection mode is enabled, a drag with {scene-edit-primary} or {scene-edit-secondary} not " "starting on an instance will have the same effect
    • " - "
    " - "Not implemented yet"); + "
"); addButton(":scenetoolbar/selection-mode-cube-sphere", "sphere", "Centred sphere" - "
  • Drag with {scene-select-primary} (make instances the selection) or {scene-select-secondary} " - "(invert selection state) from the centre of the selection sphere outwards
  • " + "
    • Drag with {scene-select-primary} for primary select or {scene-select-secondary} for secondary select " + "from the centre of the selection sphere outwards
    • " "
    • If context selection mode is enabled, a drag with {scene-edit-primary} or {scene-edit-secondary} not " "starting on an instance will have the same effect
    • " - "
    " - "Not implemented yet"); + "
"); mSelectAll = new QAction("Select all", this); mDeselectAll = new QAction("Clear selection", this); diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index b1088aa60..c4fef45dd 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -140,6 +140,16 @@ void CSVRender::UnpagedWorldspaceWidget::selectAllWithSameParentId (int elementM flagAsModified(); } +void CSVRender::UnpagedWorldspaceWidget::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) +{ + mCell->selectInsideCube (pointA, pointB, dragMode); +} + +void CSVRender::UnpagedWorldspaceWidget::selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) +{ + mCell->selectWithinDistance (point, distance, dragMode); +} + std::string CSVRender::UnpagedWorldspaceWidget::getCellId (const osg::Vec3f& point) const { return mCellId; diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index eec1b01f3..83233c327 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -60,6 +60,10 @@ namespace CSVRender /// \param elementMask Elements to be affected by the select operation void selectAllWithSameParentId (int elementMask) override; + void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) override; + + void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) override; + std::string getCellId (const osg::Vec3f& point) const override; Cell* getCell(const osg::Vec3d& point) const override; diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 3b8cf70c2..5e224b380 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -7,6 +7,7 @@ #include "../../model/doc/document.hpp" #include "../../model/world/tablemimedata.hpp" +#include "instancedragmodes.hpp" #include "scenewidget.hpp" #include "mask.hpp" @@ -160,6 +161,10 @@ namespace CSVRender /// \param elementMask Elements to be affected by the select operation virtual void selectAllWithSameParentId (int elementMask) = 0; + virtual void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) = 0; + + virtual void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) = 0; + /// Return the next intersection with scene elements matched by /// \a interactionMask based on \a localPos and the camera vector. /// If there is no such intersection, instead a point "in front" of \a localPos will be From 7b54415c40a184b2cd4c41e726f63d61ad3ab41a Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Jan 2021 21:38:56 +0100 Subject: [PATCH 033/117] Fix reference binding to null /usr/include/c++/10.2.0/bits/stl_vector.h:1046:34: runtime error: reference binding to null pointer of type 'value_type' #0 0x55e37f50008a in std::vector >::operator[](unsigned long) /usr/include/c++/10.2.0/bits/stl_vector.h:1046 #1 0x55e37f50008a in ESM::SavedGame::load(ESM::ESMReader&) /home/elsid/dev/openmw/components/esm/savedgame.cpp:28 #2 0x55e37e726139 in MWState::Character::addSlot(boost::filesystem::path const&, std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/apps/openmw/mwstate/character.cpp:31 #3 0x55e37e742b39 in MWState::Character::Character(boost::filesystem::path const&, std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/apps/openmw/mwstate/character.cpp:88 #4 0x55e37e7006e1 in MWState::CharacterManager::CharacterManager(boost::filesystem::path const&, std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/apps/openmw/mwstate/charactermanager.cpp:25 #5 0x55e37e6d4140 in MWState::StateManager::StateManager(boost::filesystem::path const&, std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/apps/openmw/mwstate/statemanagerimp.cpp:90 #6 0x55e37e82595a in OMW::Engine::prepareEngine(Settings::Manager&) /home/elsid/dev/openmw/apps/openmw/engine.cpp:641 #7 0x55e37e8439fd in OMW::Engine::go() /home/elsid/dev/openmw/apps/openmw/engine.cpp:867 #8 0x55e37e782760 in runApplication(int, char**) /home/elsid/dev/openmw/apps/openmw/main.cpp:289 #9 0x55e37f6483c3 in wrapApplication(int (*)(int, char**), int, char**, std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/components/debug/debugging.cpp:200 #10 0x55e37ba8e3fe in main /home/elsid/dev/openmw/apps/openmw/main.cpp:301 #11 0x7f013e845151 in __libc_start_main (/usr/lib/libc.so.6+0x28151) #12 0x55e37baa0e3d in _start (/home/elsid/dev/openmw/build/gcc/ubsan/openmw+0x6c11e3d) --- components/esm/savedgame.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index 9edcb1a67..7cb30f2dd 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -25,7 +25,7 @@ void ESM::SavedGame::load (ESMReader &esm) esm.getSubNameIs("SCRN"); esm.getSubHeader(); mScreenshot.resize(esm.getSubSize()); - esm.getExact(&mScreenshot[0], mScreenshot.size()); + esm.getExact(mScreenshot.data(), mScreenshot.size()); } void ESM::SavedGame::save (ESMWriter &esm) const From d2d8a7a940b47660f7bd51cc065fbf13119c0179 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Jan 2021 21:58:27 +0100 Subject: [PATCH 034/117] Fix passing null to memcpy /home/elsid/dev/openmw/components/detournavigator/navmeshtilescache.cpp:36:24: runtime error: null pointer passed as argument 2, which is declared to never be null #0 0x55e37ba4cda5 in makeNavMeshKey /home/elsid/dev/openmw/components/detournavigator/navmeshtilescache.cpp:36 #1 0x55e37ba4cda5 in DetourNavigator::NavMeshTilesCache::set(osg::Vec3f const&, osg::Vec2i const&, DetourNavigator::RecastMesh const&, std::vector > const&, DetourNavigator::NavMeshData&&) /home/elsid/dev/openmw/components/detournavigator/navmeshtilescache.cpp:81 #2 0x55e37fe3c861 in DetourNavigator::updateNavMesh(osg::Vec3f const&, DetourNavigator::RecastMesh const*, osg::Vec2i const&, osg::Vec2i const&, std::vector > const&, DetourNavigator::Settings const&, std::shared_ptr > const&, DetourNavigator::NavMeshTilesCache&) /home/elsid/dev/openmw/components/detournavigator/makenavmesh.cpp:582 #3 0x55e37fb796ce in DetourNavigator::AsyncNavMeshUpdater::processJob(DetourNavigator::AsyncNavMeshUpdater::Job const&) /home/elsid/dev/openmw/components/detournavigator/asyncnavmeshupdater.cpp:178 #4 0x55e37fb9a125 in DetourNavigator::AsyncNavMeshUpdater::process() /home/elsid/dev/openmw/components/detournavigator/asyncnavmeshupdater.cpp:144 #5 0x7f013f585c23 in execute_native_thread_routine /build/gcc/src/gcc/libstdc++-v3/src/c++11/thread.cc:80 #6 0x7f013f8c63e8 in start_thread (/usr/lib/libpthread.so.0+0x93e8) #7 0x7f013e91d292 in __GI___clone (/usr/lib/libc.so.6+0x100292) --- .../detournavigator/navmeshtilescache.cpp | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp index f554cd414..cff93ac0e 100644 --- a/components/detournavigator/navmeshtilescache.cpp +++ b/components/detournavigator/navmeshtilescache.cpp @@ -21,19 +21,32 @@ namespace DetourNavigator std::vector result(indicesSize + verticesSize + areaTypesSize + waterSize + offMeshConnectionsSize); unsigned char* dst = result.data(); - std::memcpy(dst, recastMesh.getIndices().data(), indicesSize); - dst += indicesSize; + if (indicesSize > 0) + { + std::memcpy(dst, recastMesh.getIndices().data(), indicesSize); + dst += indicesSize; + } - std::memcpy(dst, recastMesh.getVertices().data(), verticesSize); - dst += verticesSize; + if (verticesSize > 0) + { + std::memcpy(dst, recastMesh.getVertices().data(), verticesSize); + dst += verticesSize; + } - std::memcpy(dst, recastMesh.getAreaTypes().data(), areaTypesSize); - dst += areaTypesSize; + if (areaTypesSize > 0) + { + std::memcpy(dst, recastMesh.getAreaTypes().data(), areaTypesSize); + dst += areaTypesSize; + } - std::memcpy(dst, recastMesh.getWater().data(), waterSize); - dst += waterSize; + if (waterSize > 0) + { + std::memcpy(dst, recastMesh.getWater().data(), waterSize); + dst += waterSize; + } - std::memcpy(dst, offMeshConnections.data(), offMeshConnectionsSize); + if (offMeshConnectionsSize > 0) + std::memcpy(dst, offMeshConnections.data(), offMeshConnectionsSize); return result; } From 5f1d3e0e2fb2c209d7ad23c6c53d69d4258e6788 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sat, 9 Jan 2021 22:20:57 +0200 Subject: [PATCH 035/117] Use the Topic ID of the cloned target from topicinfos --- apps/opencs/model/world/collection.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 451ef9d0e..f657c67a1 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -15,6 +15,7 @@ #include "columnbase.hpp" #include "collectionbase.hpp" +#include "info.hpp" #include "land.hpp" #include "landtexture.hpp" #include "ref.hpp" @@ -264,6 +265,13 @@ namespace CSMWorld CSMWorld::CellRef* ptr = (CSMWorld::CellRef*) ©.mModified; ptr->mRefNum.mIndex = 0; } + if (type == UniversalId::Type_TopicInfo || type == UniversalId::Type_JournalInfo) + { + CSMWorld::Info* ptr = (CSMWorld::Info*) ©.mModified; + std::vector splitStringContainer; + Misc::StringUtils::split(destination, splitStringContainer, "#"); + ptr->mTopicId = splitStringContainer[0]; + } int index = getAppendIndex(destination, type); insertRecord(copy, getAppendIndex(destination, type)); From 29416269b27434f0e1bf7854f736fb2ada47c9eb Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sun, 10 Jan 2021 01:07:03 +0200 Subject: [PATCH 036/117] Update changelog --- CHANGELOG.md | 1 + CHANGELOG_PR.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee84ae6d7..123936d42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Bug #4055: Local scripts don't inherit variables from their base record Bug #4083: Door animation freezes when colliding with actors Bug #4247: Cannot walk up stairs in Ebonheart docks + Bug #4363: Editor: Defect in Clone Function for Dialogue Info records Bug #4447: Actor collision capsule shape allows looking through some walls Bug #4465: Collision shape overlapping causes twitching Bug #4476: Abot Gondoliers: player hangs in air during scenic travel diff --git a/CHANGELOG_PR.md b/CHANGELOG_PR.md index fdf27dc93..f70fbad57 100644 --- a/CHANGELOG_PR.md +++ b/CHANGELOG_PR.md @@ -35,6 +35,7 @@ Bug Fixes: Editor Bug Fixes: - Deleted and moved objects within a cell are now saved properly (#832) +- Topic and Journal Info records can now be cloned with a different parent Topic/Journal Id (#4363) - Verifier no longer checks for alleged 'race' entries in clothing body parts (#5400) - Loading mods now keeps the master index (#5675) - Flicker and crashing on XFCE4 fixed (#5703) From 3045d20a970afd0da6a63081186e545ca940bfe6 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sun, 10 Jan 2021 01:25:40 +0200 Subject: [PATCH 037/117] Make sure that vector isn't empty, just in case --- apps/opencs/model/world/collection.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index f657c67a1..16ebb3a61 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -270,7 +270,7 @@ namespace CSMWorld CSMWorld::Info* ptr = (CSMWorld::Info*) ©.mModified; std::vector splitStringContainer; Misc::StringUtils::split(destination, splitStringContainer, "#"); - ptr->mTopicId = splitStringContainer[0]; + if (!splitStringContainer.empty()) ptr->mTopicId = splitStringContainer[0]; } int index = getAppendIndex(destination, type); From a257567b8077a0c272eb987569947e4cdac3ad71 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 10 Jan 2021 08:04:06 +0000 Subject: [PATCH 038/117] Don't update magic effects when unequipping items to equip something else --- CHANGELOG.md | 1 + apps/openmw/mwworld/inventorystore.cpp | 9 +++++---- apps/openmw/mwworld/inventorystore.hpp | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa3572282..db1e4042c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,6 +81,7 @@ Bug #5661: Region sounds don't play at the right interval Bug #5675: OpenMW-cs. FRMR subrecords are saved with the wrong MastIdx Bug #5681: Player character can clip or pass through bridges instead of colliding against them + Bug #5687: Bound items covering the same inventory slot expiring at the same time freezes the game Bug #5688: Water shader broken indoors with enable indoor shadows = false Bug #5695: ExplodeSpell for actors doesn't target the ground Bug #5703: OpenMW-CS menu system crashing on XFCE diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 38007c1cb..c9b1d8bc1 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -762,7 +762,7 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor return retCount; } -MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor, bool fireEvent) +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor, bool applyUpdates) { if (slot<0 || slot>=static_cast (mSlots.size())) throw std::runtime_error ("slot number out of range"); @@ -794,10 +794,11 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c } } - if (fireEvent) + if (applyUpdates) + { fireEquipmentChangedEvent(actor); - - updateMagicEffects(actor); + updateMagicEffects(actor); + } return retval; } diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index df69a5709..bfe0a9992 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -173,7 +173,7 @@ namespace MWWorld /// /// @return the number of items actually removed - ContainerStoreIterator unequipSlot(int slot, const Ptr& actor, bool fireEvent=true); + ContainerStoreIterator unequipSlot(int slot, const Ptr& actor, bool applyUpdates = true); ///< Unequip \a slot. /// /// @return an iterator to the item that was previously in the slot From d015f17a6ce290a0a338db51745dc9a98329cba6 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 25 Dec 2020 18:20:42 +0100 Subject: [PATCH 039/117] Make all physics object manage their own resources Use smart pointer for heightfields and their members. Move collision object addition inside of Object's ctor, as for Actors and HeightFields --- apps/openmw/mwphysics/actor.cpp | 5 ++--- apps/openmw/mwphysics/heightfield.cpp | 25 +++++++++++++------------ apps/openmw/mwphysics/heightfield.hpp | 11 ++++++++--- apps/openmw/mwphysics/object.cpp | 12 ++++++------ apps/openmw/mwphysics/object.hpp | 2 +- apps/openmw/mwphysics/physicssystem.cpp | 24 ++++-------------------- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwphysics/projectile.cpp | 11 ++++------- 8 files changed, 39 insertions(+), 53 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 3b7b0d061..3b52ee934 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -58,7 +58,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic mConvexShape = static_cast(mShape.get()); - mCollisionObject.reset(new btCollisionObject); + mCollisionObject = std::make_unique(); mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); mCollisionObject->setActivationState(DISABLE_DEACTIVATION); mCollisionObject->setCollisionShape(mShape.get()); @@ -76,8 +76,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic Actor::~Actor() { - if (mCollisionObject) - mTaskScheduler->removeCollisionObject(mCollisionObject.get()); + mTaskScheduler->removeCollisionObject(mCollisionObject.get()); } void Actor::enableCollisionMode(bool collision) diff --git a/apps/openmw/mwphysics/heightfield.cpp b/apps/openmw/mwphysics/heightfield.cpp index e1448116b..436cdfe8f 100644 --- a/apps/openmw/mwphysics/heightfield.cpp +++ b/apps/openmw/mwphysics/heightfield.cpp @@ -1,4 +1,5 @@ #include "heightfield.hpp" +#include "mtphysics.hpp" #include @@ -42,10 +43,12 @@ namespace namespace MWPhysics { - HeightField::HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject) - : mHeights(makeHeights(heights, sqrtVerts)) + HeightField::HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject, PhysicsTaskScheduler* scheduler) + : mHoldObject(holdObject) + , mHeights(makeHeights(heights, sqrtVerts)) + , mTaskScheduler(scheduler) { - mShape = new btHeightfieldTerrainShape( + mShape = std::make_unique( sqrtVerts, sqrtVerts, getHeights(heights, mHeights), 1, @@ -60,31 +63,29 @@ namespace MWPhysics (y+0.5f) * triSize * (sqrtVerts-1), (maxH+minH)*0.5f)); - mCollisionObject = new btCollisionObject; - mCollisionObject->setCollisionShape(mShape); + mCollisionObject = std::make_unique(); + mCollisionObject->setCollisionShape(mShape.get()); mCollisionObject->setWorldTransform(transform); - - mHoldObject = holdObject; + mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_HeightMap, CollisionType_Actor|CollisionType_Projectile); } HeightField::~HeightField() { - delete mCollisionObject; - delete mShape; + mTaskScheduler->removeCollisionObject(mCollisionObject.get()); } btCollisionObject* HeightField::getCollisionObject() { - return mCollisionObject; + return mCollisionObject.get(); } const btCollisionObject* HeightField::getCollisionObject() const { - return mCollisionObject; + return mCollisionObject.get(); } const btHeightfieldTerrainShape* HeightField::getShape() const { - return mShape; + return mShape.get(); } } diff --git a/apps/openmw/mwphysics/heightfield.hpp b/apps/openmw/mwphysics/heightfield.hpp index 2ba58afff..c76f8b943 100644 --- a/apps/openmw/mwphysics/heightfield.hpp +++ b/apps/openmw/mwphysics/heightfield.hpp @@ -5,6 +5,7 @@ #include +#include #include class btCollisionObject; @@ -17,10 +18,12 @@ namespace osg namespace MWPhysics { + class PhysicsTaskScheduler; + class HeightField { public: - HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject); + HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject, PhysicsTaskScheduler* scheduler); ~HeightField(); btCollisionObject* getCollisionObject(); @@ -28,11 +31,13 @@ namespace MWPhysics const btHeightfieldTerrainShape* getShape() const; private: - btHeightfieldTerrainShape* mShape; - btCollisionObject* mCollisionObject; + std::unique_ptr mShape; + std::unique_ptr mCollisionObject; osg::ref_ptr mHoldObject; std::vector mHeights; + PhysicsTaskScheduler* mTaskScheduler; + void operator=(const HeightField&); HeightField(const HeightField&); }; diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 910f7bf15..e3615989d 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -14,29 +14,29 @@ namespace MWPhysics { - Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, PhysicsTaskScheduler* scheduler) + Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler) : mShapeInstance(shapeInstance) , mSolid(true) , mTaskScheduler(scheduler) { mPtr = ptr; - mCollisionObject.reset(new btCollisionObject); + mCollisionObject = std::make_unique(); mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape()); mCollisionObject->setUserPointer(this); setScale(ptr.getCellRef().getScale()); setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); - const float* pos = ptr.getRefData().getPosition().pos; - setOrigin(btVector3(pos[0], pos[1], pos[2])); + setOrigin(Misc::Convert::toBullet(ptr.getRefData().getPosition().asVec3())); commitPositionChange(); + + mTaskScheduler->addCollisionObject(mCollisionObject.get(), collisionType, CollisionType_Actor|CollisionType_HeightMap|CollisionType_Projectile); } Object::~Object() { - if (mCollisionObject) - mTaskScheduler->removeCollisionObject(mCollisionObject.get()); + mTaskScheduler->removeCollisionObject(mCollisionObject.get()); } const Resource::BulletShapeInstance* Object::getShapeInstance() const diff --git a/apps/openmw/mwphysics/object.hpp b/apps/openmw/mwphysics/object.hpp index 876e35651..cae877180 100644 --- a/apps/openmw/mwphysics/object.hpp +++ b/apps/openmw/mwphysics/object.hpp @@ -26,7 +26,7 @@ namespace MWPhysics class Object final : public PtrHolder { public: - Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, PhysicsTaskScheduler* scheduler); + Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler); ~Object() override; const Resource::BulletShapeInstance* getShapeInstance() const; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index b50366ade..95d1ec70a 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -107,12 +107,7 @@ namespace MWPhysics if (mWaterCollisionObject) mTaskScheduler->removeCollisionObject(mWaterCollisionObject.get()); - for (auto& heightField : mHeightFields) - { - mTaskScheduler->removeCollisionObject(heightField.second->getCollisionObject()); - delete heightField.second; - } - + mHeightFields.clear(); mObjects.clear(); mActors.clear(); mProjectiles.clear(); @@ -442,22 +437,14 @@ namespace MWPhysics void PhysicsSystem::addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject) { - HeightField *heightfield = new HeightField(heights, x, y, triSize, sqrtVerts, minH, maxH, holdObject); - mHeightFields[std::make_pair(x,y)] = heightfield; - - mTaskScheduler->addCollisionObject(heightfield->getCollisionObject(), CollisionType_HeightMap, - CollisionType_Actor|CollisionType_Projectile); + mHeightFields[std::make_pair(x,y)] = std::make_unique(heights, x, y, triSize, sqrtVerts, minH, maxH, holdObject, mTaskScheduler.get()); } void PhysicsSystem::removeHeightField (int x, int y) { HeightFieldMap::iterator heightfield = mHeightFields.find(std::make_pair(x,y)); if(heightfield != mHeightFields.end()) - { - mTaskScheduler->removeCollisionObject(heightfield->second->getCollisionObject()); - delete heightfield->second; mHeightFields.erase(heightfield); - } } const HeightField* PhysicsSystem::getHeightField(int x, int y) const @@ -465,7 +452,7 @@ namespace MWPhysics const auto heightField = mHeightFields.find(std::make_pair(x, y)); if (heightField == mHeightFields.end()) return nullptr; - return heightField->second; + return heightField->second.get(); } void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType) @@ -474,14 +461,11 @@ namespace MWPhysics if (!shapeInstance || !shapeInstance->getCollisionShape()) return; - auto obj = std::make_shared(ptr, shapeInstance, mTaskScheduler.get()); + auto obj = std::make_shared(ptr, shapeInstance, collisionType, mTaskScheduler.get()); mObjects.emplace(ptr, obj); if (obj->isAnimated()) mAnimatedObjects.insert(obj.get()); - - mTaskScheduler->addCollisionObject(obj->getCollisionObject(), collisionType, - CollisionType_Actor|CollisionType_HeightMap|CollisionType_Projectile); } void PhysicsSystem::remove(const MWWorld::Ptr &ptr) diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 2de8d153b..449209974 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -274,7 +274,7 @@ namespace MWPhysics using ProjectileMap = std::map>; ProjectileMap mProjectiles; - using HeightFieldMap = std::map, HeightField *>; + using HeightFieldMap = std::map, std::unique_ptr>; HeightFieldMap mHeightFields; bool mDebugDrawEnabled; diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index a8aaeb72a..1b9beca5f 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -28,7 +28,7 @@ Projectile::Projectile(int projectileId, const MWWorld::Ptr& caster, const osg:: mShape.reset(new btSphereShape(1.f)); mConvexShape = static_cast(mShape.get()); - mCollisionObject.reset(new btCollisionObject); + mCollisionObject = std::make_unique(); mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); mCollisionObject->setActivationState(DISABLE_DEACTIVATION); mCollisionObject->setCollisionShape(mShape.get()); @@ -45,12 +45,9 @@ Projectile::Projectile(int projectileId, const MWWorld::Ptr& caster, const osg:: Projectile::~Projectile() { - if (mCollisionObject) - { - if (!mActive) - mPhysics->reportCollision(mHitPosition, mHitNormal); - mTaskScheduler->removeCollisionObject(mCollisionObject.get()); - } + if (!mActive) + mPhysics->reportCollision(mHitPosition, mHitNormal); + mTaskScheduler->removeCollisionObject(mCollisionObject.get()); } void Projectile::commitPositionChange() From 1ab4683dcecb4947b81dcf6606407b6c46a1eacb Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 28 Dec 2020 19:19:07 +0100 Subject: [PATCH 040/117] Tweak follow distance to be more like the original --- apps/openmw/mwmechanics/aifollow.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index f31be9c10..ec2367997 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -14,6 +14,16 @@ #include "movement.hpp" #include "steering.hpp" +namespace +{ +osg::Vec3f::value_type getHalfExtents(const MWWorld::ConstPtr& actor) +{ + if(actor.getClass().isNpc()) + return 64; + return MWBase::Environment::get().getWorld()->getHalfExtents(actor).y(); +} +} + namespace MWMechanics { int AiFollow::mFollowIndexCounter = 0; @@ -121,13 +131,14 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte { for(auto& follower : followers) { - auto halfExtent = MWBase::Environment::get().getWorld()->getHalfExtents(follower.second).y(); + auto halfExtent = getHalfExtents(follower.second); if(halfExtent > floatingDistance) floatingDistance = halfExtent; } + floatingDistance += 128; } - floatingDistance += MWBase::Environment::get().getWorld()->getHalfExtents(target).y(); - floatingDistance += MWBase::Environment::get().getWorld()->getHalfExtents(actor).y() * 2; + floatingDistance += getHalfExtents(target) + 64; + floatingDistance += getHalfExtents(actor) * 2; short followDistance = static_cast(floatingDistance); if (!mAlwaysFollow) //Update if you only follow for a bit From 14dd11372f3e76a7289d929387ef38c06f0fbc8d Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 5 Jan 2021 22:20:09 +0100 Subject: [PATCH 041/117] Utility functions for little-endian <-> big-endian conversion. --- components/misc/endianness.hpp | 82 ++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 components/misc/endianness.hpp diff --git a/components/misc/endianness.hpp b/components/misc/endianness.hpp new file mode 100644 index 000000000..1b43e584e --- /dev/null +++ b/components/misc/endianness.hpp @@ -0,0 +1,82 @@ +#ifndef COMPONENTS_MISC_ENDIANNESS_H +#define COMPONENTS_MISC_ENDIANNESS_H + +#include + +namespace Misc +{ + + // Two-way conversion little-endian <-> big-endian + template + void swapEndiannessInplace(T& v) + { + static_assert(std::is_arithmetic_v); + static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8); + + if constexpr (sizeof(T) == 2) + { + uint16_t& v16 = *reinterpret_cast(&v); + v16 = (v16 >> 8) | (v16 << 8); + } + if constexpr (sizeof(T) == 4) + { + uint32_t& v32 = *reinterpret_cast(&v); + v32 = (v32 >> 24) | ((v32 >> 8) & 0xff00) | ((v32 & 0xff00) << 8) || v32 << 24; + } + if constexpr (sizeof(T) == 8) + { + uint64_t& v64 = *reinterpret_cast(&v); + v64 = (v64 >> 56) | ((v64 & 0x00ff'0000'0000'0000) >> 40) | ((v64 & 0x0000'ff00'0000'0000) >> 24) + | ((v64 & 0x0000'00ff'0000'0000) >> 8) | ((v64 & 0x0000'0000'ff00'0000) << 8) + | ((v64 & 0x0000'0000'00ff'0000) << 24) | ((v64 & 0x0000'0000'0000'ff00) << 40) | (v64 << 56); + } + } + + #ifdef _WIN32 + constexpr bool IS_LITTLE_ENDIAN = true; + constexpr bool IS_BIG_ENDIAN = false; + #else + constexpr bool IS_LITTLE_ENDIAN = __BYTE_ORDER__ != __ORDER_BIG_ENDIAN__; + constexpr bool IS_BIG_ENDIAN = __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__; + #endif + + // Usage: swapEndiannessInplaceIf(v) - native to little-endian or back + // swapEndiannessInplaceIf(v) - native to big-endian or back + template + void swapEndiannessInplaceIf(T& v) + { + static_assert(std::is_arithmetic_v); + static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8); + if constexpr (C) + swapEndiannessInplace(v); + } + + template + T toLittleEndian(T v) + { + swapEndiannessInplaceIf(v); + return v; + } + template + T fromLittleEndian(T v) + { + swapEndiannessInplaceIf(v); + return v; + } + + template + T toBigEndian(T v) + { + swapEndiannessInplaceIf(v); + return v; + } + template + T fromBigEndian(T v) + { + swapEndiannessInplaceIf(v); + return v; + } + +} + +#endif // COMPONENTS_MISC_ENDIANNESS_H From eaaa2f4a1c4daff1e035208eda662476449a4a1f Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 8 Jan 2021 23:02:19 +0100 Subject: [PATCH 042/117] Use misc/endianness.hpp in components/nif/nifstream --- components/nif/nifstream.cpp | 2 +- components/nif/nifstream.hpp | 98 +++++++++++++----------------------- 2 files changed, 35 insertions(+), 65 deletions(-) diff --git a/components/nif/nifstream.cpp b/components/nif/nifstream.cpp index 69f1a905b..07c9c917c 100644 --- a/components/nif/nifstream.cpp +++ b/components/nif/nifstream.cpp @@ -7,7 +7,7 @@ namespace Nif osg::Quat NIFStream::getQuaternion() { float f[4]; - readLittleEndianBufferOfType<4, float,uint32_t>(inp, (float*)&f); + readLittleEndianBufferOfType<4, float>(inp, (float*)&f); osg::Quat quat; quat.w() = f[0]; quat.x() = f[1]; diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index 97075c288..4d221b867 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -22,61 +23,30 @@ namespace Nif class NIFFile; /* - readLittleEndianBufferOfType: This template should only be used with non POD data types + readLittleEndianBufferOfType: This template should only be used with arithmetic types */ -template inline void readLittleEndianBufferOfType(Files::IStreamPtr &pIStream, T* dest) +template inline void readLittleEndianBufferOfType(Files::IStreamPtr &pIStream, T* dest) { -#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86) pIStream->read((char*)dest, numInstances * sizeof(T)); -#else - uint8_t* destByteBuffer = (uint8_t*)dest; - pIStream->read((char*)dest, numInstances * sizeof(T)); - /* - Due to the loop iterations being known at compile time, - this nested loop will most likely be unrolled - For example, for 2 instances of a 4 byte data type, you should get the below result - */ - union { - IntegerT i; - T t; - } u; - for (uint32_t i = 0; i < numInstances; i++) - { - u = { 0 }; - for (uint32_t byte = 0; byte < sizeof(T); byte++) - u.i |= (((IntegerT)destByteBuffer[i * sizeof(T) + byte]) << (byte * 8)); - dest[i] = u.t; - } -#endif + if constexpr (Misc::IS_BIG_ENDIAN) + for (uint32_t i = 0; i < numInstances; i++) + Misc::swapEndiannessInplace(dest[i]); } /* - readLittleEndianDynamicBufferOfType: This template should only be used with non POD data types + readLittleEndianDynamicBufferOfType: This template should only be used with arithmetic types */ -template inline void readLittleEndianDynamicBufferOfType(Files::IStreamPtr &pIStream, T* dest, uint32_t numInstances) +template inline void readLittleEndianDynamicBufferOfType(Files::IStreamPtr &pIStream, T* dest, uint32_t numInstances) { -#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86) pIStream->read((char*)dest, numInstances * sizeof(T)); -#else - uint8_t* destByteBuffer = (uint8_t*)dest; - pIStream->read((char*)dest, numInstances * sizeof(T)); - union { - IntegerT i; - T t; - } u; - for (uint32_t i = 0; i < numInstances; i++) - { - u.i = 0; - for (uint32_t byte = 0; byte < sizeof(T); byte++) - u.i |= ((IntegerT)destByteBuffer[i * sizeof(T) + byte]) << (byte * 8); - dest[i] = u.t; - } -#endif + if constexpr (Misc::IS_BIG_ENDIAN) + for (uint32_t i = 0; i < numInstances; i++) + Misc::swapEndiannessInplace(dest[i]); } -template type inline readLittleEndianType(Files::IStreamPtr &pIStream) +template type inline readLittleEndianType(Files::IStreamPtr &pIStream) { type val; - readLittleEndianBufferOfType<1,type,IntegerT>(pIStream, (type*)&val); + readLittleEndianBufferOfType<1, type>(pIStream, (type*)&val); return val; } @@ -95,59 +65,59 @@ public: char getChar() { - return readLittleEndianType(inp); + return readLittleEndianType(inp); } short getShort() { - return readLittleEndianType(inp); + return readLittleEndianType(inp); } unsigned short getUShort() { - return readLittleEndianType(inp); + return readLittleEndianType(inp); } int getInt() { - return readLittleEndianType(inp); + return readLittleEndianType(inp); } unsigned int getUInt() { - return readLittleEndianType(inp); + return readLittleEndianType(inp); } float getFloat() { - return readLittleEndianType(inp); + return readLittleEndianType(inp); } osg::Vec2f getVector2() { osg::Vec2f vec; - readLittleEndianBufferOfType<2,float,uint32_t>(inp, (float*)&vec._v[0]); + readLittleEndianBufferOfType<2,float>(inp, (float*)&vec._v[0]); return vec; } osg::Vec3f getVector3() { osg::Vec3f vec; - readLittleEndianBufferOfType<3, float,uint32_t>(inp, (float*)&vec._v[0]); + readLittleEndianBufferOfType<3, float>(inp, (float*)&vec._v[0]); return vec; } osg::Vec4f getVector4() { osg::Vec4f vec; - readLittleEndianBufferOfType<4, float,uint32_t>(inp, (float*)&vec._v[0]); + readLittleEndianBufferOfType<4, float>(inp, (float*)&vec._v[0]); return vec; } Matrix3 getMatrix3() { Matrix3 mat; - readLittleEndianBufferOfType<9, float,uint32_t>(inp, (float*)&mat.mValues); + readLittleEndianBufferOfType<9, float>(inp, (float*)&mat.mValues); return mat; } @@ -181,14 +151,14 @@ public: ///Read in a string of the length specified in the file std::string getSizedString() { - size_t size = readLittleEndianType(inp); + size_t size = readLittleEndianType(inp); return getSizedString(size); } ///Specific to Bethesda headers, uses a byte for length std::string getExportString() { - size_t size = static_cast(readLittleEndianType(inp)); + size_t size = static_cast(readLittleEndianType(inp)); return getSizedString(size); } @@ -203,58 +173,58 @@ public: void getChars(std::vector &vec, size_t size) { vec.resize(size); - readLittleEndianDynamicBufferOfType(inp, vec.data(), size); + readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getUChars(std::vector &vec, size_t size) { vec.resize(size); - readLittleEndianDynamicBufferOfType(inp, vec.data(), size); + readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getUShorts(std::vector &vec, size_t size) { vec.resize(size); - readLittleEndianDynamicBufferOfType(inp, vec.data(), size); + readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getFloats(std::vector &vec, size_t size) { vec.resize(size); - readLittleEndianDynamicBufferOfType(inp, vec.data(), size); + readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getInts(std::vector &vec, size_t size) { vec.resize(size); - readLittleEndianDynamicBufferOfType(inp, vec.data(), size); + readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getUInts(std::vector &vec, size_t size) { vec.resize(size); - readLittleEndianDynamicBufferOfType(inp, vec.data(), size); + readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getVector2s(std::vector &vec, size_t size) { vec.resize(size); /* The packed storage of each Vec2f is 2 floats exactly */ - readLittleEndianDynamicBufferOfType(inp,(float*)vec.data(), size*2); + readLittleEndianDynamicBufferOfType(inp,(float*)vec.data(), size*2); } void getVector3s(std::vector &vec, size_t size) { vec.resize(size); /* The packed storage of each Vec3f is 3 floats exactly */ - readLittleEndianDynamicBufferOfType(inp, (float*)vec.data(), size*3); + readLittleEndianDynamicBufferOfType(inp, (float*)vec.data(), size*3); } void getVector4s(std::vector &vec, size_t size) { vec.resize(size); /* The packed storage of each Vec4f is 4 floats exactly */ - readLittleEndianDynamicBufferOfType(inp, (float*)vec.data(), size*4); + readLittleEndianDynamicBufferOfType(inp, (float*)vec.data(), size*4); } void getQuaternions(std::vector &quat, size_t size) From 7196ad74557d8c608a9401273a51220fd786f8db Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sun, 10 Jan 2021 21:23:52 +0200 Subject: [PATCH 043/117] Implement an override-value when cloning, use when cloning info records --- apps/opencs/model/world/collection.hpp | 8 -------- apps/opencs/model/world/commands.cpp | 9 +++++++++ apps/opencs/model/world/commands.hpp | 3 +++ apps/opencs/view/world/infocreator.cpp | 4 ++++ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 16ebb3a61..451ef9d0e 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -15,7 +15,6 @@ #include "columnbase.hpp" #include "collectionbase.hpp" -#include "info.hpp" #include "land.hpp" #include "landtexture.hpp" #include "ref.hpp" @@ -265,13 +264,6 @@ namespace CSMWorld CSMWorld::CellRef* ptr = (CSMWorld::CellRef*) ©.mModified; ptr->mRefNum.mIndex = 0; } - if (type == UniversalId::Type_TopicInfo || type == UniversalId::Type_JournalInfo) - { - CSMWorld::Info* ptr = (CSMWorld::Info*) ©.mModified; - std::vector splitStringContainer; - Misc::StringUtils::split(destination, splitStringContainer, "#"); - if (!splitStringContainer.empty()) ptr->mTopicId = splitStringContainer[0]; - } int index = getAppendIndex(destination, type); insertRecord(copy, getAppendIndex(destination, type)); diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index e33be1139..34485a46d 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -397,6 +397,10 @@ void CSMWorld::CloneCommand::redo() { mModel.cloneRecord (mIdOrigin, mId, mType); applyModifications(); + for (auto& value : mOverrideValues) + { + mModel.setData(mModel.getModelIndex (mId, value.first), value.second); + } } void CSMWorld::CloneCommand::undo() @@ -404,6 +408,11 @@ void CSMWorld::CloneCommand::undo() mModel.removeRow (mModel.getModelIndex (mId, 0).row()); } +void CSMWorld::CloneCommand::setOverrideValue(int column, QVariant value) +{ + mOverrideValues.emplace_back(std::make_pair(column, value)); +} + CSMWorld::CreatePathgridCommand::CreatePathgridCommand(IdTable& model, const std::string& id, QUndoCommand *parent) : CreateCommand(model, id, parent) { diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index 5776cae36..33608304f 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -183,6 +183,7 @@ namespace CSMWorld class CloneCommand : public CreateCommand { std::string mIdOrigin; + std::vector> mOverrideValues; public: @@ -194,6 +195,8 @@ namespace CSMWorld void redo() override; void undo() override; + + void setOverrideValue(int column, QVariant value); }; class RevertCommand : public QUndoCommand diff --git a/apps/opencs/view/world/infocreator.cpp b/apps/opencs/view/world/infocreator.cpp index 2f1615c87..505de753c 100644 --- a/apps/opencs/view/world/infocreator.cpp +++ b/apps/opencs/view/world/infocreator.cpp @@ -40,10 +40,14 @@ void CSVWorld::InfoCreator::configureCreateCommand (CSMWorld::CreateCommand& com command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Rank), -1); command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Gender), -1); command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_PcRank), -1); + CSMWorld::CloneCommand* cloneCommand = dynamic_cast (&command); + if (cloneCommand) cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text()); } else { command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); + CSMWorld::CloneCommand* cloneCommand = dynamic_cast (&command); + if (cloneCommand) cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); } } From 7be7af13d753d7cdbf7a8fa3b6ef9331a79b063b Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Mon, 11 Jan 2021 06:53:23 +0300 Subject: [PATCH 044/117] Downgrade FOV-dependent view distance factor to a recommendation --- apps/launcher/advancedpage.cpp | 4 +-- apps/openmw/mwrender/fogmanager.cpp | 4 +-- .../reference/modding/settings/camera.rst | 31 ++++++------------- 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 54fbbdfe0..d82dd1be2 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -62,12 +62,12 @@ namespace double convertToCells(double unitRadius) { - return std::round((unitRadius / 0.93 + 1024) / CellSizeInUnits); + return std::round((unitRadius + 1024) / CellSizeInUnits); } double convertToUnits(double CellGridRadius) { - return (CellSizeInUnits * CellGridRadius - 1024) * 0.93; + return CellSizeInUnits * CellGridRadius - 1024; } } diff --git a/apps/openmw/mwrender/fogmanager.cpp b/apps/openmw/mwrender/fogmanager.cpp index 837e6ad04..b15188292 100644 --- a/apps/openmw/mwrender/fogmanager.cpp +++ b/apps/openmw/mwrender/fogmanager.cpp @@ -76,8 +76,8 @@ namespace MWRender mLandFogStart = viewDistance * (1 - fogDepth); mLandFogEnd = viewDistance; } - mUnderwaterFogStart = std::min(viewDistance, 6666.f) * (1 - underwaterFog); - mUnderwaterFogEnd = std::min(viewDistance, 6666.f); + mUnderwaterFogStart = std::min(viewDistance, 7168.f) * (1 - underwaterFog); + mUnderwaterFogEnd = std::min(viewDistance, 7168.f); } mFogColor = color; } diff --git a/docs/source/reference/modding/settings/camera.rst b/docs/source/reference/modding/settings/camera.rst index c30dcb3f3..e15b06e74 100644 --- a/docs/source/reference/modding/settings/camera.rst +++ b/docs/source/reference/modding/settings/camera.rst @@ -51,39 +51,28 @@ viewing distance This value controls the maximum visible distance (also called the far clipping plane). Larger values significantly improve rendering in exterior spaces, but also increase the amount of rendered geometry and significantly reduce the frame rate. -Note that when cells are visible before loading (when not using a Distant Land), the geometry will "pop-in" suddenly, -creating a jarring visual effect. To prevent this effect, this value must be less than:: +Note that when cells are visible before loading, the geometry will "pop-in" suddenly, +creating a jarring visual effect. To prevent this effect, this value should not be greater than: - (CellSizeInUnits * CellGridRadius - 1024) * 0.93 + CellSizeInUnits * CellGridRadius - 1024 The CellSizeInUnits is the size of a game cell in units (8192 by default), CellGridRadius determines how many neighboring cells to current one to load (1 by default - 3x3 grid), and 1024 is the threshold distance for loading a new cell. -Additionally, the field of view setting also interacts with this setting because the view frustum end is a plane, +The field of view setting also interacts with this setting because the view frustum end is a plane, so you can see further at the edges of the screen than you should be able to. This can be observed in game by looking at distant objects and rotating the camera so the objects are near the edge of the screen. -As a result, this setting should further be reduced by a factor that depends on the field of view setting. -In the default configuration this reduction is 7%, hence the factor of 0.93 above. -Using this factor, approximate values recommended for other CellGridRadius values are: +As a result, this distance is recommended to further be reduced to avoid pop-in for wide fields of view +and long viewing distances near the edges of the screen if distant terrain and object paging are not used. -======= ======== -Cells Viewing - Distance -======= ======== -2 14285 -3 21903 -4 29522 -5 35924 -======= ======== - -Reductions of up to 25% or more can be required to completely eliminate pop-in for wide fields of view -and long viewing distances near the edges of the screen. +Reductions of up to 25% or more can be required to completely eliminate this pop-in. Such situations are unusual and probably not worth the performance penalty introduced by loading geometry obscured by fog in the center of the screen. See RenderingManager::configureFog for the relevant source code. -This setting can be adjusted in game from the ridiculously low value of 2048.0 to a maximum of 81920.0 -using the View Distance slider in the Detail tab of the Video panel of the Options menu. +This setting can be adjusted in game from the ridiculously low value of 2500 units to a maximum of 7168 units +using the View Distance slider in the Detail tab of the Video panel of the Options menu, unless distant terrain is enabled, +in which case the maximum is increased to 81920 units. field of view ------------- From 93b1b444f2a3abd13d5105b0bb35c41fc5498cd3 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Mon, 11 Jan 2021 12:53:34 +0200 Subject: [PATCH 045/117] Optimize CreateCommand and CloneCommand configuration --- apps/opencs/view/world/infocreator.cpp | 27 +++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/apps/opencs/view/world/infocreator.cpp b/apps/opencs/view/world/infocreator.cpp index 505de753c..cf1b48a19 100644 --- a/apps/opencs/view/world/infocreator.cpp +++ b/apps/opencs/view/world/infocreator.cpp @@ -34,20 +34,29 @@ void CSVWorld::InfoCreator::configureCreateCommand (CSMWorld::CreateCommand& com { CSMWorld::IdTable& table = dynamic_cast (*getData().getTableModel (getCollectionId())); + CSMWorld::CloneCommand* cloneCommand = dynamic_cast (&command); if (getCollectionId() == CSMWorld::UniversalId::Type_TopicInfos) { - command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text()); - command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Rank), -1); - command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Gender), -1); - command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_PcRank), -1); - CSMWorld::CloneCommand* cloneCommand = dynamic_cast (&command); - if (cloneCommand) cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text()); + if (!cloneCommand) + { + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text()); + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Rank), -1); + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Gender), -1); + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_PcRank), -1); + } + else + { + cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text()); + } } else { - command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); - CSMWorld::CloneCommand* cloneCommand = dynamic_cast (&command); - if (cloneCommand) cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); + if (!cloneCommand) + { + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); + } + else + cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); } } From 3087ce9c7039bb072c7587b723d0a58623e81d75 Mon Sep 17 00:00:00 2001 From: fredzio Date: Mon, 11 Jan 2021 17:56:34 +0100 Subject: [PATCH 046/117] Use saved fallheight to determine a character's jump state. The jump state initial state is "none", and it is set after physics simulation. If a save is done just above the ground, the character may land before the first run of the simulation, effectively cancelling the effect of falling. --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/character.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa3572282..5b2e1b260 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,7 @@ Bug #5703: OpenMW-CS menu system crashing on XFCE Bug #5706: AI sequences stop looping after the saved game is reloaded Bug #5731: Editor: skirts are invisible on characters + Bug #5739: Saving and loading the save a second or two before hitting the ground doesn't count fall damage Bug #5758: Paralyzed actors behavior is inconsistent with vanilla Bug #5762: Movement solver is insufficiently robust Feature #390: 3rd person look "over the shoulder" diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index b9bfe6bc0..53c8e4a8b 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -883,7 +883,11 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim } if(!cls.getCreatureStats(mPtr).isDead()) + { mIdleState = CharState_Idle; + if (cls.getCreatureStats(mPtr).getFallHeight() > 0) + mJumpState = JumpState_InAir; + } else { const MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr); From 70087e16feaeda3bdd8789b209d0f7cdbca75044 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Mon, 11 Jan 2021 13:43:44 +0200 Subject: [PATCH 047/117] Disable dialogue info table sorting --- apps/opencs/view/world/subviews.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 3e72f9a9e..169bc2e94 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -75,10 +75,10 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) new CSVDoc::SubViewFactoryWithCreator); manager.add (CSMWorld::UniversalId::Type_TopicInfos, - new CSVDoc::SubViewFactoryWithCreator); + new CSVDoc::SubViewFactoryWithCreator(false)); manager.add (CSMWorld::UniversalId::Type_JournalInfos, - new CSVDoc::SubViewFactoryWithCreator); + new CSVDoc::SubViewFactoryWithCreator(false)); manager.add (CSMWorld::UniversalId::Type_Pathgrids, new CSVDoc::SubViewFactoryWithCreator); From 390314215225099ef09ef6c10b7365b587d3767b Mon Sep 17 00:00:00 2001 From: psi29a Date: Tue, 12 Jan 2021 12:05:17 +0000 Subject: [PATCH 048/117] Update apps/openmw/mwbase/environment.cpp --- apps/openmw/mwbase/environment.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index edb10d945..d639dd4c6 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -19,7 +19,7 @@ MWBase::Environment *MWBase::Environment::sThis = nullptr; MWBase::Environment::Environment() -: mWorld (nullptr), mSoundManager (nullptr), mScriptManager (nullptr), mWindowManager (nullptr), +: mWorld (nullptr), mSoundManager (nullptr), mScriptManager (nullptr), mWindowManager (nullptr), mResourceSystem (nullptr), mMechanicsManager (nullptr), mDialogueManager (nullptr), mJournal (nullptr), mInputManager (nullptr), mStateManager (nullptr), mFrameDuration (0), mFrameRateLimit(0.f) { From 4638fc36b46c8a0961f84ae5c3a026c59e72a7e5 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Tue, 12 Jan 2021 15:46:19 +0300 Subject: [PATCH 049/117] Allow all creatures to float to the water surface --- apps/openmw/mwmechanics/character.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index abff30bb9..172ddb25c 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2448,7 +2448,7 @@ void CharacterController::update(float duration) } } - if (mFloatToSurface && cls.isActor() && cls.canSwim(mPtr)) + if (mFloatToSurface && cls.isActor()) { if (cls.getCreatureStats(mPtr).isDead() || (!godmode && cls.getCreatureStats(mPtr).isParalyzed())) From 73740013a30cece6cb99f0ea9312bb95dd96d94f Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 12 Jan 2021 19:58:46 +0100 Subject: [PATCH 050/117] mResourceSystem initialization reorder --- apps/openmw/mwbase/environment.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index d639dd4c6..9014fafff 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -19,9 +19,9 @@ MWBase::Environment *MWBase::Environment::sThis = nullptr; MWBase::Environment::Environment() -: mWorld (nullptr), mSoundManager (nullptr), mScriptManager (nullptr), mWindowManager (nullptr), mResourceSystem (nullptr), - mMechanicsManager (nullptr), mDialogueManager (nullptr), mJournal (nullptr), mInputManager (nullptr), mStateManager (nullptr), - mFrameDuration (0), mFrameRateLimit(0.f) +: mWorld (nullptr), mSoundManager (nullptr), mScriptManager (nullptr), mWindowManager (nullptr), + mMechanicsManager (nullptr), mDialogueManager (nullptr), mJournal (nullptr), mInputManager (nullptr), + mStateManager (nullptr), mResourceSystem (nullptr), mFrameDuration (0), mFrameRateLimit(0.f) { assert (!sThis); sThis = this; From 823e7bea382ef028643b114dfb85d49a147b2e6e Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Wed, 13 Jan 2021 02:48:54 +0000 Subject: [PATCH 051/117] Fix MyGUI detection `libfind_pkg_detect` used `pkg_check_modules`, which requires all the given modules to be found. This means it always failed for MyGUI, which passes `MyGUI${MYGUI_STATIC_SUFFIX} MYGUI${MYGUI_STATIC_SUFFIX}` to it. Replaces `pkg_check_modules` with `pkg_search_module`, which finds the first match instead. --- cmake/LibFindMacros.cmake | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmake/LibFindMacros.cmake b/cmake/LibFindMacros.cmake index 2be27c5fc..3044601f6 100644 --- a/cmake/LibFindMacros.cmake +++ b/cmake/LibFindMacros.cmake @@ -19,11 +19,11 @@ macro (libfind_package PREFIX PKG) endmacro() # A simple wrapper to make pkg-config searches a bit easier. -# Works the same as CMake's internal pkg_check_modules but is always quiet. -macro (libfind_pkg_check_modules) +# Works the same as CMake's internal pkg_search_module but is always quiet. +macro (libfind_pkg_search_module) find_package(PkgConfig QUIET) if (PKG_CONFIG_FOUND) - pkg_check_modules(${ARGN} QUIET) + pkg_search_module(${ARGN} QUIET) endif() endmacro() @@ -47,7 +47,7 @@ function (libfind_pkg_detect PREFIX) message(FATAL_ERROR "libfind_pkg_detect requires at least a pkg_config package name to be passed.") endif() # Find library - libfind_pkg_check_modules(${PREFIX}_PKGCONF ${pkgargs}) + libfind_pkg_search_module(${PREFIX}_PKGCONF ${pkgargs}) if (pathargs) find_path(${PREFIX}_INCLUDE_DIR NAMES ${pathargs} HINTS ${${PREFIX}_PKGCONF_INCLUDE_DIRS}) endif() From f175beb3047d97dffabc3caa9ffc57746760060a Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Jan 2021 13:33:46 +0400 Subject: [PATCH 052/117] Define template ref classes in components --- apps/openmw/mwrender/objectpaging.cpp | 13 ++----------- components/resource/scenemanager.cpp | 18 ++++-------------- components/resource/scenemanager.hpp | 25 +++++++++++++++++++++++++ components/sceneutil/serialize.cpp | 1 + 4 files changed, 32 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index a5015f377..d8e856e76 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -249,15 +249,6 @@ namespace MWRender } }; - class TemplateRef : public osg::Object - { - public: - TemplateRef() {} - TemplateRef(const TemplateRef& copy, const osg::CopyOp&) : mObjects(copy.mObjects) {} - META_Object(MWRender, TemplateRef) - std::vector> mObjects; - }; - class RefnumSet : public osg::Object { public: @@ -530,7 +521,7 @@ namespace MWRender osg::ref_ptr group = new osg::Group; osg::ref_ptr mergeGroup = new osg::Group; - osg::ref_ptr templateRefs = new TemplateRef; + osg::ref_ptr templateRefs = new Resource::TemplateMultiRef; osgUtil::StateToCompile stateToCompile(0, nullptr); CopyOp copyop; for (const auto& pair : nodes) @@ -596,7 +587,7 @@ namespace MWRender if (numinstances > 0) { // add a ref to the original template, to hint to the cache that it's still being used and should be kept in cache - templateRefs->mObjects.emplace_back(cnode); + templateRefs->addRef(cnode); if (pair.second.mNeedCompile) { diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 2630cd453..e75fa4f74 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -110,6 +110,10 @@ namespace namespace Resource { + void TemplateMultiRef::addRef(const osg::Node* node) + { + mObjects.emplace_back(node); + } class SharedStateManager : public osgDB::SharedStateManager { @@ -554,20 +558,6 @@ namespace Resource return node; } - class TemplateRef : public osg::Object - { - public: - TemplateRef(const Object* object) - : mObject(object) {} - TemplateRef() {} - TemplateRef(const TemplateRef& copy, const osg::CopyOp&) : mObject(copy.mObject) {} - - META_Object(Resource, TemplateRef) - - private: - osg::ref_ptr mObject; - }; - osg::ref_ptr SceneManager::createInstance(const std::string& name) { osg::ref_ptr scene = getTemplate(name); diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index fd75070a1..e897566a8 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -37,6 +37,31 @@ namespace Shader namespace Resource { + class TemplateRef : public osg::Object + { + public: + TemplateRef(const Object* object) : mObject(object) {} + TemplateRef() {} + TemplateRef(const TemplateRef& copy, const osg::CopyOp&) : mObject(copy.mObject) {} + + META_Object(Resource, TemplateRef) + + private: + osg::ref_ptr mObject; + }; + + class TemplateMultiRef : public osg::Object + { + public: + TemplateMultiRef() {} + TemplateMultiRef(const TemplateMultiRef& copy, const osg::CopyOp&) : mObjects(copy.mObjects) {} + void addRef(const osg::Node* node); + + META_Object(Resource, TemplateMultiRef) + + private: + std::vector> mObjects; + }; class MultiObjectCache; diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index d03612fc1..7e176be3d 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -121,6 +121,7 @@ void registerSerializers() const char* ignore[] = { "MWRender::PtrHolder", "Resource::TemplateRef", + "Resource::TemplateMultiRef", "SceneUtil::CompositeStateSetUpdater", "SceneUtil::LightListCallback", "SceneUtil::LightManagerUpdateCallback", From 0418e8e7a6eccef17a6995470d4f9ce2e95d1106 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Jan 2021 13:41:02 +0400 Subject: [PATCH 053/117] Add an API to get base wind speed (which is from openmw.cfg) --- apps/openmw/mwrender/sky.cpp | 9 +++++++++ apps/openmw/mwrender/sky.hpp | 4 ++++ apps/openmw/mwworld/weather.cpp | 2 ++ 3 files changed, 15 insertions(+) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 2920e07dd..93061022c 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -1134,6 +1134,7 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana , mRainEntranceSpeed(1) , mRainMaxRaindrops(0) , mWindSpeed(0.f) + , mBaseWindSpeed(0.f) , mEnabled(true) , mSunEnabled(true) , mWeatherAlpha(0.f) @@ -1685,6 +1686,7 @@ void SkyManager::setWeather(const WeatherResult& weather) mRainMaxHeight = weather.mRainMaxHeight; mRainSpeed = weather.mRainSpeed; mWindSpeed = weather.mWindSpeed; + mBaseWindSpeed = weather.mBaseWindSpeed; if (mRainEffect != weather.mRainEffect) { @@ -1853,6 +1855,13 @@ void SkyManager::setWeather(const WeatherResult& weather) fader->setAlpha(weather.mEffectFade); } +float SkyManager::getBaseWindSpeed() const +{ + if (!mCreated) return 0.f; + + return mBaseWindSpeed; +} + void SkyManager::sunEnable() { if (!mCreated) return; diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index cf697bd44..2ec134d09 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -70,6 +70,7 @@ namespace MWRender float mDLFogOffset; float mWindSpeed; + float mBaseWindSpeed; float mCurrentWindSpeed; float mNextWindSpeed; @@ -181,6 +182,8 @@ namespace MWRender void setRainIntensityUniform(osg::Uniform *uniform); + float getBaseWindSpeed() const; + private: void create(); ///< no need to call this, automatically done on first enable() @@ -265,6 +268,7 @@ namespace MWRender float mRainEntranceSpeed; int mRainMaxRaindrops; float mWindSpeed; + float mBaseWindSpeed; bool mEnabled; bool mSunEnabled; diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 6a4a227a4..415e69d20 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -1123,6 +1123,7 @@ inline void WeatherManager::calculateResult(const int weatherID, const float gam mResult.mCloudBlendFactor = 0; mResult.mNextWindSpeed = 0; mResult.mWindSpeed = mResult.mCurrentWindSpeed = calculateWindSpeed(weatherID, mWindSpeed); + mResult.mBaseWindSpeed = mWeatherSettings[weatherID].mWindSpeed; mResult.mCloudSpeed = current.mCloudSpeed; mResult.mGlareView = current.mGlareView; @@ -1214,6 +1215,7 @@ inline void WeatherManager::calculateTransitionResult(const float factor, const mResult.mCurrentWindSpeed = calculateWindSpeed(mCurrentWeather, mCurrentWindSpeed); mResult.mNextWindSpeed = calculateWindSpeed(mNextWeather, mNextWindSpeed); + mResult.mBaseWindSpeed = lerp(current.mBaseWindSpeed, other.mBaseWindSpeed, factor); mResult.mWindSpeed = lerp(mResult.mCurrentWindSpeed, mResult.mNextWindSpeed, factor); mResult.mCloudSpeed = lerp(current.mCloudSpeed, other.mCloudSpeed, factor); From 89f3f860ed99ce1b19c4ee3ec07373ad2f4cdcf1 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Jan 2021 13:57:05 +0400 Subject: [PATCH 054/117] Allow to get a rotation vector from ESM::Position --- components/esm/defs.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index 0f9cefab1..9bf9b01f3 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -54,6 +54,11 @@ struct Position { return osg::Vec3f(pos[0], pos[1], pos[2]); } + + osg::Vec3f asRotationVec3() const + { + return osg::Vec3f(rot[0], rot[1], rot[2]); + } }; #pragma pack(pop) From 16e03c151a783fc9902bce6cde73026c58cb6ddc Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 13 Jan 2021 15:38:29 +0200 Subject: [PATCH 055/117] Implement basic move algorithm, connect it to drag&drop --- apps/opencs/view/world/dragdroputils.cpp | 10 +++- apps/opencs/view/world/dragdroputils.hpp | 3 + apps/opencs/view/world/dragrecordtable.cpp | 7 ++- apps/opencs/view/world/dragrecordtable.hpp | 5 ++ apps/opencs/view/world/table.cpp | 70 ++++++++++++++++++++++ apps/opencs/view/world/table.hpp | 2 + 6 files changed, 95 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/world/dragdroputils.cpp b/apps/opencs/view/world/dragdroputils.cpp index 808125a60..bb4d97273 100644 --- a/apps/opencs/view/world/dragdroputils.cpp +++ b/apps/opencs/view/world/dragdroputils.cpp @@ -15,7 +15,15 @@ bool CSVWorld::DragDropUtils::canAcceptData(const QDropEvent &event, CSMWorld::C return data != nullptr && data->holdsType(type); } -CSMWorld::UniversalId CSVWorld::DragDropUtils::getAcceptedData(const QDropEvent &event, +bool CSVWorld::DragDropUtils::isInfo(const QDropEvent &event, CSMWorld::ColumnBase::Display type) +{ + const CSMWorld::TableMimeData *data = getTableMimeData(event); + return data != nullptr && ( + data->holdsType(CSMWorld::UniversalId::Type_TopicInfo) || + data->holdsType(CSMWorld::UniversalId::Type_JournalInfo) ); +} + +CSMWorld::UniversalId CSVWorld::DragDropUtils::getAcceptedData(const QDropEvent &event, CSMWorld::ColumnBase::Display type) { if (canAcceptData(event, type)) diff --git a/apps/opencs/view/world/dragdroputils.hpp b/apps/opencs/view/world/dragdroputils.hpp index d1d780708..2181e7606 100644 --- a/apps/opencs/view/world/dragdroputils.hpp +++ b/apps/opencs/view/world/dragdroputils.hpp @@ -20,6 +20,9 @@ namespace CSVWorld bool canAcceptData(const QDropEvent &event, CSMWorld::ColumnBase::Display type); ///< Checks whether the \a event contains a valid CSMWorld::TableMimeData that holds the \a type + bool isInfo(const QDropEvent &event, CSMWorld::ColumnBase::Display type); + ///< Info types can be dragged to sort the info table + CSMWorld::UniversalId getAcceptedData(const QDropEvent &event, CSMWorld::ColumnBase::Display type); ///< Gets the accepted data from the \a event using the \a type ///< \return Type_None if the \a event data doesn't holds the \a type diff --git a/apps/opencs/view/world/dragrecordtable.cpp b/apps/opencs/view/world/dragrecordtable.cpp index f84bf639d..58041af9f 100644 --- a/apps/opencs/view/world/dragrecordtable.cpp +++ b/apps/opencs/view/world/dragrecordtable.cpp @@ -46,7 +46,8 @@ void CSVWorld::DragRecordTable::dragEnterEvent(QDragEnterEvent *event) void CSVWorld::DragRecordTable::dragMoveEvent(QDragMoveEvent *event) { QModelIndex index = indexAt(event->pos()); - if (CSVWorld::DragDropUtils::canAcceptData(*event, getIndexDisplayType(index))) + if (CSVWorld::DragDropUtils::canAcceptData(*event, getIndexDisplayType(index)) || + CSVWorld::DragDropUtils::isInfo(*event, getIndexDisplayType(index)) ) { if (index.flags() & Qt::ItemIsEditable) { @@ -75,6 +76,10 @@ void CSVWorld::DragRecordTable::dropEvent(QDropEvent *event) } } } + else if (CSVWorld::DragDropUtils::isInfo(*event, display) && event->source() == this) + { + emit moveRecordsFromSameTable(event); + } } CSMWorld::ColumnBase::Display CSVWorld::DragRecordTable::getIndexDisplayType(const QModelIndex &index) const diff --git a/apps/opencs/view/world/dragrecordtable.hpp b/apps/opencs/view/world/dragrecordtable.hpp index a6b6756aa..f6c3fa890 100644 --- a/apps/opencs/view/world/dragrecordtable.hpp +++ b/apps/opencs/view/world/dragrecordtable.hpp @@ -23,6 +23,8 @@ namespace CSVWorld { class DragRecordTable : public QTableView { + Q_OBJECT + protected: CSMDoc::Document& mDocument; bool mEditLock; @@ -45,6 +47,9 @@ namespace CSVWorld private: CSMWorld::ColumnBase::Display getIndexDisplayType(const QModelIndex &index) const; + + signals: + void moveRecordsFromSameTable(QDropEvent *event); }; } diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index e5f4e36c5..03d7dbbfa 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -248,6 +249,7 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, if (isInfoTable) { mProxyModel = new CSMWorld::InfoTableProxyModel(id.getType(), this); + connect (this, &CSVWorld::DragRecordTable::moveRecordsFromSameTable, this, &CSVWorld::Table::moveRecords); } else if (isLtexTable) { @@ -563,6 +565,74 @@ void CSVWorld::Table::moveDownRecord() } } +void CSVWorld::Table::moveRecords(QDropEvent *event) +{ + if (mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) + return; + + QModelIndex targedIndex = indexAt(event->pos()); + + QModelIndexList selectedRows = selectionModel()->selectedRows(); + int targetRow = targedIndex.row(); + int baseRow = targedIndex.row() - 1; + int highestDifference = 0; + + for (const auto& thisRowData : selectedRows) + { + if (std::abs(targetRow - thisRowData.row()) > highestDifference) highestDifference = std::abs(targetRow - thisRowData.row()); + if (thisRowData.row() - 1 < baseRow) baseRow = thisRowData.row() - 1; + } + + std::vector newOrder (highestDifference + 1); + + for (long unsigned int i = 0; i < newOrder.size(); ++i) + { + newOrder[i] = i; + } + + if (selectedRows.size() > 1) + { + Log(Debug::Warning) << "Move operation failed: Moving multiple selections isn't implemented."; + return; + } + + for (const auto& thisRowData : selectedRows) + { + /* + Moving algorithm description + a) Remove the (ORIGIN + 1)th list member. + b) Add (ORIGIN+1)th list member with value TARGET + c) If ORIGIN > TARGET,d_INC; ELSE d_DEC + d_INC) increase all members after (and including) the TARGET by one, stop before hitting ORIGINth address + d_DEC) decrease all members after the ORIGIN by one, stop after hitting address TARGET + */ + + int originRow = thisRowData.row(); + //int sourceMappedOriginRow = mProxyModel->mapToSource (mProxyModel->index (originRow, 0)).row(); + + newOrder.erase(newOrder.begin() + originRow - baseRow - 1); + newOrder.emplace(newOrder.begin() + originRow - baseRow - 1, targetRow - baseRow - 1); + + if (originRow > targetRow) + { + for (int i = targetRow - baseRow - 1; i < originRow - baseRow - 1; ++i) + { + ++newOrder[i]; + } + } + else + { + for (int i = originRow - baseRow; i <= targetRow - baseRow - 1; ++i) + { + --newOrder[i]; + } + } + + } + mDocument.getUndoStack().push (new CSMWorld::ReorderRowsCommand ( + dynamic_cast (*mModel), baseRow + 1, newOrder)); +} + void CSVWorld::Table::editCell() { emit editRequest(mEditIdAction->getCurrentId(), ""); diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 61dd57c06..9c4b9b5e5 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -141,6 +141,8 @@ namespace CSVWorld void moveDownRecord(); + void moveRecords(QDropEvent *event); + void viewRecord(); void previewRecord(); From f2fc02cdff69cabafa00a33de95bd942511d2eb7 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 13 Jan 2021 15:55:16 +0200 Subject: [PATCH 056/117] Support filtered tables (mapToSource for indexes) --- apps/opencs/view/world/table.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 03d7dbbfa..c676a5ecc 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -573,14 +573,17 @@ void CSVWorld::Table::moveRecords(QDropEvent *event) QModelIndex targedIndex = indexAt(event->pos()); QModelIndexList selectedRows = selectionModel()->selectedRows(); - int targetRow = targedIndex.row(); - int baseRow = targedIndex.row() - 1; + int targetRowRaw = targedIndex.row(); + int targetRow = mProxyModel->mapToSource (mProxyModel->index (targetRowRaw, 0)).row(); + int baseRowRaw = targedIndex.row() - 1; + int baseRow = mProxyModel->mapToSource (mProxyModel->index (baseRowRaw, 0)).row(); int highestDifference = 0; for (const auto& thisRowData : selectedRows) { - if (std::abs(targetRow - thisRowData.row()) > highestDifference) highestDifference = std::abs(targetRow - thisRowData.row()); - if (thisRowData.row() - 1 < baseRow) baseRow = thisRowData.row() - 1; + int thisRow = mProxyModel->mapToSource (mProxyModel->index (thisRowData.row(), 0)).row(); + if (std::abs(targetRow - thisRow) > highestDifference) highestDifference = std::abs(targetRow - thisRow); + if (thisRow - 1 < baseRow) baseRow = thisRow - 1; } std::vector newOrder (highestDifference + 1); @@ -607,8 +610,8 @@ void CSVWorld::Table::moveRecords(QDropEvent *event) d_DEC) decrease all members after the ORIGIN by one, stop after hitting address TARGET */ - int originRow = thisRowData.row(); - //int sourceMappedOriginRow = mProxyModel->mapToSource (mProxyModel->index (originRow, 0)).row(); + int originRowRaw = thisRowData.row(); + int originRow = mProxyModel->mapToSource (mProxyModel->index (originRowRaw, 0)).row(); newOrder.erase(newOrder.begin() + originRow - baseRow - 1); newOrder.emplace(newOrder.begin() + originRow - baseRow - 1, targetRow - baseRow - 1); From 5740258d3b006977a5a8fa78f71b4cbb5d11f837 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 13 Jan 2021 16:23:59 +0200 Subject: [PATCH 057/117] Add changelog entry --- CHANGELOG.md | 1 + CHANGELOG_PR.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 943dcbe89..b628a847a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Bug #4055: Local scripts don't inherit variables from their base record Bug #4083: Door animation freezes when colliding with actors Bug #4247: Cannot walk up stairs in Ebonheart docks + Bug #4357: OpenMW-CS: TopicInfos index sorting and rearranging isn't fully functional Bug #4363: Editor: Defect in Clone Function for Dialogue Info records Bug #4447: Actor collision capsule shape allows looking through some walls Bug #4465: Collision shape overlapping causes twitching diff --git a/CHANGELOG_PR.md b/CHANGELOG_PR.md index f70fbad57..c7abb6fec 100644 --- a/CHANGELOG_PR.md +++ b/CHANGELOG_PR.md @@ -35,6 +35,7 @@ Bug Fixes: Editor Bug Fixes: - Deleted and moved objects within a cell are now saved properly (#832) +- Disabled record sorting in Topic and Journal Info tables, implemented drag-move for records (#4357) - Topic and Journal Info records can now be cloned with a different parent Topic/Journal Id (#4363) - Verifier no longer checks for alleged 'race' entries in clothing body parts (#5400) - Loading mods now keeps the master index (#5675) From d24a5f7b89a775baa09febcfd960138ae36eaf3e Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Wed, 13 Jan 2021 20:45:34 +0000 Subject: [PATCH 058/117] Fix OSG USE_GRAPHICSWINDOW check When OSG is built with `-DOSG_WINDOWING_SYSTEM=None`, this macro does not exist. Replaces the Android-specific check with a general one. --- components/sdlutil/sdlcursormanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sdlutil/sdlcursormanager.cpp b/components/sdlutil/sdlcursormanager.cpp index 56225868e..964762ec7 100644 --- a/components/sdlutil/sdlcursormanager.cpp +++ b/components/sdlutil/sdlcursormanager.cpp @@ -20,7 +20,7 @@ #include "imagetosurface.hpp" -#if defined(OSG_LIBRARY_STATIC) && !defined(ANDROID) +#if defined(OSG_LIBRARY_STATIC) && defined(USE_GRAPHICSWINDOW) // Sets the default windowing system interface according to the OS. // Necessary for OpenSceneGraph to do some things, like decompression. USE_GRAPHICSWINDOW() From 24d8412c0cab3b1eb146389168ec601acf222537 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Thu, 14 Jan 2021 02:52:59 +0000 Subject: [PATCH 059/117] cmake: Fix missing OPENGL_INCLUDE_DIR OPENGL_INCLUDE_DIR wasn't being included. It is usually just /usr/include but if it was custom the build failed. --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a615e843..226511b52 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -323,6 +323,7 @@ include_directories("." ${Boost_INCLUDE_DIR} ${MyGUI_INCLUDE_DIRS} ${OPENAL_INCLUDE_DIR} + ${OPENGL_INCLUDE_DIR} ${BULLET_INCLUDE_DIRS} ) From 11dfb9daff116ae9e1ccda971ebb8852831bfc6e Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Fri, 15 Jan 2021 15:11:50 +0000 Subject: [PATCH 060/117] cmake/FindOSGPlugins: Support lib-prefixed plugins With a regular OSG build, the plugin names are prefixed with `lib`, e.g. `libosgdb_jpeg.a` for a static build. However, on Debian on Ubuntu they are not. With this commit we now try both options. Fixes #5972 Signed-off-by: Gleb Mazovetskiy --- cmake/FindOSGPlugins.cmake | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cmake/FindOSGPlugins.cmake b/cmake/FindOSGPlugins.cmake index c210466c0..457abf665 100644 --- a/cmake/FindOSGPlugins.cmake +++ b/cmake/FindOSGPlugins.cmake @@ -27,9 +27,12 @@ foreach(_library ${OSGPlugins_FIND_COMPONENTS}) string(TOUPPER ${_library} _library_uc) set(_component OSGPlugins_${_library}) - set(${_library_uc}_DIR ${OSGPlugins_LIB_DIR}) # to help function osg_find_library + # On some systems, notably Debian and Ubuntu, the OSG plugins do not have + # the usual "lib" prefix. We temporarily add the empty string to the list + # of prefixes CMake searches for (via osg_find_library) to support these systems. set(_saved_lib_prefix ${CMAKE_FIND_LIBRARY_PREFIXES}) # save CMAKE_FIND_LIBRARY_PREFIXES - set(CMAKE_FIND_LIBRARY_PREFIXES "") # search libraries with no prefix + list(APPEND CMAKE_FIND_LIBRARY_PREFIXES "") # search libraries with no prefix + set(${_library_uc}_DIR ${OSGPlugins_LIB_DIR}) # to help function osg_find_library osg_find_library(${_library_uc} ${_library}) # find it into ${_library_uc}_LIBRARIES set(CMAKE_FIND_LIBRARY_PREFIXES ${_saved_lib_prefix}) # restore prefix From 4974b64cbf4f54d446386a0303962d08cd518026 Mon Sep 17 00:00:00 2001 From: psi29a Date: Sun, 17 Jan 2021 15:58:43 +0000 Subject: [PATCH 061/117] Update CI/before_install.osx.sh --- CI/before_install.osx.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index 85434fa06..ba0ff00ca 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -1,5 +1,9 @@ #!/bin/sh -e +# workaround python issue on travis +HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.8 || true +HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.9 || true + # Some of these tools can come from places other than brew, so check before installing command -v ccache >/dev/null 2>&1 || brew install ccache command -v cmake >/dev/null 2>&1 || brew install cmake From e37e5d4d16fd3240bd57355df40abc889e3ce8db Mon Sep 17 00:00:00 2001 From: fredzio Date: Mon, 18 Jan 2021 17:42:03 +0100 Subject: [PATCH 062/117] Don't run unstuck if there is no simulation running in async case. In this case, the actor mPreviousPosition is not updated, so the actor position is interpolated between an old (stucked) position and the new (unstucked) position. The new position is most likely "stucked", so the unstuck code strikes again, making the actor "vibrates". That's exactly what the sync code path does, and it doesn't exhibit this behavior. --- apps/openmw/mwphysics/mtphysics.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 2781a5b1b..6c7c573a4 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -173,6 +173,8 @@ namespace MWPhysics { if (mDeferAabbUpdate) updateAabbs(); + if (!mRemainingSteps) + return; for (auto& data : mActorsFrameData) if (data.mActor.lock()) { From 54ea8eb5c79d4a6bb88159cd6a67ce0dd4e17235 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Mon, 18 Jan 2021 19:21:02 +0200 Subject: [PATCH 063/117] Fix string corruption with Qt on linux-systems --- apps/opencs/main.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index 5287c8b19..c7d57a256 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -63,6 +63,9 @@ int runApplication(int argc, char *argv[]) application.setWindowIcon (QIcon (":./openmw-cs.png")); CS::Editor editor(argc, argv); +#ifdef __linux__ + setlocale(LC_NUMERIC,"C"); +#endif if(!editor.makeIPCServer()) { From 9f0f3eaeb2224a725fee002b986ebd56eb02b57c Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Mon, 18 Jan 2021 19:22:01 +0200 Subject: [PATCH 064/117] Add collada to supported formats --- apps/opencs/model/world/resourcesmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/resourcesmanager.cpp b/apps/opencs/model/world/resourcesmanager.cpp index 1af9c5e9b..378ba7c6b 100644 --- a/apps/opencs/model/world/resourcesmanager.cpp +++ b/apps/opencs/model/world/resourcesmanager.cpp @@ -17,7 +17,7 @@ void CSMWorld::ResourcesManager::addResources (const Resources& resources) const char * const * CSMWorld::ResourcesManager::getMeshExtensions() { // maybe we could go over the osgDB::Registry to list all supported node formats - static const char * const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2", 0 }; + static const char * const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2", "dae", 0 }; return sMeshTypes; } From 0acae08e526846838f8e5f5f0673e3af7cca4d83 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Mon, 18 Jan 2021 19:24:38 +0200 Subject: [PATCH 065/117] Add changelog entry --- CHANGELOG.md | 1 + CHANGELOG_PR.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75fd4cbe3..47cfd0031 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,7 @@ Bug #5695: ExplodeSpell for actors doesn't target the ground Bug #5703: OpenMW-CS menu system crashing on XFCE Bug #5706: AI sequences stop looping after the saved game is reloaded + Bug #5713: OpenMW-CS: Collada models are corrupted in Qt-based scene view Bug #5731: Editor: skirts are invisible on characters Bug #5739: Saving and loading the save a second or two before hitting the ground doesn't count fall damage Bug #5758: Paralyzed actors behavior is inconsistent with vanilla diff --git a/CHANGELOG_PR.md b/CHANGELOG_PR.md index f70fbad57..944b26044 100644 --- a/CHANGELOG_PR.md +++ b/CHANGELOG_PR.md @@ -39,6 +39,7 @@ Editor Bug Fixes: - Verifier no longer checks for alleged 'race' entries in clothing body parts (#5400) - Loading mods now keeps the master index (#5675) - Flicker and crashing on XFCE4 fixed (#5703) +- Collada models render properly in the Editor (#5713) Miscellaneous: - Prevent save-game bloating by using an appropriate fog texture format (#5108) From 8af8ad38406c35182dcb23a3c511be007a87dd9c Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 20 Jan 2021 01:17:16 +0000 Subject: [PATCH 066/117] Always write opaque fragments instead of relying on blending being off for translucent RTT --- apps/openmw/mwrender/characterpreview.cpp | 26 +++++++++++++++++++++-- apps/openmw/mwrender/renderingmanager.cpp | 1 + files/shaders/objects_fragment.glsl | 4 ++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 89db3e5f4..f21e667eb 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -85,7 +86,7 @@ namespace MWRender class SetUpBlendVisitor : public osg::NodeVisitor { public: - SetUpBlendVisitor(): osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + SetUpBlendVisitor(): osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mNoAlphaUniform(new osg::Uniform("noAlpha", false)) { } @@ -102,10 +103,17 @@ namespace MWRender newStateSet->setAttribute(newBlendFunc, osg::StateAttribute::ON); node.setStateSet(newStateSet); } - + if (stateset->getMode(GL_BLEND) & osg::StateAttribute::ON) + { + // Disable noBlendAlphaEnv + stateset->setTextureMode(7, GL_TEXTURE_2D, osg::StateAttribute::OFF); + stateset->addUniform(mNoAlphaUniform); + } } traverse(node); } + private: + osg::ref_ptr mNoAlphaUniform; }; CharacterPreview::CharacterPreview(osg::Group* parent, Resource::ResourceSystem* resourceSystem, @@ -164,6 +172,20 @@ namespace MWRender fog->setEnd(10000000); stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); + // Opaque stuff must have 1 as its fragment alpha as the FBO is translucent, so having blending off isn't enough + osg::ref_ptr noBlendAlphaEnv = new osg::TexEnvCombine(); + noBlendAlphaEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); + noBlendAlphaEnv->setSource0_Alpha(osg::TexEnvCombine::CONSTANT); + noBlendAlphaEnv->setConstantColor(osg::Vec4(0.0, 0.0, 0.0, 1.0)); + noBlendAlphaEnv->setCombine_RGB(osg::TexEnvCombine::REPLACE); + noBlendAlphaEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + osg::ref_ptr dummyTexture = new osg::Texture2D(); + dummyTexture->setInternalFormat(GL_RED); + dummyTexture->setTextureSize(1, 1); + stateset->setTextureAttributeAndModes(7, dummyTexture, osg::StateAttribute::ON); + stateset->setTextureAttribute(7, noBlendAlphaEnv, osg::StateAttribute::ON); + stateset->addUniform(new osg::Uniform("noAlpha", true)); + osg::ref_ptr lightmodel = new osg::LightModel; lightmodel->setAmbientIntensity(osg::Vec4(0.0, 0.0, 0.0, 1.0)); stateset->setAttributeAndModes(lightmodel, osg::StateAttribute::ON); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6ce431d2e..e1f6f0dd9 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -370,6 +370,7 @@ namespace MWRender mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("simpleWater", false)); + mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("noAlpha", false)); mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near"); mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far"); diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 78660685f..bd2bd5909 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -50,6 +50,7 @@ uniform mat2 bumpMapMatrix; #endif uniform bool simpleWater; +uniform bool noAlpha; varying float euclideanDepth; varying float linearDepth; @@ -208,5 +209,8 @@ void main() #endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); + if (noAlpha) + gl_FragData[0].a = 1.0; + applyShadowDebugOverlay(); } From 35fab974783e6a3f3b384ec09ce31f22432c89eb Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 20 Jan 2021 01:24:05 +0000 Subject: [PATCH 067/117] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75fd4cbe3..ce1fb07e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ Bug #5370: Opening an unlocked but trapped door uses the key Bug #5384: openmw-cs: deleting an instance requires reload of scene window to show in editor Bug #5387: Move/MoveWorld don't update the object's cell properly + Bug #5391: Races Redone 1.2 bodies don't show on the inventory Bug #5397: NPC greeting does not reset if you leave + reenter area Bug #5400: Editor: Verifier checks race of non-skin bodyparts Bug #5403: Enchantment effect doesn't show on an enemy during death animation From b6e92c9c6deedfb9c4a4acfd106d57eb3e1628e8 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 20 Jan 2021 23:37:19 +0000 Subject: [PATCH 068/117] Use ShaderVisitor to skip translucent framebuffer specific stuff --- apps/openmw/mwrender/characterpreview.cpp | 3 +++ apps/openmw/mwrender/renderingmanager.cpp | 1 - components/resource/scenemanager.cpp | 7 ++++--- components/resource/scenemanager.hpp | 4 ++-- components/shader/shadervisitor.cpp | 10 +++++++++- components/shader/shadervisitor.hpp | 4 ++++ files/shaders/objects_fragment.glsl | 3 +++ 7 files changed, 25 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index f21e667eb..14735050c 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -16,6 +16,8 @@ #include #include +#include +#include #include #include @@ -249,6 +251,7 @@ namespace MWRender void CharacterPreview::setBlendMode() { + mResourceSystem->getSceneManager()->recreateShaders(mNode, "objects", true); SetUpBlendVisitor visitor; mNode->accept(visitor); } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index e1f6f0dd9..6ce431d2e 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -370,7 +370,6 @@ namespace MWRender mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("simpleWater", false)); - mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("noAlpha", false)); mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near"); mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far"); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index e75fa4f74..d937b992b 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -247,9 +247,9 @@ namespace Resource return mForceShaders; } - void SceneManager::recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix) + void SceneManager::recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix, bool translucentFramebuffer) { - osg::ref_ptr shaderVisitor(createShaderVisitor(shaderPrefix)); + osg::ref_ptr shaderVisitor(createShaderVisitor(shaderPrefix, translucentFramebuffer)); shaderVisitor->setAllowedToModifyStateSets(false); node->accept(*shaderVisitor); } @@ -749,7 +749,7 @@ namespace Resource stats->setAttribute(frameNumber, "Node Instance", mInstanceCache->getCacheSize()); } - Shader::ShaderVisitor *SceneManager::createShaderVisitor(const std::string& shaderPrefix) + Shader::ShaderVisitor *SceneManager::createShaderVisitor(const std::string& shaderPrefix, bool translucentFramebuffer) { Shader::ShaderVisitor* shaderVisitor = new Shader::ShaderVisitor(*mShaderManager.get(), *mImageManager, shaderPrefix+"_vertex.glsl", shaderPrefix+"_fragment.glsl"); shaderVisitor->setForceShaders(mForceShaders); @@ -759,6 +759,7 @@ namespace Resource shaderVisitor->setAutoUseSpecularMaps(mAutoUseSpecularMaps); shaderVisitor->setSpecularMapPattern(mSpecularMapPattern); shaderVisitor->setApplyLightingToEnvMaps(mApplyLightingToEnvMaps); + shaderVisitor->setTranslucentFramebuffer(translucentFramebuffer); return shaderVisitor; } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index e897566a8..a815a324f 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -76,7 +76,7 @@ namespace Resource Shader::ShaderManager& getShaderManager(); /// Re-create shaders for this node, need to call this if texture stages or vertex color mode have changed. - void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects"); + void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false); /// @see ShaderVisitor::setForceShaders void setForceShaders(bool force); @@ -173,7 +173,7 @@ namespace Resource private: - Shader::ShaderVisitor* createShaderVisitor(const std::string& shaderPrefix = "objects"); + Shader::ShaderVisitor* createShaderVisitor(const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false); std::unique_ptr mShaderManager; bool mForceShaders; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index e908b6aaa..9dec5522c 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -40,6 +40,7 @@ namespace Shader , mAutoUseNormalMaps(false) , mAutoUseSpecularMaps(false) , mApplyLightingToEnvMaps(false) + , mTranslucentFramebuffer(false) , mShaderManager(shaderManager) , mImageManager(imageManager) , mDefaultVsTemplate(defaultVsTemplate) @@ -146,7 +147,7 @@ namespace Shader mRequirements.back().mShaderRequired = true; } } - else + else if (!mTranslucentFramebuffer) Log(Debug::Error) << "ShaderVisitor encountered unknown texture " << texture; } } @@ -322,6 +323,8 @@ namespace Shader writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); + defineMap["translucentFramebuffer"] = mTranslucentFramebuffer ? "1" : "0"; + osg::ref_ptr vertexShader (mShaderManager.getShader(mDefaultVsTemplate, defineMap, osg::Shader::VERTEX)); osg::ref_ptr fragmentShader (mShaderManager.getShader(mDefaultFsTemplate, defineMap, osg::Shader::FRAGMENT)); @@ -474,4 +477,9 @@ namespace Shader mApplyLightingToEnvMaps = apply; } + void ShaderVisitor::setTranslucentFramebuffer(bool translucent) + { + mTranslucentFramebuffer = translucent; + } + } diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 6031dbfe6..11b37c923 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -40,6 +40,8 @@ namespace Shader void setApplyLightingToEnvMaps(bool apply); + void setTranslucentFramebuffer(bool translucent); + void apply(osg::Node& node) override; void apply(osg::Drawable& drawable) override; @@ -63,6 +65,8 @@ namespace Shader bool mApplyLightingToEnvMaps; + bool mTranslucentFramebuffer; + ShaderManager& mShaderManager; Resource::ImageManager& mImageManager; diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index bd2bd5909..50bb77145 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -209,8 +209,11 @@ void main() #endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); +#if @translucentFramebuffer + // having testing & blending isn't enough - we need to write an opaque pixel to be opaque if (noAlpha) gl_FragData[0].a = 1.0; +#endif applyShadowDebugOverlay(); } From cc24f13b391b3c3db25d8a1d77cb95b65fcfafee Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 21 Jan 2021 13:08:50 +0100 Subject: [PATCH 069/117] Remove duplicated sound_buffer entry --- apps/openmw/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 56875a8c3..f96ddb27f 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -58,7 +58,6 @@ add_openmw_dir (mwscript add_openmw_dir (mwsound soundmanagerimp openal_output ffmpeg_decoder sound sound_buffer sound_decoder sound_output loudness movieaudiofactory alext efx efx-presets regionsoundselector watersoundupdater volumesettings - sound_buffer ) add_openmw_dir (mwworld From 1f4c85520f33a1f340765597e0b1dbf837670b64 Mon Sep 17 00:00:00 2001 From: fredzio Date: Thu, 7 Jan 2021 11:02:53 +0100 Subject: [PATCH 070/117] Use convexSweepTest for projectile movement to solve any imprecision issue with projectile collision detection. Simplify the mechanics: manage hits in one spot. Give magic projectiles a collision shape similar in size to their visible model. Rename the 2 convex result callback to clearly state their purpose. --- CHANGELOG.md | 1 + apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwmechanics/spellcasting.cpp | 35 ++--- ...ltcallback.cpp => actorconvexcallback.cpp} | 6 +- ...ltcallback.hpp => actorconvexcallback.hpp} | 8 +- .../closestnotmerayresultcallback.cpp | 33 +--- .../closestnotmerayresultcallback.hpp | 4 +- apps/openmw/mwphysics/physicssystem.cpp | 54 +++++-- apps/openmw/mwphysics/physicssystem.hpp | 6 +- apps/openmw/mwphysics/projectile.cpp | 61 +++++--- apps/openmw/mwphysics/projectile.hpp | 26 ++-- .../mwphysics/projectileconvexcallback.cpp | 67 ++++++++ .../mwphysics/projectileconvexcallback.hpp | 27 ++++ apps/openmw/mwphysics/raycasting.hpp | 2 +- apps/openmw/mwphysics/trace.cpp | 6 +- apps/openmw/mwworld/projectilemanager.cpp | 144 +++++------------- apps/openmw/mwworld/projectilemanager.hpp | 4 +- 17 files changed, 260 insertions(+), 226 deletions(-) rename apps/openmw/mwphysics/{closestnotmeconvexresultcallback.cpp => actorconvexcallback.cpp} (92%) rename apps/openmw/mwphysics/{closestnotmeconvexresultcallback.hpp => actorconvexcallback.hpp} (54%) create mode 100644 apps/openmw/mwphysics/projectileconvexcallback.cpp create mode 100644 apps/openmw/mwphysics/projectileconvexcallback.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 75fd4cbe3..a2eaa3259 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Bug #4039: Multiple followers should have the same following distance Bug #4055: Local scripts don't inherit variables from their base record Bug #4083: Door animation freezes when colliding with actors + Bug #4201: Projectile-projectile collision Bug #4247: Cannot walk up stairs in Ebonheart docks Bug #4363: Editor: Defect in Clone Function for Dialogue Info records Bug #4447: Actor collision capsule shape allows looking through some walls diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 56875a8c3..412041f74 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -74,7 +74,7 @@ add_openmw_dir (mwworld add_openmw_dir (mwphysics physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver projectile - closestnotmeconvexresultcallback raycasting mtphysics contacttestwrapper + actorconvexcallback raycasting mtphysics contacttestwrapper projectileconvexcallback ) add_openmw_dir (mwclass diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 81b3a353d..142914c35 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -60,7 +60,10 @@ namespace MWMechanics void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded) { - if (!target.isEmpty() && target.getClass().isActor()) + if (target.isEmpty()) + return; + + if (target.getClass().isActor()) { // Early-out for characters that have departed. const auto& stats = target.getClass().getCreatureStats(target); @@ -82,7 +85,7 @@ namespace MWMechanics return; const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search (mId); - if (spell && !target.isEmpty() && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight)) + if (spell && target.getClass().isActor() && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight)) { int requiredResistance = (spell->mData.mType == ESM::Spell::ST_Disease) ? ESM::MagicEffect::ResistCommonDisease @@ -105,13 +108,13 @@ namespace MWMechanics // This is required for Weakness effects in a spell to apply to any subsequent effects in the spell. // Otherwise, they'd only apply after the whole spell was added. MagicEffects targetEffects; - if (!target.isEmpty() && target.getClass().isActor()) + if (target.getClass().isActor()) targetEffects += target.getClass().getCreatureStats(target).getMagicEffects(); bool castByPlayer = (!caster.isEmpty() && caster == getPlayer()); ActiveSpells targetSpells; - if (!target.isEmpty() && target.getClass().isActor()) + if (target.getClass().isActor()) targetSpells = target.getClass().getCreatureStats(target).getActiveSpells(); bool canCastAnEffect = false; // For bound equipment.If this remains false @@ -123,7 +126,7 @@ namespace MWMechanics int currentEffectIndex = 0; for (std::vector::const_iterator effectIt (effects.mList.begin()); - !target.isEmpty() && effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex) + effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex) { if (effectIt->mRange != range) continue; @@ -267,7 +270,7 @@ namespace MWMechanics } // Re-casting a summon effect will remove the creature from previous castings of that effect. - if (isSummoningEffect(effectIt->mEffectID) && !target.isEmpty() && target.getClass().isActor()) + if (isSummoningEffect(effectIt->mEffectID) && target.getClass().isActor()) { CreatureStats& targetStats = target.getClass().getCreatureStats(target); ESM::SummonKey key(effectIt->mEffectID, mId, currentEffectIndex); @@ -310,18 +313,16 @@ namespace MWMechanics if (!exploded) MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName, mFromProjectile); - if (!target.isEmpty()) { - if (!reflectedEffects.mList.empty()) - inflict(caster, target, reflectedEffects, range, true, exploded); + if (!reflectedEffects.mList.empty()) + inflict(caster, target, reflectedEffects, range, true, exploded); - if (!appliedLastingEffects.empty()) - { - int casterActorId = -1; - if (!caster.isEmpty() && caster.getClass().isActor()) - casterActorId = caster.getClass().getCreatureStats(caster).getActorId(); - target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, - mSourceName, casterActorId); - } + if (!appliedLastingEffects.empty()) + { + int casterActorId = -1; + if (!caster.isEmpty() && caster.getClass().isActor()) + casterActorId = caster.getClass().getCreatureStats(caster).getActorId(); + target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, + mSourceName, casterActorId); } } diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp b/apps/openmw/mwphysics/actorconvexcallback.cpp similarity index 92% rename from apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp rename to apps/openmw/mwphysics/actorconvexcallback.cpp index 27e7a390c..f99b706d7 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp +++ b/apps/openmw/mwphysics/actorconvexcallback.cpp @@ -1,6 +1,6 @@ #include -#include "closestnotmeconvexresultcallback.hpp" +#include "actorconvexcallback.hpp" #include "collisiontype.hpp" #include "contacttestwrapper.h" @@ -31,13 +31,13 @@ namespace MWPhysics } }; - ClosestNotMeConvexResultCallback::ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world) + ActorConvexCallback::ActorConvexCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world) : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)), mMe(me), mMotion(motion), mMinCollisionDot(minCollisionDot), mWorld(world) { } - btScalar ClosestNotMeConvexResultCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) + btScalar ActorConvexCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) { if (convexResult.m_hitCollisionObject == mMe) return btScalar(1); diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp b/apps/openmw/mwphysics/actorconvexcallback.hpp similarity index 54% rename from apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp rename to apps/openmw/mwphysics/actorconvexcallback.hpp index 538721ad8..1c28ee6cc 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp +++ b/apps/openmw/mwphysics/actorconvexcallback.hpp @@ -1,5 +1,5 @@ -#ifndef OPENMW_MWPHYSICS_CLOSESTNOTMECONVEXRESULTCALLBACK_H -#define OPENMW_MWPHYSICS_CLOSESTNOTMECONVEXRESULTCALLBACK_H +#ifndef OPENMW_MWPHYSICS_ACTORCONVEXCALLBACK_H +#define OPENMW_MWPHYSICS_ACTORCONVEXCALLBACK_H #include @@ -7,10 +7,10 @@ class btCollisionObject; namespace MWPhysics { - class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback + class ActorConvexCallback : public btCollisionWorld::ClosestConvexResultCallback { public: - ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world); + ActorConvexCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world); btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) override; diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp index c3104f860..32d97d6c7 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp @@ -7,16 +7,13 @@ #include "../mwworld/class.hpp" -#include "actor.hpp" -#include "collisiontype.hpp" -#include "projectile.hpp" #include "ptrholder.hpp" namespace MWPhysics { - ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector targets, const btVector3& from, const btVector3& to, Projectile* proj) + ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector targets, const btVector3& from, const btVector3& to) : btCollisionWorld::ClosestRayResultCallback(from, to) - , mMe(me), mTargets(std::move(targets)), mProjectile(proj) + , mMe(me), mTargets(std::move(targets)) { } @@ -25,9 +22,6 @@ namespace MWPhysics if (rayResult.m_collisionObject == mMe) return 1.f; - if (mProjectile && rayResult.m_collisionObject == mProjectile->getCollisionObject()) - return 1.f; - if (!mTargets.empty()) { if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end())) @@ -38,27 +32,6 @@ namespace MWPhysics } } - btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); - if (mProjectile) - { - switch (rayResult.m_collisionObject->getBroadphaseHandle()->m_collisionFilterGroup) - { - case CollisionType_Actor: - { - auto* target = static_cast(rayResult.m_collisionObject->getUserPointer()); - mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); - break; - } - case CollisionType_Projectile: - { - auto* target = static_cast(rayResult.m_collisionObject->getUserPointer()); - target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld); - mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); - break; - } - } - } - - return rayResult.m_hitFraction; + return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); } } diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp index b86427165..1fa32ef68 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp @@ -14,13 +14,13 @@ namespace MWPhysics class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback { public: - ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector targets, const btVector3& from, const btVector3& to, Projectile* proj=nullptr); + ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector targets, const btVector3& from, const btVector3& to); btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override; + private: const btCollisionObject* mMe; const std::vector mTargets; - Projectile* mProjectile; }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 95d1ec70a..ed0e0c915 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -55,6 +55,7 @@ #include "deepestnotmecontacttestresultcallback.hpp" #include "closestnotmerayresultcallback.hpp" #include "contacttestresultcallback.hpp" +#include "projectileconvexcallback.hpp" #include "constants.hpp" #include "movementsolver.hpp" #include "mtphysics.hpp" @@ -246,7 +247,7 @@ namespace MWPhysics return 0.f; } - RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector targets, int mask, int group, int projId) const + RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector targets, int mask, int group) const { if (from == to) { @@ -283,7 +284,7 @@ namespace MWPhysics } } - ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo, getProjectile(projId)); + ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo); resultCallback.m_collisionFilterGroup = group; resultCallback.m_collisionFilterMask = mask; @@ -580,15 +581,44 @@ namespace MWPhysics } } - void PhysicsSystem::updateProjectile(const int projectileId, const osg::Vec3f &position) + void PhysicsSystem::updateProjectile(const int projectileId, const osg::Vec3f &position) const { - ProjectileMap::iterator foundProjectile = mProjectiles.find(projectileId); - if (foundProjectile != mProjectiles.end()) - { - foundProjectile->second->setPosition(position); - mTaskScheduler->updateSingleAabb(foundProjectile->second); + const auto foundProjectile = mProjectiles.find(projectileId); + assert(foundProjectile != mProjectiles.end()); + auto* projectile = foundProjectile->second.get(); + + btVector3 btFrom = Misc::Convert::toBullet(projectile->getPosition()); + btVector3 btTo = Misc::Convert::toBullet(position); + + if (btFrom == btTo) return; - } + + const auto casterPtr = projectile->getCaster(); + const auto* caster = [this,&casterPtr]() -> const btCollisionObject* + { + const Actor* actor = getActor(casterPtr); + if (actor) + return actor->getCollisionObject(); + const Object* object = getObject(casterPtr); + if (object) + return object->getCollisionObject(); + return nullptr; + }(); + assert(caster); + + ProjectileConvexCallback resultCallback(caster, btFrom, btTo, projectile); + resultCallback.m_collisionFilterMask = 0xff; + resultCallback.m_collisionFilterGroup = CollisionType_Projectile; + + const btQuaternion btrot = btQuaternion::getIdentity(); + btTransform from_ (btrot, btFrom); + btTransform to_ (btrot, btTo); + + mTaskScheduler->convexSweepTest(projectile->getConvexShape(), from_, to_, resultCallback); + + const auto newpos = projectile->isActive() ? position : Misc::Convert::toOsg(resultCallback.m_hitPointWorld); + projectile->setPosition(newpos); + mTaskScheduler->updateSingleAabb(foundProjectile->second); } void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr) @@ -651,10 +681,10 @@ namespace MWPhysics mActors.emplace(ptr, std::move(actor)); } - int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position) + int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canTraverseWater) { mProjectileId++; - auto projectile = std::make_shared(mProjectileId, caster, position, mTaskScheduler.get(), this); + auto projectile = std::make_shared(caster, position, radius, canTraverseWater, mTaskScheduler.get(), this); mProjectiles.emplace(mProjectileId, std::move(projectile)); return mProjectileId; @@ -863,7 +893,7 @@ namespace MWPhysics mWaterCollisionShape.reset(new btStaticPlaneShape(btVector3(0,0,1), mWaterHeight)); mWaterCollisionObject->setCollisionShape(mWaterCollisionShape.get()); mTaskScheduler->addCollisionObject(mWaterCollisionObject.get(), CollisionType_Water, - CollisionType_Actor); + CollisionType_Actor|CollisionType_Projectile); } bool PhysicsSystem::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 449209974..6f901067a 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -124,8 +124,8 @@ namespace MWPhysics void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); - int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position); - void updateProjectile(const int projectileId, const osg::Vec3f &position); + int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canTraverseWater); + void updateProjectile(const int projectileId, const osg::Vec3f &position) const; void removeProjectile(const int projectileId); void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated); @@ -174,7 +174,7 @@ namespace MWPhysics /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors. RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), std::vector targets = std::vector(), - int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff, int projId=-1) const override; + int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const override; RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const override; diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index 1b9beca5f..a93121997 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -13,19 +13,22 @@ #include "../mwworld/class.hpp" #include "collisiontype.hpp" +#include "memory" #include "mtphysics.hpp" #include "projectile.hpp" namespace MWPhysics { -Projectile::Projectile(int projectileId, const MWWorld::Ptr& caster, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem) - : mActive(true) +Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canCrossWaterSurface, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem) + : mCanCrossWaterSurface(canCrossWaterSurface) + , mCrossedWaterSurface(false) + , mActive(true) , mCaster(caster) + , mWaterHitPosition(std::nullopt) , mPhysics(physicssystem) , mTaskScheduler(scheduler) - , mProjectileId(projectileId) { - mShape.reset(new btSphereShape(1.f)); + mShape = std::make_unique(radius); mConvexShape = static_cast(mShape.get()); mCollisionObject = std::make_unique(); @@ -67,6 +70,17 @@ void Projectile::setPosition(const osg::Vec3f &position) mTransformUpdatePending = true; } +osg::Vec3f Projectile::getPosition() const +{ + std::scoped_lock lock(mMutex); + return Misc::Convert::toOsg(mLocalTransform.getOrigin()); +} + +bool Projectile::canTraverseWater() const +{ + return mCanCrossWaterSurface; +} + void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal) { if (!mActive.load(std::memory_order_acquire)) @@ -78,12 +92,6 @@ void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal) mActive.store(false, std::memory_order_release); } -void Projectile::activate() -{ - assert(!mActive); - mActive.store(true, std::memory_order_release); -} - MWWorld::Ptr Projectile::getCaster() const { std::scoped_lock lock(mMutex); @@ -108,21 +116,32 @@ bool Projectile::isValidTarget(const MWWorld::Ptr& target) const if (mCaster == target) return false; - if (!mValidTargets.empty()) + if (target.isEmpty() || mValidTargets.empty()) + return true; + + bool validTarget = false; + for (const auto& targetActor : mValidTargets) { - bool validTarget = false; - for (const auto& targetActor : mValidTargets) + if (targetActor == target) { - if (targetActor == target) - { - validTarget = true; - break; - } + validTarget = true; + break; } - - return validTarget; } - return true; + return validTarget; +} + +std::optional Projectile::getWaterHitPosition() +{ + return std::exchange(mWaterHitPosition, std::nullopt); +} + +void Projectile::setWaterHitPosition(btVector3 pos) +{ + if (mCrossedWaterSurface) + return; + mCrossedWaterSurface = true; + mWaterHitPosition = pos; } } diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp index 5ae963b9a..fb50eebde 100644 --- a/apps/openmw/mwphysics/projectile.hpp +++ b/apps/openmw/mwphysics/projectile.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "components/misc/convert.hpp" @@ -32,7 +33,7 @@ namespace MWPhysics class Projectile final : public PtrHolder { public: - Projectile(const int projectileId, const MWWorld::Ptr& caster, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem); + Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canCrossWaterSurface, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem); ~Projectile() override; btConvexShape* getConvexShape() const { return mConvexShape; } @@ -40,17 +41,13 @@ namespace MWPhysics void commitPositionChange(); void setPosition(const osg::Vec3f& position); + osg::Vec3f getPosition() const; btCollisionObject* getCollisionObject() const { return mCollisionObject.get(); } - int getProjectileId() const - { - return mProjectileId; - } - bool isActive() const { return mActive.load(std::memory_order_acquire); @@ -65,18 +62,16 @@ namespace MWPhysics MWWorld::Ptr getCaster() const; void setCaster(MWWorld::Ptr caster); - osg::Vec3f getHitPos() const - { - assert(!mActive); - return Misc::Convert::toOsg(mHitPosition); - } + bool canTraverseWater() const; void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal); - void activate(); void setValidTargets(const std::vector& targets); bool isValidTarget(const MWWorld::Ptr& target) const; + std::optional getWaterHitPosition(); + void setWaterHitPosition(btVector3 pos); + private: std::unique_ptr mShape; @@ -85,9 +80,12 @@ namespace MWPhysics std::unique_ptr mCollisionObject; btTransform mLocalTransform; bool mTransformUpdatePending; + bool mCanCrossWaterSurface; + bool mCrossedWaterSurface; std::atomic mActive; MWWorld::Ptr mCaster; MWWorld::Ptr mHitTarget; + std::optional mWaterHitPosition; btVector3 mHitPosition; btVector3 mHitNormal; @@ -95,15 +93,11 @@ namespace MWPhysics mutable std::mutex mMutex; - osg::Vec3f mPosition; - PhysicsSystem *mPhysics; PhysicsTaskScheduler *mTaskScheduler; Projectile(const Projectile&); Projectile& operator=(const Projectile&); - - int mProjectileId; }; } diff --git a/apps/openmw/mwphysics/projectileconvexcallback.cpp b/apps/openmw/mwphysics/projectileconvexcallback.cpp new file mode 100644 index 000000000..0d0ac8720 --- /dev/null +++ b/apps/openmw/mwphysics/projectileconvexcallback.cpp @@ -0,0 +1,67 @@ +#include "../mwworld/class.hpp" + +#include "actor.hpp" +#include "collisiontype.hpp" +#include "projectile.hpp" +#include "projectileconvexcallback.hpp" +#include "ptrholder.hpp" + +namespace MWPhysics +{ + ProjectileConvexCallback::ProjectileConvexCallback(const btCollisionObject* me, const btVector3& from, const btVector3& to, Projectile* proj) + : btCollisionWorld::ClosestConvexResultCallback(from, to) + , mMe(me), mProjectile(proj) + { + assert(mProjectile); + } + + btScalar ProjectileConvexCallback::addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) + { + // don't hit the caster + if (result.m_hitCollisionObject == mMe) + return 1.f; + + // don't hit the projectile + if (result.m_hitCollisionObject == mProjectile->getCollisionObject()) + return 1.f; + + btCollisionWorld::ClosestConvexResultCallback::addSingleResult(result, normalInWorldSpace); + switch (result.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup) + { + case CollisionType_Actor: + { + auto* target = static_cast(result.m_hitCollisionObject->getUserPointer()); + if (!mProjectile->isValidTarget(target->getPtr())) + return 1.f; + mProjectile->hit(target->getPtr(), result.m_hitPointLocal, result.m_hitNormalLocal); + break; + } + case CollisionType_Projectile: + { + auto* target = static_cast(result.m_hitCollisionObject->getUserPointer()); + if (!mProjectile->isValidTarget(target->getCaster())) + return 1.f; + target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld); + mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); + break; + } + case CollisionType_Water: + { + mProjectile->setWaterHitPosition(m_hitPointWorld); + if (mProjectile->canTraverseWater()) + return 1.f; + mProjectile->hit(MWWorld::Ptr(), m_hitPointWorld, m_hitNormalWorld); + break; + } + default: + { + mProjectile->hit(MWWorld::Ptr(), m_hitPointWorld, m_hitNormalWorld); + break; + } + } + + return result.m_hitFraction; + } + +} + diff --git a/apps/openmw/mwphysics/projectileconvexcallback.hpp b/apps/openmw/mwphysics/projectileconvexcallback.hpp new file mode 100644 index 000000000..c687de36c --- /dev/null +++ b/apps/openmw/mwphysics/projectileconvexcallback.hpp @@ -0,0 +1,27 @@ +#ifndef OPENMW_MWPHYSICS_PROJECTILECONVEXCALLBACK_H +#define OPENMW_MWPHYSICS_PROJECTILECONVEXCALLBACK_H + +#include + +#include + +class btCollisionObject; + +namespace MWPhysics +{ + class Projectile; + + class ProjectileConvexCallback : public btCollisionWorld::ClosestConvexResultCallback + { + public: + ProjectileConvexCallback(const btCollisionObject* me, const btVector3& from, const btVector3& to, Projectile* proj); + + btScalar addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) override; + + private: + const btCollisionObject* mMe; + Projectile* mProjectile; + }; +} + +#endif diff --git a/apps/openmw/mwphysics/raycasting.hpp b/apps/openmw/mwphysics/raycasting.hpp index b176e8330..7c8375cb5 100644 --- a/apps/openmw/mwphysics/raycasting.hpp +++ b/apps/openmw/mwphysics/raycasting.hpp @@ -29,7 +29,7 @@ namespace MWPhysics /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors. virtual RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), std::vector targets = std::vector(), - int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff, int projId=-1) const = 0; + int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const = 0; virtual RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const = 0; diff --git a/apps/openmw/mwphysics/trace.cpp b/apps/openmw/mwphysics/trace.cpp index f50b6100a..049d026e8 100644 --- a/apps/openmw/mwphysics/trace.cpp +++ b/apps/openmw/mwphysics/trace.cpp @@ -7,7 +7,7 @@ #include "collisiontype.hpp" #include "actor.hpp" -#include "closestnotmeconvexresultcallback.hpp" +#include "actorconvexcallback.hpp" namespace MWPhysics { @@ -24,7 +24,7 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star to.setOrigin(btend); const btVector3 motion = btstart-btend; - ClosestNotMeConvexResultCallback newTraceCallback(actor, motion, btScalar(0.0), world); + ActorConvexCallback newTraceCallback(actor, motion, btScalar(0.0), world); // Inherit the actor's collision group and mask newTraceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup; newTraceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask; @@ -62,7 +62,7 @@ void ActorTracer::findGround(const Actor* actor, const osg::Vec3f& start, const btTransform to(trans.getBasis(), btend); const btVector3 motion = btstart-btend; - ClosestNotMeConvexResultCallback newTraceCallback(actor->getCollisionObject(), motion, btScalar(0.0), world); + ActorConvexCallback newTraceCallback(actor->getCollisionObject(), motion, btScalar(0.0), world); // Inherit the actor's collision group and mask newTraceCallback.m_collisionFilterGroup = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterGroup; newTraceCallback.m_collisionFilterMask = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterMask; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 08c9e4662..c87f62505 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -43,6 +44,7 @@ #include "../mwsound/sound.hpp" +#include "../mwphysics/collisiontype.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwphysics/projectile.hpp" @@ -187,7 +189,7 @@ namespace MWWorld }; - void ProjectileManager::createModel(State &state, const std::string &model, const osg::Vec3f& pos, const osg::Quat& orient, + float ProjectileManager::createModel(State &state, const std::string &model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture) { state.mNode = new osg::PositionAttitudeTransform; @@ -251,6 +253,7 @@ namespace MWWorld state.mNode->accept(assignVisitor); MWRender::overrideFirstRootTexture(texture, mResourceSystem, projectile); + return projectile->getBound().radius(); } void ProjectileManager::update(State& state, float duration) @@ -305,7 +308,7 @@ namespace MWWorld osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); - createModel(state, ptr.getClass().getModel(ptr), pos, orient, true, true, lightDiffuseColor, texture); + const auto radius = createModel(state, ptr.getClass().getModel(ptr), pos, orient, true, true, lightDiffuseColor, texture); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); for (const std::string &soundid : state.mSoundIds) @@ -316,7 +319,7 @@ namespace MWWorld state.mSounds.push_back(sound); } - state.mProjectileId = mPhysics->addProjectile(caster, pos); + state.mProjectileId = mPhysics->addProjectile(caster, pos, radius, false); state.mToDelete = false; mMagicBolts.push_back(state); } @@ -340,7 +343,7 @@ namespace MWWorld if (!ptr.getClass().getEnchantment(ptr).empty()) SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); - state.mProjectileId = mPhysics->addProjectile(actor, pos); + state.mProjectileId = mPhysics->addProjectile(actor, pos, 1.f, true); state.mToDelete = false; mProjectiles.push_back(state); } @@ -407,15 +410,7 @@ namespace MWWorld float speed = fTargetSpellMaxSpeed * magicBoltState.mSpeed; osg::Vec3f direction = orient * osg::Vec3f(0,1,0); direction.normalize(); - osg::Vec3f pos(magicBoltState.mNode->getPosition()); - osg::Vec3f newPos = pos + direction * duration * speed; - - for (const auto& sound : magicBoltState.mSounds) - sound->setPosition(newPos); - - magicBoltState.mNode->setPosition(newPos); - - mPhysics->updateProjectile(magicBoltState.mProjectileId, newPos); + osg::Vec3f newPos = projectile->getPosition() + direction * duration * speed; update(magicBoltState, duration); @@ -425,41 +420,7 @@ namespace MWWorld caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); projectile->setValidTargets(targetActors); - // Check for impact - // TODO: use a proper btRigidBody / btGhostObject? - const auto result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, magicBoltState.mProjectileId); - - bool hit = false; - if (result.mHit) - { - hit = true; - if (result.mHitObject.isEmpty()) - { - // terrain or projectile - } - else - { - MWMechanics::CastSpell cast(caster, result.mHitObject); - cast.mHitPosition = pos; - cast.mId = magicBoltState.mSpellId; - cast.mSourceName = magicBoltState.mSourceName; - cast.mStack = false; - cast.inflict(result.mHitObject, caster, magicBoltState.mEffects, ESM::RT_Target, false, true); - mPhysics->reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal)); - } - } - - // Explodes when hitting water - if (MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), newPos)) - hit = true; - - if (hit) - { - MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, result.mHitObject, - ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName); - - cleanupMagicBolt(magicBoltState); - } + mPhysics->updateProjectile(magicBoltState.mProjectileId, newPos); } } @@ -467,9 +428,6 @@ namespace MWWorld { for (auto& projectileState : mProjectiles) { - if (projectileState.mToDelete) - continue; - auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); if (!projectile->isActive()) continue; @@ -477,8 +435,7 @@ namespace MWWorld // simulating aerodynamics at all projectileState.mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration; - osg::Vec3f pos(projectileState.mNode->getPosition()); - osg::Vec3f newPos = pos + projectileState.mVelocity * duration; + osg::Vec3f newPos = projectile->getPosition() + projectileState.mVelocity * duration; // rotation does not work well for throwing projectiles - their roll angle will depend on shooting direction. if (!projectileState.mThrown) @@ -488,10 +445,6 @@ namespace MWWorld projectileState.mNode->setAttitude(orient); } - projectileState.mNode->setPosition(newPos); - - mPhysics->updateProjectile(projectileState.mProjectileId, newPos); - update(projectileState, duration); MWWorld::Ptr caster = projectileState.getCaster(); @@ -502,36 +455,7 @@ namespace MWWorld caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); projectile->setValidTargets(targetActors); - // Check for impact - // TODO: use a proper btRigidBody / btGhostObject? - const auto result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, projectileState.mProjectileId); - - bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), newPos); - - if (result.mHit || underwater) - { - // Try to get a Ptr to the bow that was used. It might no longer exist. - MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), projectileState.mIdArrow); - MWWorld::Ptr bow = projectileRef.getPtr(); - if (!caster.isEmpty() && projectileState.mIdArrow != projectileState.mBowId) - { - MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); - MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId)) - bow = *invIt; - } - - if (caster.isEmpty()) - caster = result.mHitObject; - - MWMechanics::projectileHit(caster, result.mHitObject, bow, projectileRef.getPtr(), result.mHit ? result.mHitPos : newPos, projectileState.mAttackStrength); - mPhysics->reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal)); - - if (underwater) - mRendering->emitWaterRipple(newPos); - - cleanupProjectile(projectileState); - } + mPhysics->updateProjectile(projectileState.mProjectileId, newPos); } } @@ -543,17 +467,19 @@ namespace MWWorld continue; auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); + + if (const auto hitWaterPos = projectile->getWaterHitPosition()) + mRendering->emitWaterRipple(Misc::Convert::toOsg(*hitWaterPos)); + + const auto pos = projectile->getPosition(); + projectileState.mNode->setPosition(pos); + if (projectile->isActive()) continue; + const auto target = projectile->getTarget(); - const auto pos = projectile->getHitPos(); - MWWorld::Ptr caster = projectileState.getCaster(); + auto caster = projectileState.getCaster(); assert(target != caster); - if (!projectile->isValidTarget(target)) - { - projectile->activate(); - continue; - } if (caster.isEmpty()) caster = target; @@ -569,9 +495,8 @@ namespace MWWorld bow = *invIt; } - projectileState.mHitPosition = pos; - cleanupProjectile(projectileState); MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength); + cleanupProjectile(projectileState); } for (auto& magicBoltState : mMagicBolts) { @@ -579,20 +504,18 @@ namespace MWWorld continue; auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId); + + const auto pos = projectile->getPosition(); + magicBoltState.mNode->setPosition(pos); + for (const auto& sound : magicBoltState.mSounds) + sound->setPosition(pos); + if (projectile->isActive()) continue; - const auto target = projectile->getTarget(); - const auto pos = projectile->getHitPos(); - MWWorld::Ptr caster = magicBoltState.getCaster(); - assert(target != caster); - if (!projectile->isValidTarget(target)) - { - projectile->activate(); - continue; - } - magicBoltState.mHitPosition = pos; - cleanupMagicBolt(magicBoltState); + const auto target = projectile->getTarget(); + const auto caster = magicBoltState.getCaster(); + assert(target != caster); MWMechanics::CastSpell cast(caster, target); cast.mHitPosition = pos; @@ -602,6 +525,7 @@ namespace MWWorld cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, false, true); MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName); + cleanupMagicBolt(magicBoltState); } mProjectiles.erase(std::remove_if(mProjectiles.begin(), mProjectiles.end(), [](const State& state) { return state.mToDelete; }), mProjectiles.end()); @@ -702,7 +626,7 @@ namespace MWWorld int weaponType = ptr.get()->mBase->mData.mType; state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown; - state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition)); + state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), 1.f, true); } catch(...) { @@ -747,7 +671,6 @@ namespace MWWorld MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0)); MWWorld::Ptr ptr = ref.getPtr(); model = ptr.getClass().getModel(ptr); - state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition)); } catch(...) { @@ -755,7 +678,8 @@ namespace MWWorld } osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); - createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, true, lightDiffuseColor, texture); + const auto radius = createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, true, lightDiffuseColor, texture); + state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), radius, false); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); for (const std::string &soundid : state.mSoundIds) diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index 2cd570847..e088dd701 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -80,8 +80,6 @@ namespace MWWorld int mActorId; int mProjectileId; - osg::Vec3f mHitPosition; - // TODO: this will break when the game is saved and reloaded, since there is currently // no way to write identifiers for non-actors to a savegame. MWWorld::Ptr mCasterHandle; @@ -132,7 +130,7 @@ namespace MWWorld void moveProjectiles(float dt); void moveMagicBolts(float dt); - void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient, + float createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture = ""); void update (State& state, float duration); From cd5e31dc4ba2cdbbc80de93bf59ccc83c3bbda70 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Fri, 22 Jan 2021 19:04:07 +0000 Subject: [PATCH 071/117] Revert "Merge branch 'windowing-system' into 'master'" This reverts merge request !541 --- components/sdlutil/sdlcursormanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sdlutil/sdlcursormanager.cpp b/components/sdlutil/sdlcursormanager.cpp index 964762ec7..56225868e 100644 --- a/components/sdlutil/sdlcursormanager.cpp +++ b/components/sdlutil/sdlcursormanager.cpp @@ -20,7 +20,7 @@ #include "imagetosurface.hpp" -#if defined(OSG_LIBRARY_STATIC) && defined(USE_GRAPHICSWINDOW) +#if defined(OSG_LIBRARY_STATIC) && !defined(ANDROID) // Sets the default windowing system interface according to the OS. // Necessary for OpenSceneGraph to do some things, like decompression. USE_GRAPHICSWINDOW() From eb80c997b8255e86b86096bc4ac9218929eaeddd Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 22 Jan 2021 19:44:22 +0000 Subject: [PATCH 072/117] Avoid OSG setting array binding from multiple threads --- components/terrain/buffercache.cpp | 2 +- components/terrain/chunkmanager.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/components/terrain/buffercache.cpp b/components/terrain/buffercache.cpp index 5871b96bc..f9eb7ae63 100644 --- a/components/terrain/buffercache.cpp +++ b/components/terrain/buffercache.cpp @@ -186,7 +186,7 @@ namespace Terrain int vertexCount = numVerts * numVerts; - osg::ref_ptr uvs (new osg::Vec2Array); + osg::ref_ptr uvs (new osg::Vec2Array(osg::Array::BIND_PER_VERTEX)); uvs->reserve(vertexCount); for (unsigned int col = 0; col < numVerts; ++col) diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 041414a87..50724628d 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -197,8 +197,7 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve bool useCompositeMap = chunkSize >= mCompositeMapLevel; unsigned int numUvSets = useCompositeMap ? 1 : 2; - for (unsigned int i=0; isetTexCoordArray(i, mBufferCache.getUVBuffer(numVerts)); + geometry->setTexCoordArrayList(osg::Geometry::ArrayList(numUvSets, mBufferCache.getUVBuffer(numVerts))); geometry->createClusterCullingCallback(); From a401c517bfa000347443c2837a0b55718000a53a Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 23 Jan 2021 00:56:46 +0100 Subject: [PATCH 073/117] Always unload height fields loadCell always adds a height field, but unloadCell only removed it for cells with height data. Reloading a cell overwrote the height field added earlier (leading to its destruction) while the navigator retained a reference to the now deleted collision shape, leading to a crash. --- apps/openmw/mwworld/scene.cpp | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 47b62862f..427ee3aa8 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -340,17 +340,9 @@ namespace MWWorld if ((*iter)->getCell()->isExterior()) { - const ESM::Land* land = - MWBase::Environment::get().getWorld()->getStore().get().search( - (*iter)->getCell()->getGridX(), - (*iter)->getCell()->getGridY() - ); - if (land && land->mDataTypes&ESM::Land::DATA_VHGT) - { - if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) - navigator->removeObject(DetourNavigator::ObjectId(heightField)); - mPhysics->removeHeightField(cellX, cellY); - } + if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) + navigator->removeObject(DetourNavigator::ObjectId(heightField)); + mPhysics->removeHeightField(cellX, cellY); } if ((*iter)->getCell()->hasWater()) From bd7d5a8f92640ceadaa70b22e650a5644f1b030f Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sat, 23 Jan 2021 04:08:39 +0000 Subject: [PATCH 074/117] Fix memory leak in FontLoader::loadFontFromXml Tried building with -DCMAKE_CXX_FLAGS='-fsanitize=address -fsanitize-recover=address' and this was one of the reported leaks. --- components/fontloader/fontloader.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 605d55243..09bf0b190 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -630,6 +630,7 @@ namespace Gui } MyGUI::ResourceManager::getInstance().loadFromXmlNode(copy, _file, _version); + delete copy; } } From 3300e26c86ecd4c84cc6b099e2abe918a6a5ef3c Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 23 Jan 2021 15:25:05 +0000 Subject: [PATCH 075/117] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b3a3438a..cede01f63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ Bug #4623: Corprus implementation is incorrect Bug #4631: Setting MSAA level too high doesn't fall back to highest supported level Bug #4764: Data race in osg ParticleSystem + Bug #4765: Data race in ChunkManager -> Array::setBinding Bug #4774: Guards are ignorant of an invisible player that tries to attack them Bug #5101: Hostile followers travel with the player Bug #5108: Savegame bloating due to inefficient fog textures format From 1e113710efb68183300c228ffa05136072789659 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sat, 23 Jan 2021 17:14:56 +0000 Subject: [PATCH 076/117] Clean-up FontLoader::loadFontFromXml fix Follow-up to !559 --- components/fontloader/fontloader.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 09bf0b190..2bed079e1 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -593,7 +593,7 @@ namespace Gui if (createCopy) { - MyGUI::xml::ElementPtr copy = _node->createCopy(); + std::unique_ptr copy{_node->createCopy()}; MyGUI::xml::ElementEnumerator copyFont = copy->getElementEnumerator(); while (copyFont.next("Resource")) @@ -629,8 +629,7 @@ namespace Gui } } - MyGUI::ResourceManager::getInstance().loadFromXmlNode(copy, _file, _version); - delete copy; + MyGUI::ResourceManager::getInstance().loadFromXmlNode(copy.get(), _file, _version); } } From 165c7314928dc281a364fa1a0143c45fd6d2adfd Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 23 Jan 2021 10:43:32 +0100 Subject: [PATCH 077/117] Remove physics dependency on basenode Necessary to be able to load physics objects from inactive cells. --- apps/openmw/mwclass/activator.cpp | 4 +-- apps/openmw/mwclass/activator.hpp | 2 +- apps/openmw/mwclass/actor.cpp | 6 +---- apps/openmw/mwclass/actor.hpp | 10 +++---- apps/openmw/mwclass/apparatus.cpp | 5 ---- apps/openmw/mwclass/apparatus.hpp | 2 -- apps/openmw/mwclass/armor.cpp | 5 ---- apps/openmw/mwclass/armor.hpp | 2 -- apps/openmw/mwclass/bodypart.cpp | 4 --- apps/openmw/mwclass/bodypart.hpp | 2 -- apps/openmw/mwclass/book.cpp | 5 ---- apps/openmw/mwclass/book.hpp | 2 -- apps/openmw/mwclass/clothing.cpp | 5 ---- apps/openmw/mwclass/clothing.hpp | 2 -- apps/openmw/mwclass/container.cpp | 4 +-- apps/openmw/mwclass/container.hpp | 2 +- apps/openmw/mwclass/door.cpp | 4 +-- apps/openmw/mwclass/door.hpp | 2 +- apps/openmw/mwclass/ingredient.cpp | 5 ---- apps/openmw/mwclass/ingredient.hpp | 2 -- apps/openmw/mwclass/light.cpp | 4 +-- apps/openmw/mwclass/light.hpp | 2 +- apps/openmw/mwclass/lockpick.cpp | 5 ---- apps/openmw/mwclass/lockpick.hpp | 2 -- apps/openmw/mwclass/misc.cpp | 5 ---- apps/openmw/mwclass/misc.hpp | 2 -- apps/openmw/mwclass/potion.cpp | 5 ---- apps/openmw/mwclass/potion.hpp | 2 -- apps/openmw/mwclass/probe.cpp | 5 ---- apps/openmw/mwclass/probe.hpp | 2 -- apps/openmw/mwclass/repair.cpp | 5 ---- apps/openmw/mwclass/repair.hpp | 2 -- apps/openmw/mwclass/static.cpp | 4 +-- apps/openmw/mwclass/static.hpp | 2 +- apps/openmw/mwclass/weapon.cpp | 5 ---- apps/openmw/mwclass/weapon.hpp | 2 -- apps/openmw/mwphysics/actor.cpp | 6 ++--- apps/openmw/mwphysics/actor.hpp | 2 +- apps/openmw/mwphysics/object.cpp | 11 +++++--- apps/openmw/mwphysics/object.hpp | 4 +-- apps/openmw/mwphysics/physicssystem.cpp | 10 +++---- apps/openmw/mwphysics/physicssystem.hpp | 4 +-- apps/openmw/mwworld/class.cpp | 6 +---- apps/openmw/mwworld/class.hpp | 13 +++++---- apps/openmw/mwworld/scene.cpp | 36 +++++++++++++------------ apps/openmw/mwworld/worldimp.cpp | 2 +- 46 files changed, 68 insertions(+), 155 deletions(-) diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index c54b1c369..716e548e1 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -38,10 +38,10 @@ namespace MWClass } } - void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model); + physics.addObject(ptr, model, rotation); } std::string Activator::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index 10ace6f74..c7b65ef67 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -17,7 +17,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp index 33aeb26bb..1789f1b19 100644 --- a/apps/openmw/mwclass/actor.cpp +++ b/apps/openmw/mwclass/actor.cpp @@ -17,16 +17,12 @@ namespace MWClass { - Actor::Actor() {} - - Actor::~Actor() {} - void Actor::adjustPosition(const MWWorld::Ptr& ptr, bool force) const { MWBase::Environment::get().getWorld()->adjustPosition(ptr, force); } - void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const { if (!model.empty()) { diff --git a/apps/openmw/mwclass/actor.hpp b/apps/openmw/mwclass/actor.hpp index 3d509b276..98998b4eb 100644 --- a/apps/openmw/mwclass/actor.hpp +++ b/apps/openmw/mwclass/actor.hpp @@ -15,16 +15,16 @@ namespace MWClass { protected: - Actor(); + Actor() = default; public: - virtual ~Actor(); + ~Actor() override = default; void adjustPosition(const MWWorld::Ptr& ptr, bool force) const override; ///< Adjust position to stand on ground. Must be called post model load /// @param force do this even if the ptr is flying - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; bool useAnim() const override; @@ -46,8 +46,8 @@ namespace MWClass float getCurrentSpeed(const MWWorld::Ptr& ptr) const override; // not implemented - Actor(const Actor&); - Actor& operator= (const Actor&); + Actor(const Actor&) = delete; + Actor& operator= (const Actor&) = delete; }; } diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 518695fab..e09e4804c 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -26,11 +26,6 @@ namespace MWClass } } - void Apparatus::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Apparatus::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/apparatus.hpp b/apps/openmw/mwclass/apparatus.hpp index 8087c57ba..828abef25 100644 --- a/apps/openmw/mwclass/apparatus.hpp +++ b/apps/openmw/mwclass/apparatus.hpp @@ -17,8 +17,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 3f9bfb859..817adbc1f 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -34,11 +34,6 @@ namespace MWClass } } - void Armor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Armor::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index 4f04e0824..f64f138a2 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -16,8 +16,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/bodypart.cpp b/apps/openmw/mwclass/bodypart.cpp index 0315d3ddb..7fe798e27 100644 --- a/apps/openmw/mwclass/bodypart.cpp +++ b/apps/openmw/mwclass/bodypart.cpp @@ -22,10 +22,6 @@ namespace MWClass } } - void BodyPart::insertObject(const MWWorld::Ptr &ptr, const std::string &model, MWPhysics::PhysicsSystem &physics) const - { - } - std::string BodyPart::getName(const MWWorld::ConstPtr &ptr) const { return std::string(); diff --git a/apps/openmw/mwclass/bodypart.hpp b/apps/openmw/mwclass/bodypart.hpp index 13d914138..0e372b884 100644 --- a/apps/openmw/mwclass/bodypart.hpp +++ b/apps/openmw/mwclass/bodypart.hpp @@ -15,8 +15,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 4ea71e3ac..51b9e39d7 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -31,11 +31,6 @@ namespace MWClass } } - void Book::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Book::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index c58e68ad8..f3d34c516 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -14,8 +14,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 6d7960aac..400cd97e4 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -29,11 +29,6 @@ namespace MWClass } } - void Clothing::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Clothing::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index a87e0cbe0..3d5c162aa 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -14,8 +14,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 28305c394..3023466f0 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -111,10 +111,10 @@ namespace MWClass } } - void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model); + physics.addObject(ptr, model, rotation); } std::string Container::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 2dc0c06ca..74d9ac0da 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -44,7 +44,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index ba51d9c2b..983953c20 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -62,10 +62,10 @@ namespace MWClass } } - void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, MWPhysics::CollisionType_Door); + physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_Door); // Resume the door's opening/closing animation if it wasn't finished if (ptr.getRefData().getCustomData()) diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index 6c2fa26b8..6d40a840b 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -18,7 +18,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; bool isDoor() const override; diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index a007ad115..20f9576df 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -28,11 +28,6 @@ namespace MWClass } } - void Ingredient::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Ingredient::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/ingredient.hpp b/apps/openmw/mwclass/ingredient.hpp index 5219cf39c..2aa831f86 100644 --- a/apps/openmw/mwclass/ingredient.hpp +++ b/apps/openmw/mwclass/ingredient.hpp @@ -14,8 +14,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 3bdf10f47..d4d196bc4 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -33,7 +33,7 @@ namespace MWClass renderingInterface.getObjects().insertModel(ptr, model, true, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)); } - void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const { MWWorld::LiveCellRef *ref = ptr.get(); @@ -41,7 +41,7 @@ namespace MWClass // TODO: add option somewhere to enable collision for placeable objects if (!model.empty() && (ref->mBase->mData.mFlags & ESM::Light::Carry) == 0) - physics.addObject(ptr, model); + physics.addObject(ptr, model, rotation); if (!ref->mBase->mSound.empty() && !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)) MWBase::Environment::get().getSoundManager()->playSound3D(ptr, ref->mBase->mSound, 1.0, 1.0, diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index e37dddc25..1b1794d4a 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -14,7 +14,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; bool useAnim() const override; diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 9b8abc8f2..985b08771 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -28,11 +28,6 @@ namespace MWClass } } - void Lockpick::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Lockpick::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index fabae3343..d4b265e39 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -14,8 +14,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 8d3cda6fe..facab9d51 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -37,11 +37,6 @@ namespace MWClass } } - void Miscellaneous::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Miscellaneous::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index 9bff85ca5..18788c7ed 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -14,8 +14,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 4af97e634..56d9dff27 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -30,11 +30,6 @@ namespace MWClass } } - void Potion::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Potion::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp index 75d923f0b..75b962164 100644 --- a/apps/openmw/mwclass/potion.hpp +++ b/apps/openmw/mwclass/potion.hpp @@ -14,8 +14,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index dba4e8c06..51273337a 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -28,11 +28,6 @@ namespace MWClass } } - void Probe::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Probe::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index a0a41dcfb..ef9273a37 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -14,8 +14,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 8907c8212..f1b88e422 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -25,11 +25,6 @@ namespace MWClass } } - void Repair::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Repair::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/repair.hpp b/apps/openmw/mwclass/repair.hpp index b9791e9cf..c403449e1 100644 --- a/apps/openmw/mwclass/repair.hpp +++ b/apps/openmw/mwclass/repair.hpp @@ -14,8 +14,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 5551b3d73..108c4eaa2 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -23,10 +23,10 @@ namespace MWClass } } - void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model); + physics.addObject(ptr, model, rotation); } std::string Static::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index 6bc783dad..f856e9fd9 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -14,7 +14,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 0d6a27cf6..6246c8fb0 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -34,11 +34,6 @@ namespace MWClass } } - void Weapon::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Weapon::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index f1824b7d1..db17e6b70 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -15,8 +15,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 3b52ee934..06600fd6a 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -67,7 +67,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic updateScale(); if(!mRotationallyInvariant) - updateRotation(); + setRotation(mPtr.getRefData().getBaseNode()->getAttitude()); updatePosition(); addCollisionMask(getCollisionMask()); @@ -197,10 +197,10 @@ osg::Vec3f Actor::getPreviousPosition() const return mPreviousPosition; } -void Actor::updateRotation () +void Actor::setRotation(osg::Quat quat) { std::scoped_lock lock(mPositionMutex); - mRotation = mPtr.getRefData().getBaseNode()->getAttitude(); + mRotation = quat; } bool Actor::isRotationallyInvariant() const diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 9d129f2ba..4039f4481 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -49,7 +49,7 @@ namespace MWPhysics void enableCollisionBody(bool collision); void updateScale(); - void updateRotation(); + void setRotation(osg::Quat quat); /** * Return true if the collision shape looks the same no matter how its Z rotated. diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index e3615989d..0a7b9540c 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -14,7 +14,7 @@ namespace MWPhysics { - Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler) + Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, osg::Quat rotation, int collisionType, PhysicsTaskScheduler* scheduler) : mShapeInstance(shapeInstance) , mSolid(true) , mTaskScheduler(scheduler) @@ -27,7 +27,7 @@ namespace MWPhysics mCollisionObject->setUserPointer(this); setScale(ptr.getCellRef().getScale()); - setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); + setRotation(rotation); setOrigin(Misc::Convert::toBullet(ptr.getRefData().getPosition().asVec3())); commitPositionChange(); @@ -51,10 +51,10 @@ namespace MWPhysics mScaleUpdatePending = true; } - void Object::setRotation(const btQuaternion& quat) + void Object::setRotation(osg::Quat quat) { std::unique_lock lock(mPositionMutex); - mLocalTransform.setRotation(quat); + mLocalTransform.setRotation(Misc::Convert::toBullet(quat)); mTransformUpdatePending = true; } @@ -116,6 +116,9 @@ namespace MWPhysics if (mShapeInstance->mAnimatedShapes.empty()) return false; + if (mPtr.getRefData().getBaseNode() == nullptr) + return true; + assert (mShapeInstance->getCollisionShape()->isCompound()); btCompoundShape* compound = static_cast(mShapeInstance->getCollisionShape()); diff --git a/apps/openmw/mwphysics/object.hpp b/apps/openmw/mwphysics/object.hpp index cae877180..c2273831e 100644 --- a/apps/openmw/mwphysics/object.hpp +++ b/apps/openmw/mwphysics/object.hpp @@ -26,12 +26,12 @@ namespace MWPhysics class Object final : public PtrHolder { public: - Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler); + Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, osg::Quat rotation, int collisionType, PhysicsTaskScheduler* scheduler); ~Object() override; const Resource::BulletShapeInstance* getShapeInstance() const; void setScale(float scale); - void setRotation(const btQuaternion& quat); + void setRotation(osg::Quat quat); void setOrigin(const btVector3& vec); void commitPositionChange(); btCollisionObject* getCollisionObject(); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index ed0e0c915..68cec48bc 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -456,13 +456,13 @@ namespace MWPhysics return heightField->second.get(); } - void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType) + void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType) { osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); if (!shapeInstance || !shapeInstance->getCollisionShape()) return; - auto obj = std::make_shared(ptr, shapeInstance, collisionType, mTaskScheduler.get()); + auto obj = std::make_shared(ptr, shapeInstance, rotation, collisionType, mTaskScheduler.get()); mObjects.emplace(ptr, obj); if (obj->isAnimated()) @@ -621,12 +621,12 @@ namespace MWPhysics mTaskScheduler->updateSingleAabb(foundProjectile->second); } - void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr) + void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr, osg::Quat rotate) { ObjectMap::iterator found = mObjects.find(ptr); if (found != mObjects.end()) { - found->second->setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); + found->second->setRotation(rotate); mTaskScheduler->updateSingleAabb(found->second); return; } @@ -635,7 +635,7 @@ namespace MWPhysics { if (!foundActor->second->isRotationallyInvariant()) { - foundActor->second->updateRotation(); + foundActor->second->setRotation(rotate); mTaskScheduler->updateSingleAabb(foundActor->second); } return; diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 6f901067a..c61b368f8 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -121,7 +121,7 @@ namespace MWPhysics void setWaterHeight(float height); void disableWater(); - void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World); + void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType = CollisionType_World); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canTraverseWater); @@ -141,7 +141,7 @@ namespace MWPhysics void remove (const MWWorld::Ptr& ptr); void updateScale (const MWWorld::Ptr& ptr); - void updateRotation (const MWWorld::Ptr& ptr); + void updateRotation (const MWWorld::Ptr& ptr, osg::Quat rotate); void updatePosition (const MWWorld::Ptr& ptr); void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject); diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 950c8a6d4..becf912ea 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -25,16 +25,12 @@ namespace MWWorld { std::map > Class::sClasses; - Class::Class() {} - - Class::~Class() {} - void Class::insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const { } - void Class::insertObject(const Ptr& ptr, const std::string& mesh, MWPhysics::PhysicsSystem& physics) const + void Class::insertObject(const Ptr& ptr, const std::string& mesh, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const { } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 1b3d4029e..39fb6fe4c 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include "ptr.hpp" @@ -57,13 +58,9 @@ namespace MWWorld std::string mTypeName; - // not implemented - Class (const Class&); - Class& operator= (const Class&); - protected: - Class(); + Class() = default; std::shared_ptr defaultItemActivate(const Ptr &ptr, const Ptr &actor) const; ///< Generate default action for activating inventory items @@ -72,14 +69,16 @@ namespace MWWorld public: - virtual ~Class(); + virtual ~Class() = default; + Class (const Class&) = delete; + Class& operator= (const Class&) = delete; const std::string& getTypeName() const { return mTypeName; } virtual void insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const; - virtual void insertObject(const Ptr& ptr, const std::string& mesh, MWPhysics::PhysicsSystem& physics) const; + virtual void insertObject(const Ptr& ptr, const std::string& mesh, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const; ///< Add reference into a cell for rendering (default implementation: don't render anything). virtual std::string getName (const ConstPtr& ptr) const = 0; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 427ee3aa8..313e9a152 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -75,18 +75,20 @@ namespace * osg::Quat(xr, osg::Vec3(-1, 0, 0)); } - void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, RotationOrder order) + osg::Quat makeNodeRotation(const MWWorld::Ptr& ptr, RotationOrder order) { - if (!ptr.getRefData().getBaseNode()) - return; + const auto pos = ptr.getRefData().getPosition(); - rendering.rotateObject(ptr, - ptr.getClass().isActor() - ? makeActorOsgQuat(ptr.getRefData().getPosition()) - : (order == RotationOrder::inverse - ? makeInversedOrderObjectOsgQuat(ptr.getRefData().getPosition()) - : makeObjectOsgQuat(ptr.getRefData().getPosition())) - ); + const auto rot = ptr.getClass().isActor() ? makeActorOsgQuat(pos) + : (order == RotationOrder::inverse ? makeInversedOrderObjectOsgQuat(pos) : makeObjectOsgQuat(pos)); + + return rot; + } + + void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, osg::Quat rotation) + { + if (ptr.getRefData().getBaseNode()) + rendering.rotateObject(ptr, rotation); } std::string getModel(const MWWorld::Ptr &ptr, const VFS::Manager *vfs) @@ -117,11 +119,10 @@ namespace const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) ptr.getClass().insertObjectRendering(ptr, model, rendering); - else - ptr.getRefData().setBaseNode(new SceneUtil::PositionAttitudeTransform); // FIXME remove this when physics code is fixed not to depend on basenode - setNodeRotation(ptr, rendering, RotationOrder::direct); - ptr.getClass().insertObject (ptr, model, physics); + const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); + setNodeRotation(ptr, rendering, rotation); + ptr.getClass().insertObject (ptr, model, rotation, physics); if (useAnim) MWBase::Environment::get().getMechanicsManager()->add(ptr); @@ -276,7 +277,7 @@ namespace MWWorld { if (!ptr.getRefData().getBaseNode()) return; ptr.getClass().insertObjectRendering(ptr, getModel(ptr, mRendering.getResourceSystem()->getVFS()), mRendering); - setNodeRotation(ptr, mRendering, RotationOrder::direct); + setNodeRotation(ptr, mRendering, makeNodeRotation(ptr, RotationOrder::direct)); reloadTerrain(); } } @@ -292,8 +293,9 @@ namespace MWWorld void Scene::updateObjectRotation(const Ptr &ptr, RotationOrder order) { - setNodeRotation(ptr, mRendering, order); - mPhysics->updateRotation(ptr); + const auto rot = makeNodeRotation(ptr, order); + setNodeRotation(ptr, mRendering, rot); + mPhysics->updateRotation(ptr, rot); } void Scene::updateObjectScale(const Ptr &ptr) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index bf5b6db73..af9a5e0bb 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1407,7 +1407,7 @@ namespace MWWorld mWorldScene->removeFromPagedRefs(ptr); mRendering->rotateObject(ptr, rotate); - mPhysics->updateRotation(ptr); + mPhysics->updateRotation(ptr, rotate); if (const auto object = mPhysics->getObject(ptr)) updateNavigatorObject(object); From f031a191b847443c848637b17d0936a43b5070b5 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 23 Jan 2021 20:59:24 +0100 Subject: [PATCH 078/117] Some actors are supposed to spawn on a static object that belong to an adjacent cell. Since actors can be active in 3x3 grid around the player, we need to first load all statics in a 5x5 grid around the player. Split load and unloading in 2 phases. Add an mInactiveCells set into the scene, which contains all cells inside the aforementioned 5x5 grid. These cells contains only heightfields and physics objects of static class. --- apps/openmw/mwclass/static.cpp | 5 + apps/openmw/mwclass/static.hpp | 2 + apps/openmw/mwphysics/physicssystem.cpp | 5 +- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwworld/cellvisitors.hpp | 13 +- apps/openmw/mwworld/class.hpp | 4 + apps/openmw/mwworld/scene.cpp | 425 ++++++++++++++---------- apps/openmw/mwworld/scene.hpp | 15 +- 8 files changed, 279 insertions(+), 192 deletions(-) diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 108c4eaa2..28156c97f 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -63,4 +63,9 @@ namespace MWClass return MWWorld::Ptr(cell.insert(ref), &cell); } + + bool Static::isStatic() const + { + return true; + } } diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index f856e9fd9..d0f4913f0 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -25,6 +25,8 @@ namespace MWClass static void registerSelf(); std::string getModel(const MWWorld::ConstPtr &ptr) const override; + + bool isStatic() const override; }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 68cec48bc..5a9a0be83 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -469,7 +469,7 @@ namespace MWPhysics mAnimatedObjects.insert(obj.get()); } - void PhysicsSystem::remove(const MWWorld::Ptr &ptr) + void PhysicsSystem::remove(const MWWorld::Ptr &ptr, bool keepObject) { ObjectMap::iterator found = mObjects.find(ptr); if (found != mObjects.end()) @@ -479,7 +479,8 @@ namespace MWPhysics mAnimatedObjects.erase(found->second.get()); - mObjects.erase(found); + if (!keepObject) + mObjects.erase(found); } ActorMap::iterator foundActor = mActors.find(ptr); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index c61b368f8..715a6cd1a 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -138,7 +138,7 @@ namespace MWPhysics Projectile* getProjectile(int projectileId) const; // Object or Actor - void remove (const MWWorld::Ptr& ptr); + void remove (const MWWorld::Ptr& ptr, bool keepObject = false); void updateScale (const MWWorld::Ptr& ptr); void updateRotation (const MWWorld::Ptr& ptr, osg::Quat rotate); diff --git a/apps/openmw/mwworld/cellvisitors.hpp b/apps/openmw/mwworld/cellvisitors.hpp index e68b383b7..5985d06fb 100644 --- a/apps/openmw/mwworld/cellvisitors.hpp +++ b/apps/openmw/mwworld/cellvisitors.hpp @@ -18,12 +18,23 @@ namespace MWWorld if (ptr.getRefData().getBaseNode()) { ptr.getRefData().setBaseNode(nullptr); - mObjects.push_back (ptr); } + mObjects.push_back (ptr); return true; } }; + + struct ListObjectsVisitor + { + std::vector mObjects; + + bool operator() (MWWorld::Ptr ptr) + { + mObjects.push_back (ptr); + return true; + } + }; } #endif diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 39fb6fe4c..592552d47 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -318,6 +318,10 @@ namespace MWWorld return false; } + virtual bool isStatic() const { + return false; + } + virtual bool isBipedal(const MWWorld::ConstPtr& ptr) const; virtual bool canFly(const MWWorld::ConstPtr& ptr) const; virtual bool canSwim(const MWWorld::ConstPtr& ptr) const; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 313e9a152..fcf2c4b38 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -105,7 +105,7 @@ namespace } void addObject(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, - MWRender::RenderingManager& rendering, std::set& pagedRefs) + MWRender::RenderingManager& rendering, std::set& pagedRefs, bool onlyPhysics) { if (ptr.getRefData().getBaseNode() || physics.getActor(ptr)) { @@ -113,25 +113,29 @@ namespace return; } - bool useAnim = ptr.getClass().useAnim(); std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS()); - - const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); - if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) - ptr.getClass().insertObjectRendering(ptr, model, rendering); - const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); - setNodeRotation(ptr, rendering, rotation); - ptr.getClass().insertObject (ptr, model, rotation, physics); + if (!onlyPhysics) + { + bool useAnim = ptr.getClass().useAnim(); - if (useAnim) - MWBase::Environment::get().getMechanicsManager()->add(ptr); + const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); + if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) + ptr.getClass().insertObjectRendering(ptr, model, rendering); - if (ptr.getClass().isActor()) - rendering.addWaterRippleEmitter(ptr); + setNodeRotation(ptr, rendering, rotation); - // Restore effect particles - MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); + if (useAnim) + MWBase::Environment::get().getMechanicsManager()->add(ptr); + + if (ptr.getClass().isActor()) + rendering.addWaterRippleEmitter(ptr); + + // Restore effect particles + MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); + } + if (!physics.getObject(ptr)) + ptr.getClass().insertObject (ptr, model, rotation, physics); } void addObject(const MWWorld::Ptr& ptr, const MWPhysics::PhysicsSystem& physics, DetourNavigator::Navigator& navigator) @@ -202,11 +206,12 @@ namespace { MWWorld::CellStore& mCell; Loading::Listener& mLoadingListener; + bool mOnlyStatics; bool mTest; std::vector mToInsert; - InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool test); + InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool onlyStatics, bool test); bool operator() (const MWWorld::Ptr& ptr); @@ -214,8 +219,8 @@ namespace void insert(AddObject&& addObject); }; - InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool test) - : mCell (cell), mLoadingListener (loadingListener), mTest(test) + InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool onlyStatics, bool test) + : mCell (cell), mLoadingListener (loadingListener), mOnlyStatics(onlyStatics), mTest(test) {} bool InsertVisitor::operator() (const MWWorld::Ptr& ptr) @@ -231,7 +236,7 @@ namespace { for (MWWorld::Ptr& ptr : mToInsert) { - if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled()) + if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled() && ((mOnlyStatics && ptr.getClass().isStatic()) || !mOnlyStatics)) { try { @@ -264,6 +269,16 @@ namespace return std::abs(cellPosition.first) + std::abs(cellPosition.second); } + bool isCellInCollection(int x, int y, MWWorld::Scene::CellStoreCollection& collection) + { + for (auto *cell : collection) + { + assert(cell->getCell()->isExterior()); + if (x == cell->getCell()->getGridX() && y == cell->getCell()->getGridY()) + return true; + } + return false; + } } @@ -315,15 +330,41 @@ namespace MWWorld mRendering.update (duration, paused); } - void Scene::unloadCell (CellStoreCollection::iterator iter, bool test) + void Scene::unloadInactiveCell (CellStore* cell, bool test) { + assert(mActiveCells.find(cell) == mActiveCells.end()); + assert(mInactiveCells.find(cell) != mInactiveCells.end()); if (!test) - Log(Debug::Info) << "Unloading cell " << (*iter)->getCell()->getDescription(); + Log(Debug::Info) << "Unloading cell " << cell->getCell()->getDescription(); + + ListObjectsVisitor visitor; + + cell->forEach(visitor); + for (const auto& ptr : visitor.mObjects) + mPhysics->remove(ptr); + + if (cell->getCell()->isExterior()) + { + const auto cellX = cell->getCell()->getGridX(); + const auto cellY = cell->getCell()->getGridY(); + mPhysics->removeHeightField(cellX, cellY); + } + + mInactiveCells.erase(cell); + } + + void Scene::deactivateCell(CellStore* cell, bool test) + { + assert(mInactiveCells.find(cell) != mInactiveCells.end()); + if (mActiveCells.find(cell) == mActiveCells.end()) + return; + if (!test) + Log(Debug::Info) << "Deactivate cell " << cell->getCell()->getDescription(); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); ListAndResetObjectsVisitor visitor; - (*iter)->forEach(visitor); + cell->forEach(visitor); const auto world = MWBase::Environment::get().getWorld(); for (const auto& ptr : visitor.mObjects) { @@ -334,140 +375,157 @@ namespace MWWorld navigator->removeAgent(world->getPathfindingHalfExtents(ptr)); mRendering.removeActorPath(ptr); } - mPhysics->remove(ptr); + mPhysics->remove(ptr, ptr.getClass().isStatic()); } - const auto cellX = (*iter)->getCell()->getGridX(); - const auto cellY = (*iter)->getCell()->getGridY(); + const auto cellX = cell->getCell()->getGridX(); + const auto cellY = cell->getCell()->getGridY(); - if ((*iter)->getCell()->isExterior()) + if (cell->getCell()->isExterior()) { if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) navigator->removeObject(DetourNavigator::ObjectId(heightField)); - mPhysics->removeHeightField(cellX, cellY); } - if ((*iter)->getCell()->hasWater()) + if (cell->getCell()->hasWater()) navigator->removeWater(osg::Vec2i(cellX, cellY)); - if (const auto pathgrid = world->getStore().get().search(*(*iter)->getCell())) + if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) navigator->removePathgrid(*pathgrid); const auto player = world->getPlayerPtr(); navigator->update(player.getRefData().getPosition().asVec3()); - MWBase::Environment::get().getMechanicsManager()->drop (*iter); + MWBase::Environment::get().getMechanicsManager()->drop (cell); - mRendering.removeCell(*iter); - MWBase::Environment::get().getWindowManager()->removeCell(*iter); + mRendering.removeCell(cell); + MWBase::Environment::get().getWindowManager()->removeCell(cell); - MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (*iter); + MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (cell); - MWBase::Environment::get().getSoundManager()->stopSound (*iter); - mActiveCells.erase(*iter); + MWBase::Environment::get().getSoundManager()->stopSound (cell); + mActiveCells.erase(cell); } - void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test) + void Scene::activateCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test) { - std::pair result = mActiveCells.insert(cell); + assert(mActiveCells.find(cell) == mActiveCells.end()); + assert(mInactiveCells.find(cell) != mInactiveCells.end()); + mActiveCells.insert(cell); - if(result.second) + if (test) + Log(Debug::Info) << "Testing cell " << cell->getCell()->getDescription(); + else + Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); + + const auto world = MWBase::Environment::get().getWorld(); + const auto navigator = world->getNavigator(); + + const int cellX = cell->getCell()->getGridX(); + const int cellY = cell->getCell()->getGridY(); + + if (!test && cell->getCell()->isExterior()) { - if (test) - Log(Debug::Info) << "Testing cell " << cell->getCell()->getDescription(); - else - Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); + if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) + navigator->addObject(DetourNavigator::ObjectId(heightField), *heightField->getShape(), + heightField->getCollisionObject()->getWorldTransform()); + } - float verts = ESM::Land::LAND_SIZE; - float worldsize = ESM::Land::REAL_SIZE; + if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) + navigator->addPathgrid(*cell->getCell(), *pathgrid); - const auto world = MWBase::Environment::get().getWorld(); - const auto navigator = world->getNavigator(); + // register local scripts + // do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice + MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); - const int cellX = cell->getCell()->getGridX(); - const int cellY = cell->getCell()->getGridY(); + if (respawn) + cell->respawn(); - // Load terrain physics first... - if (!test && cell->getCell()->isExterior()) + insertCell (*cell, loadingListener, false, test); + + mRendering.addCell(cell); + if (!test) + { + MWBase::Environment::get().getWindowManager()->addCell(cell); + bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior(); + float waterLevel = cell->getWaterLevel(); + mRendering.setWaterEnabled(waterEnabled); + if (waterEnabled) { - osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); - const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; - if (data) + mPhysics->enableWater(waterLevel); + mRendering.setWaterHeight(waterLevel); + + if (cell->getCell()->isExterior()) { - mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get()); - } - else - { - static std::vector defaultHeight; - defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); - mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); - } - - if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) - navigator->addObject(DetourNavigator::ObjectId(heightField), *heightField->getShape(), - heightField->getCollisionObject()->getWorldTransform()); - } - - if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) - navigator->addPathgrid(*cell->getCell(), *pathgrid); - - // register local scripts - // do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice - MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); - - if (respawn) - cell->respawn(); - - // ... then references. This is important for adjustPosition to work correctly. - insertCell (*cell, loadingListener, test); - - mRendering.addCell(cell); - if (!test) - { - MWBase::Environment::get().getWindowManager()->addCell(cell); - bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior(); - float waterLevel = cell->getWaterLevel(); - mRendering.setWaterEnabled(waterEnabled); - if (waterEnabled) - { - mPhysics->enableWater(waterLevel); - mRendering.setWaterHeight(waterLevel); - - if (cell->getCell()->isExterior()) - { - if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) - navigator->addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, + if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) + navigator->addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, cell->getWaterLevel(), heightField->getCollisionObject()->getWorldTransform()); - } - else - { - navigator->addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), - cell->getWaterLevel(), btTransform::getIdentity()); - } } else - mPhysics->disableWater(); - - const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - navigator->update(player.getRefData().getPosition().asVec3()); - - if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) { - - mRendering.configureAmbient(cell->getCell()); + navigator->addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), + cell->getWaterLevel(), btTransform::getIdentity()); } } + else + mPhysics->disableWater(); + + const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + navigator->update(player.getRefData().getPosition().asVec3()); + + if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) + mRendering.configureAmbient(cell->getCell()); } mPreloader->notifyLoaded(cell); } + void Scene::loadInactiveCell (CellStore *cell, Loading::Listener* loadingListener, bool test) + { + assert(mActiveCells.find(cell) == mActiveCells.end()); + assert(mInactiveCells.find(cell) == mInactiveCells.end()); + mInactiveCells.insert(cell); + + if (test) + Log(Debug::Info) << "Testing inactive cell " << cell->getCell()->getDescription(); + else + Log(Debug::Info) << "Loading inactive cell " << cell->getCell()->getDescription(); + + if (!test && cell->getCell()->isExterior()) + { + float verts = ESM::Land::LAND_SIZE; + float worldsize = ESM::Land::REAL_SIZE; + + const int cellX = cell->getCell()->getGridX(); + const int cellY = cell->getCell()->getGridY(); + + osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; + if (data) + { + mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get()); + } + else + { + static std::vector defaultHeight; + defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); + mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); + } + } + + insertCell (*cell, loadingListener, true, test); + } + void Scene::clear() { - CellStoreCollection::iterator active = mActiveCells.begin(); - while (active!=mActiveCells.end()) - unloadCell (active++); + for (auto iter = mInactiveCells.begin(); iter!=mInactiveCells.end(); ) + { + auto* cell = *iter++; + deactivateCell(cell); + unloadInactiveCell (cell); + } assert(mActiveCells.empty()); + assert(mInactiveCells.empty()); mCurrentCell = nullptr; mPreloader->clear(); @@ -510,20 +568,24 @@ namespace MWWorld void Scene::changeCellGrid (const osg::Vec3f &pos, int playerCellX, int playerCellY, bool changeEvent) { - CellStoreCollection::iterator active = mActiveCells.begin(); - while (active!=mActiveCells.end()) + for (auto iter = mInactiveCells.begin(); iter != mInactiveCells.end(); ) { - if ((*active)->getCell()->isExterior()) + auto* cell = *iter++; + if (cell->getCell()->isExterior()) { - if (std::abs (playerCellX-(*active)->getCell()->getGridX())<=mHalfGridSize && - std::abs (playerCellY-(*active)->getCell()->getGridY())<=mHalfGridSize) - { - // keep cells within the new grid - ++active; - continue; - } + const auto dx = std::abs(playerCellX - cell->getCell()->getGridX()); + const auto dy = std::abs(playerCellY - cell->getCell()->getGridY()); + if (dx > mHalfGridSize || dy > mHalfGridSize) + deactivateCell(cell); + + if (dx > mHalfGridSize+1 || dy > mHalfGridSize+1) + unloadInactiveCell(cell); + } + else + { + deactivateCell(cell); + unloadInactiveCell(cell); } - unloadCell (active++); } mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY); @@ -535,32 +597,24 @@ namespace MWWorld mRendering.getPagedRefnums(newGrid, mPagedRefs); std::size_t refsToLoad = 0; - std::vector> cellsPositionsToLoad; - // get the number of refs to load - for (int x = playerCellX - mHalfGridSize; x <= playerCellX + mHalfGridSize; ++x) + const auto cellsToLoad = [&playerCellX,&playerCellY,&refsToLoad](CellStoreCollection& collection, int range) -> std::vector> { - for (int y = playerCellY - mHalfGridSize; y <= playerCellY + mHalfGridSize; ++y) + std::vector> cellsPositionsToLoad; + for (int x = playerCellX - range; x <= playerCellX + range; ++x) { - CellStoreCollection::iterator iter = mActiveCells.begin(); - - while (iter!=mActiveCells.end()) + for (int y = playerCellY - range; y <= playerCellY + range; ++y) { - assert ((*iter)->getCell()->isExterior()); - - if (x==(*iter)->getCell()->getGridX() && - y==(*iter)->getCell()->getGridY()) - break; - - ++iter; - } - - if (iter==mActiveCells.end()) - { - refsToLoad += MWBase::Environment::get().getWorld()->getExterior(x, y)->count(); - cellsPositionsToLoad.emplace_back(x, y); + if (!isCellInCollection(x, y, collection)) + { + refsToLoad += MWBase::Environment::get().getWorld()->getExterior(x, y)->count(); + cellsPositionsToLoad.emplace_back(x, y); + } } } - } + return cellsPositionsToLoad; + }; + auto cellsPositionsToLoad = cellsToLoad(mActiveCells,mHalfGridSize); + auto cellsPositionsToLoadInactive = cellsToLoad(mInactiveCells,mHalfGridSize+1); Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); @@ -584,30 +638,26 @@ namespace MWWorld return getCellPositionPriority(lhs) < getCellPositionPriority(rhs); }); + std::sort(cellsPositionsToLoadInactive.begin(), cellsPositionsToLoadInactive.end(), + [&] (const std::pair& lhs, const std::pair& rhs) { + return getCellPositionPriority(lhs) < getCellPositionPriority(rhs); + }); + // Load cells - for (const auto& cellPosition : cellsPositionsToLoad) + for (const auto& [x,y] : cellsPositionsToLoadInactive) { - const auto x = cellPosition.first; - const auto y = cellPosition.second; - - CellStoreCollection::iterator iter = mActiveCells.begin(); - - while (iter != mActiveCells.end()) - { - assert ((*iter)->getCell()->isExterior()); - - if (x == (*iter)->getCell()->getGridX() && - y == (*iter)->getCell()->getGridY()) - break; - - ++iter; - } - - if (iter == mActiveCells.end()) + if (!isCellInCollection(x, y, mInactiveCells)) { CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); - - loadCell (cell, loadingListener, changeEvent); + loadInactiveCell (cell, loadingListener); + } + } + for (const auto& [x,y] : cellsPositionsToLoad) + { + if (!isCellInCollection(x, y, mActiveCells)) + { + CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); + activateCell (cell, loadingListener, changeEvent); } } @@ -640,7 +690,8 @@ namespace MWWorld CellStoreCollection::iterator iter = mActiveCells.begin(); CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(it->mData.mX, it->mData.mY); - loadCell (cell, loadingListener, false, true); + loadInactiveCell (cell, loadingListener, true); + activateCell (cell, loadingListener, false, true); iter = mActiveCells.begin(); while (iter != mActiveCells.end()) @@ -648,7 +699,8 @@ namespace MWWorld if (it->isExterior() && it->mData.mX == (*iter)->getCell()->getGridX() && it->mData.mY == (*iter)->getCell()->getGridY()) { - unloadCell(iter, true); + deactivateCell(*iter, true); + unloadInactiveCell (*iter, true); break; } @@ -686,7 +738,8 @@ namespace MWWorld loadingListener->setLabel("Testing interior cells ("+std::to_string(i)+"/"+std::to_string(cells.getIntSize())+")..."); CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(it->mName); - loadCell (cell, loadingListener, false, true); + loadInactiveCell (cell, loadingListener, true); + activateCell (cell, loadingListener, false, true); CellStoreCollection::iterator iter = mActiveCells.begin(); while (iter != mActiveCells.end()) @@ -695,7 +748,8 @@ namespace MWWorld if (it->mName == (*iter)->getCell()->mName) { - unloadCell(iter, true); + deactivateCell(*iter, true); + unloadInactiveCell (*iter, true); break; } @@ -818,15 +872,21 @@ namespace MWWorld Log(Debug::Info) << "Changing to interior"; // unload - CellStoreCollection::iterator active = mActiveCells.begin(); - while (active!=mActiveCells.end()) - unloadCell (active++); + for (auto iter = mInactiveCells.begin(); iter!=mInactiveCells.end(); ) + { + auto* cell = *iter++; + deactivateCell(cell); + unloadInactiveCell(cell); + } + assert(mActiveCells.empty()); + assert(mInactiveCells.empty()); loadingListener->setProgressRange(cell->count()); // Load cell. mPagedRefs.clear(); - loadCell (cell, loadingListener, changeEvent); + loadInactiveCell (cell, loadingListener); + activateCell (cell, loadingListener, changeEvent); changePlayerCell(cell, position, adjustPlayerPos); @@ -874,23 +934,26 @@ namespace MWWorld mCellChanged = false; } - void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test) + void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyStatics, bool test) { - InsertVisitor insertVisitor (cell, *loadingListener, test); + InsertVisitor insertVisitor (cell, *loadingListener, onlyStatics, test); cell.forEach (insertVisitor); - insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs); }); - insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs, onlyStatics); }); + if (!onlyStatics) + { + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); - // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order - PositionVisitor posVisitor; - cell.forEach (posVisitor); + // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order + PositionVisitor posVisitor; + cell.forEach (posVisitor); + } } void Scene::addObjectToScene (const Ptr& ptr) { try { - addObject(ptr, *mPhysics, mRendering, mPagedRefs); + addObject(ptr, *mPhysics, mRendering, mPagedRefs, false); addObject(ptr, *mPhysics, mNavigator); MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index a70d3ccdd..33c7b78d0 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -65,13 +65,13 @@ namespace MWWorld class Scene { public: - - typedef std::set CellStoreCollection; + using CellStoreCollection = std::set; private: CellStore* mCurrentCell; // the cell the player is in CellStoreCollection mActiveCells; + CellStoreCollection mInactiveCells; bool mCellChanged; MWPhysics::PhysicsSystem *mPhysics; MWRender::RenderingManager& mRendering; @@ -92,7 +92,7 @@ namespace MWWorld std::set mPagedRefs; - void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test = false); + void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyStatics, bool test = false); osg::Vec2i mCurrentGridCenter; // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center @@ -108,6 +108,11 @@ namespace MWWorld osg::Vec4i gridCenterToBounds(const osg::Vec2i ¢erCell) const; osg::Vec2i getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i *currentGridCenter = nullptr) const; + void unloadInactiveCell (CellStore* cell, bool test = false); + void deactivateCell (CellStore* cell, bool test = false); + void activateCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test = false); + void loadInactiveCell (CellStore *cell, Loading::Listener* loadingListener, bool test = false); + public: Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics, @@ -119,10 +124,6 @@ namespace MWWorld void preloadTerrain(const osg::Vec3f& pos, bool sync=false); void reloadTerrain(); - void unloadCell (CellStoreCollection::iterator iter, bool test = false); - - void loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test = false); - void playerMoved (const osg::Vec3f& pos); void changePlayerCell (CellStore* newCell, const ESM::Position& position, bool adjustPlayerPos); From f219c5992bc6efe688ab128915f2d36c39da47a0 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 24 Jan 2021 11:28:26 +0100 Subject: [PATCH 079/117] Add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cede01f63..64528e5c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ Bug #5367: Selecting a spell on an enchanted item per hotkey always plays the equip sound Bug #5369: Spawnpoint in the Grazelands doesn't produce oversized creatures Bug #5370: Opening an unlocked but trapped door uses the key + Bug #5379: Wandering NPCs falling through cantons Bug #5384: openmw-cs: deleting an instance requires reload of scene window to show in editor Bug #5387: Move/MoveWorld don't update the object's cell properly Bug #5391: Races Redone 1.2 bodies don't show on the inventory From a2171875a069c2704ee035f1a407eaacee2a3979 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 24 Jan 2021 15:15:51 +0100 Subject: [PATCH 080/117] Prevent nullptr access --- apps/openmw/mwworld/projectilemanager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index c87f62505..092ce270e 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -428,6 +428,9 @@ namespace MWWorld { for (auto& projectileState : mProjectiles) { + if (projectileState.mToDelete) + continue; + auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); if (!projectile->isActive()) continue; From fe815d3d8dcfe5007b229082370113865c5ba930 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sun, 24 Jan 2021 15:22:27 +0000 Subject: [PATCH 081/117] Fix memory leak in MWInput mListener wasn't being cleaned up --- apps/openmw/mwinput/bindingsmanager.cpp | 13 ++++++------- apps/openmw/mwinput/bindingsmanager.hpp | 5 +++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwinput/bindingsmanager.cpp b/apps/openmw/mwinput/bindingsmanager.cpp index 18fac6ae2..851e33a87 100644 --- a/apps/openmw/mwinput/bindingsmanager.cpp +++ b/apps/openmw/mwinput/bindingsmanager.cpp @@ -171,16 +171,16 @@ namespace MWInput , mDragDrop(false) { std::string file = userFileExists ? userFile : ""; - mInputBinder = new InputControlSystem(file); - mListener = new BindingsListener(mInputBinder, this); - mInputBinder->setDetectingBindingListener(mListener); + mInputBinder = std::make_unique(file); + mListener = std::make_unique(mInputBinder.get(), this); + mInputBinder->setDetectingBindingListener(mListener.get()); loadKeyDefaults(); loadControllerDefaults(); for (int i = 0; i < A_Last; ++i) { - mInputBinder->getChannel(i)->addListener(mListener); + mInputBinder->getChannel(i)->addListener(mListener.get()); } } @@ -192,7 +192,6 @@ namespace MWInput BindingsManager::~BindingsManager() { mInputBinder->save(mUserFile); - delete mInputBinder; } void BindingsManager::update(float dt) @@ -315,7 +314,7 @@ namespace MWInput && mInputBinder->getMouseButtonBinding(control, ICS::Control::INCREASE) == ICS_MAX_DEVICE_BUTTONS && mInputBinder->getMouseWheelBinding(control, ICS::Control::INCREASE) == ICS::InputControlSystem::MouseWheelClick::UNASSIGNED)) { - clearAllKeyBindings(mInputBinder, control); + clearAllKeyBindings(mInputBinder.get(), control); if (defaultKeyBindings.find(i) != defaultKeyBindings.end() && (force || !mInputBinder->isKeyBound(defaultKeyBindings[i]))) @@ -402,7 +401,7 @@ namespace MWInput if (!controlExists || force || (mInputBinder->getJoystickAxisBinding(control, sFakeDeviceId, ICS::Control::INCREASE) == ICS::InputControlSystem::UNASSIGNED && mInputBinder->getJoystickButtonBinding(control, sFakeDeviceId, ICS::Control::INCREASE) == ICS_MAX_DEVICE_BUTTONS)) { - clearAllControllerBindings(mInputBinder, control); + clearAllControllerBindings(mInputBinder.get(), control); if (defaultButtonBindings.find(i) != defaultButtonBindings.end() && (force || !mInputBinder->isJoystickButtonBound(sFakeDeviceId, defaultButtonBindings[i]))) diff --git a/apps/openmw/mwinput/bindingsmanager.hpp b/apps/openmw/mwinput/bindingsmanager.hpp index 7a44a1a33..74416d3c7 100644 --- a/apps/openmw/mwinput/bindingsmanager.hpp +++ b/apps/openmw/mwinput/bindingsmanager.hpp @@ -1,6 +1,7 @@ #ifndef MWINPUT_MWBINDINGSMANAGER_H #define MWINPUT_MWBINDINGSMANAGER_H +#include #include #include @@ -64,8 +65,8 @@ namespace MWInput private: void setupSDLKeyMappings(); - InputControlSystem* mInputBinder; - BindingsListener* mListener; + std::unique_ptr mInputBinder; + std::unique_ptr mListener; std::string mUserFile; From 3bb551a6f12e1c90f884a82b7806832a38bf2284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Mon, 25 Jan 2021 10:01:39 +0000 Subject: [PATCH 082/117] Show level multipliers in levelup tooltip --- apps/openmw/mwgui/statswindow.cpp | 21 +++++++++++---------- apps/openmw/mwmechanics/npcstats.cpp | 5 ----- apps/openmw/mwmechanics/npcstats.hpp | 2 -- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 66a1ea1ef..4c4450ee0 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -337,21 +337,22 @@ namespace MWGui int max = MWBase::Environment::get().getWorld()->getStore().get().find("iLevelUpTotal")->mValue.getInteger(); getWidget(levelWidget, i==0 ? "Level_str" : "LevelText"); - std::stringstream detail; - for (int i = 0; i < ESM::Attribute::Length; ++i) - { - if (auto increase = PCstats.getLevelUpAttributeIncrease(i)) - detail << (detail.str().empty() ? "" : "\n") << "#{" - << MyGUI::TextIterator::toTagsString(ESM::Attribute::sGmstAttributeIds[i]) - << "} x" << MyGUI::utility::toString(increase); - } - if (!detail.str().empty()) - levelWidget->setUserString("Caption_LevelDetailText", MyGUI::LanguageManager::getInstance().replaceTags(detail.str())); levelWidget->setUserString("RangePosition_LevelProgress", MyGUI::utility::toString(PCstats.getLevelProgress())); levelWidget->setUserString("Range_LevelProgress", MyGUI::utility::toString(max)); levelWidget->setUserString("Caption_LevelProgressText", MyGUI::utility::toString(PCstats.getLevelProgress()) + "/" + MyGUI::utility::toString(max)); } + std::stringstream detail; + for (int attribute = 0; attribute < ESM::Attribute::Length; ++attribute) + { + float mult = PCstats.getLevelupAttributeMultiplier(attribute); + mult = std::min(mult, 100 - PCstats.getAttribute(attribute).getBase()); + if (mult > 1) + detail << (detail.str().empty() ? "" : "\n") << "#{" + << MyGUI::TextIterator::toTagsString(ESM::Attribute::sGmstAttributeIds[attribute]) + << "} x" << MyGUI::utility::toString(mult); + } + levelWidget->setUserString("Caption_LevelDetailText", MyGUI::LanguageManager::getInstance().replaceTags(detail.str())); setFactions(PCstats.getFactionRanks()); setExpelled(PCstats.getExpelled ()); diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 71453cd07..5d19368bf 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -322,11 +322,6 @@ void MWMechanics::NpcStats::updateHealth() setHealth(floor(0.5f * (strength + endurance))); } -int MWMechanics::NpcStats::getLevelUpAttributeIncrease(int attribute) const -{ - return mSkillIncreases[attribute]; -} - int MWMechanics::NpcStats::getLevelupAttributeMultiplier(int attribute) const { int num = mSkillIncreases[attribute]; diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index cab52cb28..9bd8e20ad 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -87,8 +87,6 @@ namespace MWMechanics int getLevelProgress() const; - int getLevelUpAttributeIncrease(int attribute) const; - int getLevelupAttributeMultiplier(int attribute) const; int getSkillIncreasesForSpecialization(int spec) const; From bc2cec86e9ca16abdab140aff40a1739e35c066a Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 24 Dec 2020 04:18:39 +0100 Subject: [PATCH 083/117] Fix bug: NPCs doesn't move if the target is exactly above or exactly below. --- apps/openmw/mwmechanics/pathfinding.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 276321b81..595f9d629 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -309,7 +309,7 @@ namespace MWMechanics if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance)) mPath.pop_front(); - if (mPath.size() == 1 && sqrDistanceIgnoreZ(mPath.front(), position) < destinationTolerance * destinationTolerance) + if (mPath.size() == 1 && (mPath.front() - position).length2() < destinationTolerance * destinationTolerance) mPath.pop_front(); } From 14cf0ce1dc67d561a04a3e5b13fa633af54ca939 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 12 Jan 2020 11:42:47 +0400 Subject: [PATCH 084/117] Implement instanced groundcover --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/engine.cpp | 9 +- apps/openmw/engine.hpp | 2 + apps/openmw/main.cpp | 15 +- apps/openmw/mwgui/settingswindow.cpp | 4 +- apps/openmw/mwrender/groundcover.cpp | 264 ++++++++++++++++++ apps/openmw/mwrender/groundcover.hpp | 69 +++++ apps/openmw/mwrender/objectpaging.cpp | 1 + apps/openmw/mwrender/renderingmanager.cpp | 77 ++++- apps/openmw/mwrender/renderingmanager.hpp | 9 +- apps/openmw/mwrender/vismask.hpp | 4 +- apps/openmw/mwrender/water.cpp | 5 +- apps/openmw/mwworld/cellpreloader.cpp | 7 + apps/openmw/mwworld/cellreflist.hpp | 2 + apps/openmw/mwworld/cellstore.cpp | 13 + apps/openmw/mwworld/contentloader.hpp | 2 +- apps/openmw/mwworld/esmloader.cpp | 18 +- apps/openmw/mwworld/esmloader.hpp | 2 +- apps/openmw/mwworld/esmstore.cpp | 9 + apps/openmw/mwworld/esmstore.hpp | 17 ++ apps/openmw/mwworld/worldimp.cpp | 29 +- apps/openmw/mwworld/worldimp.hpp | 3 +- components/config/gamesettings.cpp | 2 + components/esm/esmreader.cpp | 9 +- components/esm/esmreader.hpp | 6 +- components/esm/loadstat.cpp | 2 + components/esm/loadstat.hpp | 2 + components/resource/scenemanager.cpp | 6 +- components/resource/scenemanager.hpp | 2 +- components/resource/stats.cpp | 4 +- components/shader/shadermanager.cpp | 2 + components/terrain/chunkmanager.cpp | 2 +- components/terrain/chunkmanager.hpp | 2 +- components/terrain/quadtreeworld.cpp | 29 +- components/terrain/quadtreeworld.hpp | 5 +- components/terrain/terraingrid.cpp | 8 + components/terrain/terraingrid.hpp | 1 + components/terrain/world.cpp | 41 ++- components/terrain/world.hpp | 1 + docs/source/reference/modding/extended.rst | 56 +++- .../modding/settings/groundcover.rst | 68 +++++ .../reference/modding/settings/index.rst | 1 + .../reference/modding/settings/shaders.rst | 1 + .../reference/modding/settings/water.rst | 3 +- files/mygui/openmw_settings_window.layout | 1 + files/settings-default.cfg | 19 ++ files/shaders/CMakeLists.txt | 2 + files/shaders/groundcover_fragment.glsl | 93 ++++++ files/shaders/groundcover_vertex.glsl | 143 ++++++++++ files/shaders/lighting.glsl | 15 +- 50 files changed, 1015 insertions(+), 74 deletions(-) create mode 100644 apps/openmw/mwrender/groundcover.cpp create mode 100644 apps/openmw/mwrender/groundcover.hpp create mode 100644 docs/source/reference/modding/settings/groundcover.rst create mode 100644 files/shaders/groundcover_fragment.glsl create mode 100644 files/shaders/groundcover_vertex.glsl diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index c01cbe60c..fdc47e8d7 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -21,7 +21,7 @@ add_openmw_dir (mwrender actors objects renderingmanager animation rotatecontroller sky npcanimation vismask creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager bulletdebugdraw globalmap characterpreview camera viewovershoulder localmap water terrainstorage ripplesimulation - renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging + renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover ) add_openmw_dir (mwinput diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index ead2726cd..103d06f31 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -460,6 +460,11 @@ void OMW::Engine::addContentFile(const std::string& file) mContentFiles.push_back(file); } +void OMW::Engine::addGroundcoverFile(const std::string& file) +{ + mGroundcoverFiles.emplace_back(file); +} + void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame) { mSkipMenu = skipMenu; @@ -720,8 +725,8 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) } // Create the world - mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), - mFileCollections, mContentFiles, mEncoder, mActivationDistanceOverride, mCellName, + mEnvironment.setWorld(new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), + mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder, mActivationDistanceOverride, mCellName, mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string())); mEnvironment.getWorld()->setupPlayer(); diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 3dd1a69b2..ff362f4b6 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -85,6 +85,7 @@ namespace OMW osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation; std::string mCellName; std::vector mContentFiles; + std::vector mGroundcoverFiles; bool mSkipMenu; bool mUseSound; bool mCompileAll; @@ -155,6 +156,7 @@ namespace OMW * @param file - filename (extension is required) */ void addContentFile(const std::string& file); + void addGroundcoverFile(const std::string& file); /// Disable or enable all sounds void setSoundUsage(bool soundUsage); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 8eaac36e8..709ffda2c 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -62,6 +62,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("content", bpo::value()->default_value(Files::EscapeStringVector(), "") ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon") + ("groundcover", bpo::value()->default_value(Files::EscapeStringVector(), "") + ->multitoken()->composing(), "groundcover content file(s): esm/esp, or omwgame/omwaddon") + ("no-sound", bpo::value()->implicit_value(true) ->default_value(false), "disable all sounds") @@ -190,11 +193,15 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat return false; } - StringsVector::const_iterator it(content.begin()); - StringsVector::const_iterator end(content.end()); - for (; it != end; ++it) + for (auto& file : content) { - engine.addContentFile(*it); + engine.addContentFile(file); + } + + StringsVector groundcover = variables["groundcover"].as().toStdStringVector(); + for (auto& file : groundcover) + { + engine.addGroundcoverFile(file); } // startup-settings diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 68dac4a95..538b3db5e 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -269,7 +269,7 @@ namespace MWGui mWaterTextureSize->setIndexSelected(2); int waterReflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); - waterReflectionDetail = std::min(4, std::max(0, waterReflectionDetail)); + waterReflectionDetail = std::min(5, std::max(0, waterReflectionDetail)); mWaterReflectionDetail->setIndexSelected(waterReflectionDetail); mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video")); @@ -353,7 +353,7 @@ namespace MWGui void SettingsWindow::onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos) { - unsigned int level = std::min((unsigned int)4, (unsigned int)pos); + unsigned int level = std::min((unsigned int)5, (unsigned int)pos); Settings::Manager::setInt("reflection detail", "Water", level); apply(); } diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp new file mode 100644 index 000000000..b9fdc2e28 --- /dev/null +++ b/apps/openmw/mwrender/groundcover.cpp @@ -0,0 +1,264 @@ +#include "groundcover.hpp" + +#include +#include + +#include + +#include "apps/openmw/mwworld/esmstore.hpp" +#include "apps/openmw/mwbase/environment.hpp" +#include "apps/openmw/mwbase/world.hpp" + +#include "vismask.hpp" + +namespace MWRender +{ + void GroundcoverUpdater::setWindSpeed(float windSpeed) + { + mWindSpeed = windSpeed; + } + + void GroundcoverUpdater::setPlayerPos(osg::Vec3f playerPos) + { + mPlayerPos = playerPos; + } + + void GroundcoverUpdater::setDefaults(osg::StateSet *stateset) + { + osg::ref_ptr windUniform = new osg::Uniform("windSpeed", 0.0f); + stateset->addUniform(windUniform.get()); + + osg::ref_ptr playerPosUniform = new osg::Uniform("playerPos", osg::Vec3f(0.f, 0.f, 0.f)); + stateset->addUniform(playerPosUniform.get()); + } + + void GroundcoverUpdater::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) + { + osg::ref_ptr windUniform = stateset->getUniform("windSpeed"); + if (windUniform != nullptr) + windUniform->set(mWindSpeed); + + osg::ref_ptr playerPosUniform = stateset->getUniform("playerPos"); + if (playerPosUniform != nullptr) + playerPosUniform->set(mPlayerPos); + } + + class InstancingVisitor : public osg::NodeVisitor + { + public: + InstancingVisitor(std::vector& instances, osg::Vec3f& chunkPosition) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mInstances(instances) + , mChunkPosition(chunkPosition) + { + } + + void apply(osg::Node& node) override + { + osg::ref_ptr ss = node.getStateSet(); + if (ss != nullptr) + { + removeAlpha(ss); + } + + traverse(node); + } + + void apply(osg::Geometry& geom) override + { + for (unsigned int i = 0; i < geom.getNumPrimitiveSets(); ++i) + { + geom.getPrimitiveSet(i)->setNumInstances(mInstances.size()); + } + + osg::ref_ptr transforms = new osg::Vec4Array(mInstances.size()); + osg::BoundingBox box; + float radius = geom.getBoundingBox().radius(); + for (unsigned int i = 0; i < transforms->getNumElements(); i++) + { + osg::Vec3f pos(mInstances[i].mPos.asVec3()); + osg::Vec3f relativePos = pos - mChunkPosition; + (*transforms)[i] = osg::Vec4f(relativePos, mInstances[i].mScale); + + // Use an additional margin due to groundcover animation + float instanceRadius = radius * mInstances[i].mScale * 1.1f; + osg::BoundingSphere instanceBounds(relativePos, instanceRadius); + box.expandBy(instanceBounds); + } + + geom.setInitialBound(box); + + osg::ref_ptr rotations = new osg::Vec3Array(mInstances.size()); + for (unsigned int i = 0; i < rotations->getNumElements(); i++) + { + (*rotations)[i] = mInstances[i].mPos.asRotationVec3(); + } + + geom.setVertexAttribArray(6, transforms.get(), osg::Array::BIND_PER_VERTEX); + geom.setVertexAttribArray(7, rotations.get(), osg::Array::BIND_PER_VERTEX); + + osg::ref_ptr ss = geom.getOrCreateStateSet(); + ss->setAttribute(new osg::VertexAttribDivisor(6, 1)); + ss->setAttribute(new osg::VertexAttribDivisor(7, 1)); + + removeAlpha(ss); + + traverse(geom); + } + private: + std::vector mInstances; + osg::Vec3f mChunkPosition; + + void removeAlpha(osg::StateSet* stateset) + { + // MGE uses default alpha settings for groundcover, so we can not rely on alpha properties + stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); + stateset->removeMode(GL_ALPHA_TEST); + stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); + stateset->removeMode(GL_BLEND); + stateset->setRenderBinToInherit(); + } + }; + + class DensityCalculator + { + public: + DensityCalculator(float density) + : mDensity(density) + { + } + + bool isInstanceEnabled() + { + if (mDensity >= 1.f) return true; + + mCurrentGroundcover += mDensity; + if (mCurrentGroundcover < 1.f) return false; + + mCurrentGroundcover -= 1.f; + + return true; + } + void reset() { mCurrentGroundcover = 0.f; } + + private: + float mCurrentGroundcover = 0.f; + float mDensity = 0.f; + }; + + inline bool isInChunkBorders(ESM::CellRef& ref, osg::Vec2f& minBound, osg::Vec2f& maxBound) + { + osg::Vec2f size = maxBound - minBound; + if (size.x() >=1 && size.y() >=1) return true; + + osg::Vec3f pos = ref.mPos.asVec3(); + osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE; + if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y()) + || (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) || (minBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y())) + return false; + + return true; + } + + osg::ref_ptr Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) + { + ChunkId id = std::make_tuple(center, size, activeGrid); + + osg::ref_ptr obj = mCache->getRefFromObjectCache(id); + if (obj) + return obj->asNode(); + else + { + InstanceMap instances; + collectInstances(instances, size, center); + osg::ref_ptr node = createChunk(instances, center); + mCache->addEntryToObjectCache(id, node.get()); + return node; + } + } + + Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density) + : GenericResourceManager(nullptr) + , mSceneManager(sceneManager) + , mDensity(density) + { + } + + void Groundcover::collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center) + { + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + osg::Vec2f minBound = (center - osg::Vec2f(size/2.f, size/2.f)); + osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f)); + DensityCalculator calculator(mDensity); + std::vector esm; + osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size/2.f), std::floor(center.y() - size/2.f)); + for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX) + { + for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY) + { + const ESM::Cell* cell = store.get().searchStatic(cellX, cellY); + if (!cell) continue; + + calculator.reset(); + for (size_t i=0; imContextList.size(); ++i) + { + unsigned int index = cell->mContextList.at(i).index; + if (esm.size() <= index) + esm.resize(index+1); + cell->restore(esm[index], i); + ESM::CellRef ref; + ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; + bool deleted = false; + while(cell->getNextRef(esm[index], ref, deleted)) + { + if (deleted) continue; + Misc::StringUtils::lowerCaseInPlace(ref.mRefID); + std::string model; + if (!store.isGroundcover(ref.mRefID, model)) continue; + if (model.empty()) continue; + + if (!calculator.isInstanceEnabled()) continue; + if (!isInChunkBorders(ref, minBound, maxBound)) continue; + + model = "meshes/" + model; + instances[model].emplace_back(ref, model); + } + } + } + } + } + + osg::ref_ptr Groundcover::createChunk(InstanceMap& instances, const osg::Vec2f& center) + { + osg::ref_ptr group = new osg::Group; + osg::Vec3f worldCenter = osg::Vec3f(center.x(), center.y(), 0)*ESM::Land::REAL_SIZE; + for (auto& pair : instances) + { + const osg::Node* temp = mSceneManager->getTemplate(pair.first); + osg::ref_ptr node = static_cast(temp->clone(osg::CopyOp::DEEP_COPY_ALL&(~osg::CopyOp::DEEP_COPY_TEXTURES))); + + // Keep link to original mesh to keep it in cache + group->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(temp)); + + InstancingVisitor visitor(pair.second, worldCenter); + node->accept(visitor); + group->addChild(node); + } + + group->getBound(); + group->setNodeMask(Mask_Groundcover); + mSceneManager->recreateShaders(group, "groundcover", false, true); + + return group; + } + + unsigned int Groundcover::getNodeMask() + { + return Mask_Groundcover; + } + + void Groundcover::reportStats(unsigned int frameNumber, osg::Stats *stats) const + { + stats->setAttribute(frameNumber, "Groundcover Chunk", mCache->getCacheSize()); + } +} diff --git a/apps/openmw/mwrender/groundcover.hpp b/apps/openmw/mwrender/groundcover.hpp new file mode 100644 index 000000000..cd80978be --- /dev/null +++ b/apps/openmw/mwrender/groundcover.hpp @@ -0,0 +1,69 @@ +#ifndef OPENMW_MWRENDER_GROUNDCOVER_H +#define OPENMW_MWRENDER_GROUNDCOVER_H + +#include +#include +#include +#include + +namespace MWRender +{ + class GroundcoverUpdater : public SceneUtil::StateSetUpdater + { + public: + GroundcoverUpdater() + : mWindSpeed(0.f) + , mPlayerPos(osg::Vec3f()) + { + } + + void setWindSpeed(float windSpeed); + void setPlayerPos(osg::Vec3f playerPos); + + protected: + void setDefaults(osg::StateSet *stateset) override; + void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override; + + private: + float mWindSpeed; + osg::Vec3f mPlayerPos; + }; + + typedef std::tuple ChunkId; // Center, Size, ActiveGrid + class Groundcover : public Resource::GenericResourceManager, public Terrain::QuadTreeWorld::ChunkManager + { + public: + Groundcover(Resource::SceneManager* sceneManager, float density); + ~Groundcover() = default; + + 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; + + unsigned int getNodeMask() override; + + void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; + + struct GroundcoverEntry + { + ESM::Position mPos; + float mScale; + std::string mModel; + + GroundcoverEntry(const ESM::CellRef& ref, const std::string& model) + { + mPos = ref.mPos; + mScale = ref.mScale; + mModel = model; + } + }; + + private: + Resource::SceneManager* mSceneManager; + float mDensity; + + typedef std::map> InstanceMap; + osg::ref_ptr createChunk(InstanceMap& instances, const osg::Vec2f& center); + void collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center); + }; +} + +#endif diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index d8e856e76..478fde0f8 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -398,6 +398,7 @@ namespace MWRender int type = store.findStatic(ref.mRefID); if (!typeFilter(type,size>=2)) continue; if (deleted) { refs.erase(ref.mRefNum); continue; } + if (store.isGroundcover(ref.mRefID)) continue; refs[ref.mRefNum] = ref; } } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6ce431d2e..c755f46f8 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -68,10 +69,10 @@ #include "fogmanager.hpp" #include "objectpaging.hpp" #include "screenshotmanager.hpp" +#include "groundcover.hpp" namespace MWRender { - class StateUpdater : public SceneUtil::StateSetUpdater { public: @@ -243,6 +244,10 @@ namespace MWRender globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0"; globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"; + float groundcoverDistance = (Constants::CellSizeInUnits * Settings::Manager::getInt("distance", "Groundcover") - 1024) * 0.93; + globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * Settings::Manager::getFloat("fade start", "Groundcover")); + globalDefines["groundcoverFadeEnd"] = std::to_string(groundcoverDistance); + // It is unnecessary to stop/start the viewer as no frames are being rendered yet. mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines); @@ -269,7 +274,8 @@ namespace MWRender const bool useTerrainNormalMaps = Settings::Manager::getBool("auto use terrain normal maps", "Shaders"); const bool useTerrainSpecularMaps = Settings::Manager::getBool("auto use terrain specular maps", "Shaders"); - mTerrainStorage = new TerrainStorage(mResourceSystem, normalMapPattern, heightMapPattern, useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps); + mTerrainStorage.reset(new TerrainStorage(mResourceSystem, normalMapPattern, heightMapPattern, useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps)); + const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain"); if (Settings::Manager::getBool("distant terrain", "Terrain")) { @@ -277,12 +283,11 @@ namespace MWRender int compMapPower = Settings::Manager::getInt("composite map level", "Terrain"); compMapPower = std::max(-3, compMapPower); float compMapLevel = pow(2, compMapPower); - const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain"); const int vertexLodMod = Settings::Manager::getInt("vertex lod mod", "Terrain"); float maxCompGeometrySize = Settings::Manager::getFloat("max composite geometry size", "Terrain"); maxCompGeometrySize = std::max(maxCompGeometrySize, 1.f); mTerrain.reset(new Terrain::QuadTreeWorld( - sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug, + sceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug, compMapResolution, compMapLevel, lodFactor, vertexLodMod, maxCompGeometrySize)); if (Settings::Manager::getBool("object paging", "Terrain")) { @@ -292,11 +297,43 @@ namespace MWRender } } else - mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug)); + mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug)); mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); mTerrain->setWorkQueue(mWorkQueue.get()); + if (Settings::Manager::getBool("enabled", "Groundcover")) + { + osg::ref_ptr groundcoverRoot = new osg::Group; + groundcoverRoot->setNodeMask(Mask_Groundcover); + groundcoverRoot->setName("Groundcover Root"); + sceneRoot->addChild(groundcoverRoot); + + // Force a unified alpha handling instead of data from meshes + osg::ref_ptr alpha = new osg::AlphaFunc(osg::AlphaFunc::GEQUAL, 128.f/255.f); + groundcoverRoot->getOrCreateStateSet()->setAttributeAndModes(alpha.get(), osg::StateAttribute::ON); + + mGroundcoverUpdater = new GroundcoverUpdater; + groundcoverRoot->addUpdateCallback(mGroundcoverUpdater); + + float chunkSize = Settings::Manager::getFloat("min chunk size", "Groundcover"); + if (chunkSize >= 1.0f) + chunkSize = 1.0f; + else if (chunkSize >= 0.5f) + chunkSize = 0.5f; + else if (chunkSize >= 0.25f) + chunkSize = 0.25f; + else if (chunkSize != 0.125f) + chunkSize = 0.125f; + + float density = Settings::Manager::getFloat("density", "Groundcover"); + density = std::clamp(density, 0.f, 1.f); + + mGroundcoverWorld.reset(new Terrain::QuadTreeWorld(groundcoverRoot, mTerrainStorage.get(), Mask_Groundcover, lodFactor, chunkSize)); + mGroundcover.reset(new Groundcover(mResourceSystem->getSceneManager(), density)); + static_cast(mGroundcoverWorld.get())->addChunkManager(mGroundcover.get()); + mResourceSystem->addResourceManager(mGroundcover.get()); + } // water goes after terrain for correct waterculling order mWater.reset(new Water(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath)); @@ -508,7 +545,11 @@ namespace MWRender mWater->changeCell(store); if (store->getCell()->isExterior()) + { mTerrain->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + if (mGroundcoverWorld) + mGroundcoverWorld->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + } } void RenderingManager::removeCell(const MWWorld::CellStore *store) { @@ -517,7 +558,11 @@ namespace MWRender mObjects->removeCell(store); if (store->getCell()->isExterior()) + { mTerrain->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + if (mGroundcoverWorld) + mGroundcoverWorld->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + } mWater->removeCell(store); } @@ -527,6 +572,8 @@ namespace MWRender if (!enable) mWater->setCullCallback(nullptr); mTerrain->enable(enable); + if (mGroundcoverWorld) + mGroundcoverWorld->enable(enable); } void RenderingManager::setSkyEnabled(bool enabled) @@ -612,6 +659,16 @@ namespace MWRender mEffectManager->update(dt); mSky->update(dt); mWater->update(dt); + + if (mGroundcoverUpdater) + { + const MWWorld::Ptr& player = mPlayerAnimation->getPtr(); + osg::Vec3f playerPos(player.getRefData().getPosition().asVec3()); + + float windSpeed = mSky->getBaseWindSpeed(); + mGroundcoverUpdater->setWindSpeed(windSpeed); + mGroundcoverUpdater->setPlayerPos(playerPos); + } } updateNavMesh(); @@ -805,7 +862,7 @@ namespace MWRender mIntersectionVisitor->setIntersector(intersector); int mask = ~0; - mask &= ~(Mask_RenderToTexture|Mask_Sky|Mask_Debug|Mask_Effect|Mask_Water|Mask_SimpleWater); + mask &= ~(Mask_RenderToTexture|Mask_Sky|Mask_Debug|Mask_Effect|Mask_Water|Mask_SimpleWater|Mask_Groundcover); if (ignorePlayer) mask &= ~(Mask_Player); if (ignoreActors) @@ -964,6 +1021,12 @@ namespace MWRender fov = std::min(mFieldOfView, 140.f); float distanceMult = std::cos(osg::DegreesToRadians(fov)/2.f); mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f/distanceMult : 1.f)); + + if (mGroundcoverWorld) + { + int groundcoverDistance = Constants::CellSizeInUnits * Settings::Manager::getInt("distance", "Groundcover"); + mGroundcoverWorld->setViewDistance(groundcoverDistance * (distanceMult ? 1.f/distanceMult : 1.f)); + } } void RenderingManager::updateTextureFiltering() @@ -1158,6 +1221,8 @@ namespace MWRender void RenderingManager::setActiveGrid(const osg::Vec4i &grid) { mTerrain->setActiveGrid(grid); + if (mGroundcoverWorld) + mGroundcoverWorld->setActiveGrid(grid); } bool RenderingManager::pagingEnableObject(int type, const MWWorld::ConstPtr& ptr, bool enabled) { diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 39d1a0194..a7afa2fa0 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -70,7 +70,7 @@ namespace DetourNavigator namespace MWRender { - + class GroundcoverUpdater; class StateUpdater; class EffectManager; @@ -88,6 +88,7 @@ namespace MWRender class ActorsPaths; class RecastMesh; class ObjectPaging; + class Groundcover; class RenderingManager : public MWRender::RenderingInterface { @@ -261,6 +262,8 @@ namespace MWRender osg::ref_ptr mSceneRoot; Resource::ResourceSystem* mResourceSystem; + osg::ref_ptr mGroundcoverUpdater; + osg::ref_ptr mWorkQueue; osg::ref_ptr mUnrefQueue; @@ -275,8 +278,10 @@ namespace MWRender std::unique_ptr mObjects; std::unique_ptr mWater; std::unique_ptr mTerrain; - TerrainStorage* mTerrainStorage; + std::unique_ptr mGroundcoverWorld; + std::unique_ptr mTerrainStorage; std::unique_ptr mObjectPaging; + std::unique_ptr mGroundcover; std::unique_ptr mSky; std::unique_ptr mFog; std::unique_ptr mScreenshotManager; diff --git a/apps/openmw/mwrender/vismask.hpp b/apps/openmw/mwrender/vismask.hpp index f9f9dc74c..bc3d3f192 100644 --- a/apps/openmw/mwrender/vismask.hpp +++ b/apps/openmw/mwrender/vismask.hpp @@ -53,7 +53,9 @@ namespace MWRender Mask_PreCompile = (1<<18), // Set on a camera's cull mask to enable the LightManager - Mask_Lighting = (1<<19) + Mask_Lighting = (1<<19), + + Mask_Groundcover = (1<<20), }; } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index e786ce937..1cc5a3cb7 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -244,7 +244,7 @@ public: setCullCallback(new InheritViewPointCallback); setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); - setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Static|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); + setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Static|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting|Mask_Groundcover); setNodeMask(Mask_RenderToTexture); setViewport(0, 0, rttSize, rttSize); @@ -372,12 +372,13 @@ public: void setInterior(bool isInterior) { int reflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); - reflectionDetail = std::min(4, std::max(isInterior ? 2 : 0, reflectionDetail)); + reflectionDetail = std::min(5, std::max(isInterior ? 2 : 0, reflectionDetail)); unsigned int extraMask = 0; if(reflectionDetail >= 1) extraMask |= Mask_Terrain; if(reflectionDetail >= 2) extraMask |= Mask_Static; if(reflectionDetail >= 3) extraMask |= Mask_Effect|Mask_ParticleSystem|Mask_Object; if(reflectionDetail >= 4) extraMask |= Mask_Player|Mask_Actor; + if(reflectionDetail >= 5) extraMask |= Mask_Groundcover; setCullMask(Mask_Scene|Mask_Sky|Mask_Lighting|extraMask); } diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 31af5b24b..937491f62 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -36,6 +36,13 @@ namespace MWWorld virtual bool operator()(const MWWorld::Ptr& ptr) { + if (ptr.getTypeName()==typeid (ESM::Static).name()) + { + const MWWorld::LiveCellRef *ref = ptr.get(); + if (ref->mBase->mIsGroundcover) + return true; + } + ptr.getClass().getModelsToPreload(ptr, mOut); return true; diff --git a/apps/openmw/mwworld/cellreflist.hpp b/apps/openmw/mwworld/cellreflist.hpp index 30be4a661..69161c840 100644 --- a/apps/openmw/mwworld/cellreflist.hpp +++ b/apps/openmw/mwworld/cellreflist.hpp @@ -24,6 +24,8 @@ namespace MWWorld /// all methods are known. void load (ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore); + inline bool ignoreInstance (const X* ptr); + LiveRef &insert (const LiveRef &item) { mList.push_back(item); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index b48fe74a6..d8e2eb65f 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -169,6 +169,17 @@ namespace namespace MWWorld { + template + bool CellRefList::ignoreInstance (const X* ptr) + { + return false; + } + + template <> + bool CellRefList::ignoreInstance (const ESM::Static* ptr) + { + return ptr->mIsGroundcover; + } template void CellRefList::load(ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore) @@ -177,6 +188,8 @@ namespace MWWorld if (const X *ptr = store.search (ref.mRefID)) { + if (ignoreInstance(ptr)) return; + typename std::list::iterator iter = std::find(mList.begin(), mList.end(), ref.mRefNum); diff --git a/apps/openmw/mwworld/contentloader.hpp b/apps/openmw/mwworld/contentloader.hpp index b529ae9db..b559df083 100644 --- a/apps/openmw/mwworld/contentloader.hpp +++ b/apps/openmw/mwworld/contentloader.hpp @@ -21,7 +21,7 @@ struct ContentLoader { } - virtual void load(const boost::filesystem::path& filepath, int& index) + virtual void load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) { Log(Debug::Info) << "Loading content file " << filepath.string(); mListener.setLabel(MyGUI::TextIterator::toTagsString(filepath.string())); diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index b12d646e7..c96618215 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -15,17 +15,17 @@ EsmLoader::EsmLoader(MWWorld::ESMStore& store, std::vector& read { } -void EsmLoader::load(const boost::filesystem::path& filepath, int& index) +void EsmLoader::load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) { - ContentLoader::load(filepath.filename(), index); + ContentLoader::load(filepath.filename(), index, isGroundcover); - ESM::ESMReader lEsm; - lEsm.setEncoder(mEncoder); - lEsm.setIndex(index); - lEsm.setGlobalReaderList(&mEsm); - lEsm.open(filepath.string()); - mEsm[index] = lEsm; - mStore.load(mEsm[index], &mListener); + ESM::ESMReader lEsm; + lEsm.setEncoder(mEncoder); + lEsm.setIndex(index); + lEsm.setGlobalReaderList(&mEsm); + lEsm.open(filepath.string(), isGroundcover); + mEsm[index] = lEsm; + mStore.load(mEsm[index], &mListener); } } /* namespace MWWorld */ diff --git a/apps/openmw/mwworld/esmloader.hpp b/apps/openmw/mwworld/esmloader.hpp index 506105beb..cc4c15a15 100644 --- a/apps/openmw/mwworld/esmloader.hpp +++ b/apps/openmw/mwworld/esmloader.hpp @@ -25,7 +25,7 @@ struct EsmLoader : public ContentLoader EsmLoader(MWWorld::ESMStore& store, std::vector& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener& listener); - void load(const boost::filesystem::path& filepath, int& index) override; + void load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) override; private: std::vector& mEsm; diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 90bc80b48..2731d7eb1 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -190,6 +190,15 @@ void ESMStore::setUp(bool validateRecords) { validate(); countRecords(); + + if (mGroundcovers.empty()) + { + for (const ESM::Static& record : mStatics) + { + if (record.mIsGroundcover) + mGroundcovers[record.mId] = record.mModel; + } + } } } diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index d69c56d8c..18bb95580 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -75,6 +75,7 @@ namespace MWWorld // maps the id name to the record type. std::map mIds; std::map mStaticIds; + std::map mGroundcovers; std::map mRefCount; @@ -121,6 +122,22 @@ namespace MWWorld return it->second; } + bool isGroundcover(const std::string &id, std::string &model) const + { + std::map::const_iterator it = mGroundcovers.find(id); + if (it == mGroundcovers.end()) { + return false; + } + model = it->second; + return true; + } + + bool isGroundcover(const std::string &id) const + { + std::map::const_iterator it = mGroundcovers.find(id); + return (it != mGroundcovers.end()); + } + ESMStore() : mDynamicCount(0) { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index af9a5e0bb..93d1a799c 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -103,12 +103,12 @@ namespace MWWorld return mLoaders.insert(std::make_pair(extension, loader)).second; } - void load(const boost::filesystem::path& filepath, int& index) override + void load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) override { LoadersContainer::iterator it(mLoaders.find(Misc::StringUtils::lowerCase(filepath.extension().string()))); if (it != mLoaders.end()) { - it->second->load(filepath, index); + it->second->load(filepath, index, isGroundcover); } else { @@ -140,6 +140,7 @@ namespace MWWorld Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const Files::Collections& fileCollections, const std::vector& contentFiles, + const std::vector& groundcoverFiles, ToUTF8::Utf8Encoder* encoder, int activationDistanceOverride, const std::string& startCell, const std::string& startupScript, const std::string& resourcePath, const std::string& userDataPath) @@ -152,7 +153,7 @@ namespace MWWorld mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0), mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f) { - mEsm.resize(contentFiles.size()); + mEsm.resize(contentFiles.size() + groundcoverFiles.size()); Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); listener->loadingOn(); @@ -165,7 +166,7 @@ namespace MWWorld gameContentLoader.addLoader(".omwaddon", &esmLoader); gameContentLoader.addLoader(".project", &esmLoader); - loadContentFiles(fileCollections, contentFiles, gameContentLoader); + loadContentFiles(fileCollections, contentFiles, groundcoverFiles, gameContentLoader); listener->loadingOff(); @@ -2941,7 +2942,7 @@ namespace MWWorld } void World::loadContentFiles(const Files::Collections& fileCollections, - const std::vector& content, ContentLoader& contentLoader) + const std::vector& content, const std::vector& groundcover, ContentLoader& contentLoader) { int idx = 0; for (const std::string &file : content) @@ -2950,7 +2951,7 @@ namespace MWWorld const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); if (col.doesExist(file)) { - contentLoader.load(col.getPath(file), idx); + contentLoader.load(col.getPath(file), idx, false); } else { @@ -2959,6 +2960,22 @@ namespace MWWorld } idx++; } + + for (const std::string &file : groundcover) + { + boost::filesystem::path filename(file); + const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); + if (col.doesExist(file)) + { + contentLoader.load(col.getPath(file), idx, true); + } + else + { + std::string message = "Failed loading " + file + ": the groundcover file does not exist"; + throw std::runtime_error(message); + } + idx++; + } } bool World::startSpellCast(const Ptr &actor) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 79c8a4980..29d29a160 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -177,7 +177,7 @@ namespace MWWorld * @param contentLoader - */ void loadContentFiles(const Files::Collections& fileCollections, - const std::vector& content, ContentLoader& contentLoader); + const std::vector& content, const std::vector& groundcover, ContentLoader& contentLoader); float feetToGameUnits(float feet); float getActivationDistancePlusTelekinesis(); @@ -196,6 +196,7 @@ namespace MWWorld Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const Files::Collections& fileCollections, const std::vector& contentFiles, + const std::vector& groundcoverFiles, ToUTF8::Utf8Encoder* encoder, int activationDistanceOverride, const std::string& startCell, const std::string& startupScript, const std::string& resourcePath, const std::string& userDataPath); diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index d7fe7da94..8717a6839 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -100,6 +100,7 @@ bool Config::GameSettings::readFile(QTextStream &stream, QMultiMap node, const std::string& shaderPrefix, bool translucentFramebuffer) + void SceneManager::recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix, bool translucentFramebuffer, bool forceShadersForNode) { osg::ref_ptr shaderVisitor(createShaderVisitor(shaderPrefix, translucentFramebuffer)); shaderVisitor->setAllowedToModifyStateSets(false); + if (forceShadersForNode) + shaderVisitor->setForceShaders(true); node->accept(*shaderVisitor); } @@ -512,7 +514,7 @@ namespace Resource SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor(mMinFilter, mMagFilter, mMaxAnisotropy); loaded->accept(setFilterSettingsControllerVisitor); - osg::ref_ptr shaderVisitor (createShaderVisitor()); + osg::ref_ptr shaderVisitor (createShaderVisitor("objects")); loaded->accept(*shaderVisitor); // share state diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index a815a324f..9da6bc500 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -76,7 +76,7 @@ namespace Resource Shader::ShaderManager& getShaderManager(); /// Re-create shaders for this node, need to call this if texture stages or vertex color mode have changed. - void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false); + void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false, bool forceShadersForNode = false); /// @see ShaderVisitor::setForceShaders void setForceShaders(bool force); diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 942bd92d8..05f97b3ed 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -281,6 +281,7 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) "FrameNumber", "", "Compiling", + "UnrefQueue", "WorkQueue", "WorkThread", "", @@ -294,14 +295,13 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) "Nif", "Keyframe", "", + "Groundcover Chunk", "Object Chunk", "Terrain Chunk", "Terrain Texture", "Land", "Composite", "", - "UnrefQueue", - "", "NavMesh UpdateJobs", "NavMesh CacheSize", "NavMesh UsedTiles", diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 788a8720b..4f887e659 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -342,6 +342,8 @@ namespace Shader osg::ref_ptr program (new osg::Program); program->addShader(vertexShader); program->addShader(fragmentShader); + program->addBindAttribLocation("aOffset", 6); + program->addBindAttribLocation("aRotation", 7); found = mPrograms.insert(std::make_pair(std::make_pair(vertexShader, fragmentShader), program)).first; } return found->second; diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 50724628d..a744471de 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -40,7 +40,7 @@ ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, T mMultiPassRoot->setAttributeAndModes(material, osg::StateAttribute::ON); } -osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f ¢er, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint, bool compile) +osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) { ChunkId id = std::make_tuple(center, lod, lodFlags); osg::ref_ptr obj = mCache->getRefFromObjectCache(id); diff --git a/components/terrain/chunkmanager.hpp b/components/terrain/chunkmanager.hpp index 118df698f..9b7dbf3ee 100644 --- a/components/terrain/chunkmanager.hpp +++ b/components/terrain/chunkmanager.hpp @@ -35,7 +35,7 @@ namespace Terrain public: ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager, CompositeMapRenderer* renderer); - osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint, bool compile) override; + 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; void setCompositeMapSize(unsigned int size) { mCompositeMapSize = size; } void setCompositeMapLevel(float level) { mCompositeMapLevel = level; } diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 57c09000c..7f184c70e 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -90,8 +90,6 @@ private: osg::Vec4i mActiveGrid; }; -const float MIN_SIZE = 1/8.f; - class RootNode : public QuadTreeNode { public: @@ -250,6 +248,7 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour , mLodFactor(lodFactor) , mVertexLodMod(vertexLodMod) , mViewDistance(std::numeric_limits::max()) + , mMinSize(1/8.f) { mChunkManager->setCompositeMapSize(compMapResolution); mChunkManager->setCompositeMapLevel(compMapLevel); @@ -257,6 +256,17 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour mChunkManagers.push_back(mChunkManager.get()); } +QuadTreeWorld::QuadTreeWorld(osg::Group *parent, Storage *storage, int nodeMask, float lodFactor, float chunkSize) + : TerrainGrid(parent, storage, nodeMask) + , mViewDataMap(new ViewDataMap) + , mQuadTreeBuilt(false) + , mLodFactor(lodFactor) + , mVertexLodMod(0) + , mViewDistance(std::numeric_limits::max()) + , mMinSize(chunkSize) +{ +} + QuadTreeWorld::~QuadTreeWorld() { } @@ -425,7 +435,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) if (needsUpdate) { vd->reset(); - DefaultLodCallback lodCallback(mLodFactor, MIN_SIZE, mViewDistance, mActiveGrid); + DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, mActiveGrid); mRootNode->traverseNodes(vd, nv.getViewPoint(), &lodCallback); } @@ -438,7 +448,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) entry.mRenderingNode->accept(nv); } - if (isCullVisitor) + if (mHeightCullCallback && isCullVisitor) updateWaterCullingView(mHeightCullCallback, vd, static_cast(&nv), mStorage->getCellWorldSize(), !isGridEmpty()); vd->markUnchanged(); @@ -457,7 +467,7 @@ void QuadTreeWorld::ensureQuadTreeBuilt() if (mQuadTreeBuilt) return; - QuadTreeBuilder builder(mStorage, MIN_SIZE); + QuadTreeBuilder builder(mStorage, mMinSize); builder.build(); mRootNode = builder.getRootNode(); @@ -491,7 +501,7 @@ void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg:: ViewData* vd = static_cast(view); vd->setViewPoint(viewPoint); vd->setActiveGrid(grid); - DefaultLodCallback lodCallback(mLodFactor, MIN_SIZE, mViewDistance, grid); + DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, grid); mRootNode->traverseNodes(vd, viewPoint, &lodCallback); if (!progressTotal) @@ -515,14 +525,15 @@ bool QuadTreeWorld::storeView(const View* view, double referenceTime) void QuadTreeWorld::reportStats(unsigned int frameNumber, osg::Stats *stats) { - stats->setAttribute(frameNumber, "Composite", mCompositeMapRenderer->getCompileSetSize()); + if (mCompositeMapRenderer) + stats->setAttribute(frameNumber, "Composite", mCompositeMapRenderer->getCompileSetSize()); } 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)) + if (mChunkManager && !mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy)) TerrainGrid::loadCell(x,y); else World::loadCell(x,y); @@ -532,7 +543,7 @@ 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)) + if (mChunkManager && !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 4c05efe64..aba2dccf3 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -22,6 +22,8 @@ namespace Terrain 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); + QuadTreeWorld(osg::Group *parent, Storage *storage, int nodeMask, float lodFactor, float chunkSize); + ~QuadTreeWorld(); void accept(osg::NodeVisitor& nv); @@ -47,7 +49,7 @@ namespace Terrain { public: virtual ~ChunkManager(){} - virtual osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint, bool compile) = 0; + virtual osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) = 0; virtual unsigned int getNodeMask() { return 0; } }; void addChunkManager(ChunkManager*); @@ -66,6 +68,7 @@ namespace Terrain float mLodFactor; int mVertexLodMod; float mViewDistance; + float mMinSize; }; } diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 679597971..cf8debc69 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -26,6 +26,12 @@ TerrainGrid::TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource:: { } +TerrainGrid::TerrainGrid(osg::Group* parent, Storage* storage, int nodeMask) + : Terrain::World(parent, storage, nodeMask) + , mNumSplits(4) +{ +} + TerrainGrid::~TerrainGrid() { while (!mGrid.empty()) @@ -107,6 +113,8 @@ void TerrainGrid::unloadCell(int x, int y) void TerrainGrid::updateWaterCulling() { + if (!mHeightCullCallback) return; + osg::ComputeBoundsVisitor computeBoundsVisitor; mTerrainRoot->accept(computeBoundsVisitor); float lowZ = computeBoundsVisitor.getBoundingBox()._min.z(); diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index f8b0fb259..dc9203466 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -15,6 +15,7 @@ namespace Terrain { public: TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask=~0, int borderMask=0); + TerrainGrid(osg::Group* parent, Storage* storage, int nodeMask=~0); ~TerrainGrid(); void cacheCell(View* view, int x, int y) override; diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 5b4807b38..15ec72973 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -49,17 +49,38 @@ World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSyst mResourceSystem->addResourceManager(mTextureManager.get()); } +World::World(osg::Group* parent, Storage* storage, int nodeMask) + : mStorage(storage) + , mParent(parent) + , mCompositeMapCamera(nullptr) + , mCompositeMapRenderer(nullptr) + , mResourceSystem(nullptr) + , mTextureManager(nullptr) + , mChunkManager(nullptr) + , mCellBorder(nullptr) + , mBorderVisible(false) + , mHeightCullCallback(nullptr) +{ + mTerrainRoot = new osg::Group; + mTerrainRoot->setNodeMask(nodeMask); + + mParent->addChild(mTerrainRoot); +} + World::~World() { - mResourceSystem->removeResourceManager(mChunkManager.get()); - mResourceSystem->removeResourceManager(mTextureManager.get()); + if (mResourceSystem && mChunkManager) + mResourceSystem->removeResourceManager(mChunkManager.get()); + if (mResourceSystem && mTextureManager) + mResourceSystem->removeResourceManager(mTextureManager.get()); mParent->removeChild(mTerrainRoot); - mCompositeMapCamera->removeChild(mCompositeMapRenderer); - mCompositeMapCamera->getParent(0)->removeChild(mCompositeMapCamera); - - delete mStorage; + if (mCompositeMapCamera && mCompositeMapRenderer) + { + mCompositeMapCamera->removeChild(mCompositeMapRenderer); + mCompositeMapCamera->getParent(0)->removeChild(mCompositeMapCamera); + } } void World::setWorkQueue(SceneUtil::WorkQueue* workQueue) @@ -108,16 +129,20 @@ float World::getHeightAt(const osg::Vec3f &worldPos) void World::updateTextureFiltering() { - mTextureManager->updateTextureFiltering(); + if (mTextureManager) + mTextureManager->updateTextureFiltering(); } void World::clearAssociatedCaches() { - mChunkManager->clearCache(); + if (mChunkManager) + mChunkManager->clearCache(); } osg::Callback* World::getHeightCullCallback(float highz, unsigned int mask) { + if (!mHeightCullCallback) return nullptr; + mHeightCullCallback->setHighZ(highz); mHeightCullCallback->setCullMask(mask); return mHeightCullCallback; diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index d94125100..a4be57e8e 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -106,6 +106,7 @@ namespace Terrain /// @param nodeMask mask for the terrain root /// @param preCompileMask mask for pre compiling textures World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask, int borderMask); + World(osg::Group* parent, Storage* storage, int nodeMask); virtual ~World(); /// Set a WorkQueue to delete objects in the background thread. diff --git a/docs/source/reference/modding/extended.rst b/docs/source/reference/modding/extended.rst index 9e8db49fd..98b3e7f00 100644 --- a/docs/source/reference/modding/extended.rst +++ b/docs/source/reference/modding/extended.rst @@ -223,10 +223,10 @@ For example, to attach a custom weapon bone, you'll need to follow this NIF reco :: -NiNode "root" - NiNode "Bip01 L Hand" - NiNode "Weapon Bone Left" - NiStringExtraData "BONE" + NiNode "root" + NiNode "Bip01 L Hand" + NiNode "Weapon Bone Left" + NiStringExtraData "BONE" OpenMW will detect ``Weapon Bone Left`` node and attach it to ``Bip01 L Hand`` bone of the target skeleton. @@ -276,6 +276,54 @@ Also it is possible to add a "Bip01 Arrow" bone to actor skeletons. In this case Such approach allows to implement better shooting animations (for example, beast races have tail, so quivers should be attached under different angle and default arrow fetching animation does not look good). +Groundcover support +------------------- + +Groundcover objects is a special kind of objects (e.g. grass), which can be used to improve visual fidelity. +They use these assumptions: + +1. Each object is independent, so part of objects can be removed from scene without causing graphical artifacts. + +2. Groundover should not have collisions. + +3. They are not important for some parts of game scene (e.g. local map). + +4. They can not be moved or disabled on the fly. + +5. They can not be interacted with. + +As result, such objects can be treated in the separate way: + +1. It is possible to tweak groundcover objects density. + +2. It is possible to safely merge such objects even near player. + +3. Such objects can be animated (to simulate wind, for example). + +4. Some parts of processing can be skipped. + +For example, we do not need to have collision or animation objects for groundcover, +do not need to render groundcover on the map, do not need to render it for the whole visible area (which can be very large with Distant Terrain). It allows to increase performance a lot. + +General advices to create assets for this feature: +1. Alpha properties from Nif files are not used, a unified alpha settings are used (alpha testing, "greater of equal" function, 128/255 threshold). +2. Use a single NiTriShape in groundocver mesh, or at least use same properties (texture, alpha, material, etc), so OpenMW can merge them on the fly. Otherwise animations may not work properly. +3. Smooth fading does not work for meshes, which have textures without alpha (e.g. rock). + +Groundcover mods can be registered in the openmw.cfg via "groundcover" entries instead of "content" ones: + +:: + + groundcover=my_grass_mod.esp + +Every static from such mod is treated as a groundcover object. +Also groundcover detection should be enabled via settings.cfg: + +:: + + [Groundcover] + enabled = true + .. _`Graphic Herbalism`: https://www.nexusmods.com/morrowind/mods/46599 .. _`OpenMW Containers Animated`: https://www.nexusmods.com/morrowind/mods/46232 .. _`Glow in the Dahrk`: https://www.nexusmods.com/morrowind/mods/45886 diff --git a/docs/source/reference/modding/settings/groundcover.rst b/docs/source/reference/modding/settings/groundcover.rst new file mode 100644 index 000000000..9b00d85a1 --- /dev/null +++ b/docs/source/reference/modding/settings/groundcover.rst @@ -0,0 +1,68 @@ +Groundcover Settings +#################### + +enabled +------- + +:Type: boolean +:Range: True/False +:Default: False + +Allows the engine to use groundcover. +Groundcover objects are static objects which come from ESP files, registered via +"groundcover" entries from openmw.cfg rather than "content" ones. +We assume that groundcover objects have no collisions, can not be moved or interacted with, +so we can merge them to pages and animate them indifferently from distance from player. + +This setting can only be configured by editing the settings configuration file. + +fade start +---------- + +:Type: floating point +:Range: 0.0 to 1.0 +:Default: 0.85 + +Determines on which distance from player groundcover fading starts. +Does not work for meshes which textures do not have transparency (e.g. rocks). + +This setting can only be configured by editing the settings configuration file. + +density +------- + +:Type: floating point +:Range: 0.0 (0%) to 1.0 (100%) +:Default: 1.0 + +Determines how many groundcover instances from content files +are used in the game. Can affect performance a lot. + +This setting can only be configured by editing the settings configuration file. + +distance +-------- + +:Type: integer +:Range: > 0 +:Default: 1 + +Determines on which distance in cells grass pages are rendered. +Default 1 value means 3x3 cells area (active grid). +May affect performance a lot. + +This setting can only be configured by editing the settings configuration file. + +min chunk size +-------------- + +:Type: floating point +:Range: 0.125, 0.25, 0.5, 1.0 +:Default: 0.5 + +Determines a minimum size of groundcover chunks in cells. For example, with 0.5 value +chunks near player will have size 4096x4096 game units. Larger chunks reduce CPU usage +(Draw and Cull bars), but can increase GPU usage (GPU bar) since culling becomes less efficient. +Smaller values do an opposite. + +This setting can only be configured by editing the settings configuration file. diff --git a/docs/source/reference/modding/settings/index.rst b/docs/source/reference/modding/settings/index.rst index 586a99dbb..220ee88c4 100644 --- a/docs/source/reference/modding/settings/index.rst +++ b/docs/source/reference/modding/settings/index.rst @@ -42,6 +42,7 @@ The ranges included with each setting are the physically possible ranges, not re camera cells fog + groundcover map GUI HUD diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index e23cc3d54..ed43b19a2 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -26,6 +26,7 @@ Has no effect if the 'force shaders' option is false. Enabling per-pixel lighting results in visual differences to the original MW engine. It is not recommended to enable this option when using vanilla Morrowind files, because certain lights in Morrowind rely on vertex lighting to look as intended. +Note that groundcover shaders ignore this setting. clamp lighting -------------- diff --git a/docs/source/reference/modding/settings/water.rst b/docs/source/reference/modding/settings/water.rst index b79daacb7..b3a6f8c1f 100644 --- a/docs/source/reference/modding/settings/water.rst +++ b/docs/source/reference/modding/settings/water.rst @@ -62,7 +62,7 @@ reflection detail ----------------- :Type: integer -:Range: 0, 1, 2, 3, 4 +:Range: 0, 1, 2, 3, 4, 5 :Default: 2 Controls what kinds of things are rendered in water reflections. @@ -72,6 +72,7 @@ Controls what kinds of things are rendered in water reflections. 2: statics, activators, and doors are also reflected 3: items, containers, and particles are also reflected 4: actors are also reflected +5: groundcover objects are also reflected In interiors the lowest level is 2. This setting can be changed ingame with the "Reflection shader detail" dropdown under the Water tab of the Video panel in the Options menu. diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 14ab7c9de..b57d362ed 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -449,6 +449,7 @@ + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index d0793fc81..f6bfff7b1 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -950,6 +950,25 @@ lineofsight keep inactive cache = 0 defer aabb update = true [Models] + # Attempt to load any valid NIF file regardless of its version and track the progress. # Loading arbitrary meshes is not advised and may cause instability. load unsupported nif files = false + +[Groundcover] + +# enable separate groundcover handling +enabled = false + +# configure groundcover fade out threshold +fade start = 0.85 + +# A groundcover density (0.0 <= value <= 1.0) +# 1.0 means 100% density +density = 1.0 + +# A maximum distance in cells on which groundcover is rendered. +distance = 1 + +# A minimum size of groundcover chunk in cells (0.125, 0.25, 0.5, 1.0) +min chunk size = 0.5 diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 47670e7a0..a4e898e4b 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -7,6 +7,8 @@ set(SDIR ${CMAKE_CURRENT_SOURCE_DIR}) set(DDIRRELATIVE resources/shaders) set(SHADER_FILES + groundcover_vertex.glsl + groundcover_fragment.glsl water_vertex.glsl water_fragment.glsl water_nm.png diff --git a/files/shaders/groundcover_fragment.glsl b/files/shaders/groundcover_fragment.glsl new file mode 100644 index 000000000..9c680853a --- /dev/null +++ b/files/shaders/groundcover_fragment.glsl @@ -0,0 +1,93 @@ +#version 120 + +#define GROUNDCOVER + +#if @diffuseMap +uniform sampler2D diffuseMap; +varying vec2 diffuseMapUV; +#endif + +#if @normalMap +uniform sampler2D normalMap; +varying vec2 normalMapUV; +varying vec4 passTangent; +#endif + +// Other shaders respect forcePPL, but legacy groundcover mods were designed to work with vertex lighting. +// They may do not look as intended with per-pixel lighting, so ignore this setting for now. +#define PER_PIXEL_LIGHTING @normalMap + +varying float euclideanDepth; +varying float linearDepth; + +#if PER_PIXEL_LIGHTING +varying vec3 passViewPos; +varying vec3 passNormal; +#else +centroid varying vec3 passLighting; +centroid varying vec3 shadowDiffuseLighting; +#endif + +#include "shadows_fragment.glsl" +#include "lighting.glsl" + +float calc_coverage(float a, float alpha_ref, float falloff_rate) +{ + return clamp(falloff_rate * (a - alpha_ref) + alpha_ref, 0.0, 1.0); +} + +void main() +{ +#if @normalMap + vec4 normalTex = texture2D(normalMap, normalMapUV); + + vec3 normalizedNormal = normalize(passNormal); + vec3 normalizedTangent = normalize(passTangent.xyz); + vec3 binormal = cross(normalizedTangent, normalizedNormal) * passTangent.w; + mat3 tbnTranspose = mat3(normalizedTangent, binormal, normalizedNormal); + + vec3 viewNormal = gl_NormalMatrix * normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); +#endif + +#if (!@normalMap && @forcePPL && false) + vec3 viewNormal = gl_NormalMatrix * normalize(passNormal); +#endif + +#if @diffuseMap + gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); +#else + gl_FragData[0] = vec4(1.0); +#endif + + gl_FragData[0].a = calc_coverage(gl_FragData[0].a, 128.0/255.0, 4.0); + + float shadowing = unshadowedLightRatio(linearDepth); + if (euclideanDepth > @groundcoverFadeStart) + gl_FragData[0].a *= 1.0-smoothstep(@groundcoverFadeStart, @groundcoverFadeEnd, euclideanDepth); + + vec3 lighting; +#if !PER_PIXEL_LIGHTING + lighting = passLighting + shadowDiffuseLighting * shadowing; +#else + vec3 diffuseLight, ambientLight; + doLighting(passViewPos, normalize(viewNormal), shadowing, diffuseLight, ambientLight); + lighting = diffuseLight + ambientLight; +#endif + +#if @clamp + lighting = clamp(lighting, vec3(0.0), vec3(1.0)); +#else + lighting = max(lighting, 0.0); +#endif + + gl_FragData[0].xyz *= lighting; + +#if @radialFog + float fogValue = clamp((euclideanDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); +#else + float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); +#endif + gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); + + applyShadowDebugOverlay(); +} diff --git a/files/shaders/groundcover_vertex.glsl b/files/shaders/groundcover_vertex.glsl new file mode 100644 index 000000000..4f3303b03 --- /dev/null +++ b/files/shaders/groundcover_vertex.glsl @@ -0,0 +1,143 @@ +#version 120 + +#define GROUNDCOVER + +attribute vec4 aOffset; +attribute vec3 aRotation; + +#if @diffuseMap +varying vec2 diffuseMapUV; +#endif + +#if @normalMap +varying vec2 normalMapUV; +varying vec4 passTangent; +#endif + +// Other shaders respect forcePPL, but legacy groundcover mods were designed to work with vertex lighting. +// They may do not look as intended with per-pixel lighting, so ignore this setting for now. +#define PER_PIXEL_LIGHTING @normalMap + +varying float euclideanDepth; +varying float linearDepth; + +#if PER_PIXEL_LIGHTING +varying vec3 passViewPos; +varying vec3 passNormal; +#else +centroid varying vec3 passLighting; +centroid varying vec3 shadowDiffuseLighting; +#endif + +#include "shadows_vertex.glsl" +#include "lighting.glsl" + +uniform float osg_SimulationTime; +uniform mat4 osg_ViewMatrixInverse; +uniform mat4 osg_ViewMatrix; +uniform float windSpeed; +uniform vec3 playerPos; + +vec2 groundcoverDisplacement(in vec3 worldpos, float h) +{ + vec2 windDirection = vec2(1.0); + vec3 footPos = playerPos; + vec3 windVec = vec3(windSpeed * windDirection, 1.0); + + float v = length(windVec); + vec2 displace = vec2(2.0 * windVec + 0.1); + vec2 harmonics = vec2(0.0); + + harmonics += vec2((1.0 - 0.10*v) * sin(1.0*osg_SimulationTime + worldpos.xy / 1100.0)); + harmonics += vec2((1.0 - 0.04*v) * cos(2.0*osg_SimulationTime + worldpos.xy / 750.0)); + harmonics += vec2((1.0 + 0.14*v) * sin(3.0*osg_SimulationTime + worldpos.xy / 500.0)); + harmonics += vec2((1.0 + 0.28*v) * sin(5.0*osg_SimulationTime + worldpos.xy / 200.0)); + + // FIXME: stomping function does not work well in MGE: + // 1. It does not take in account Z coordinate, so it works even when player levitates. + // 2. It works more-or-less well only for grass meshes, but not for other types of plants. + // So disable this function for now, until we find a better one. + vec2 stomp = vec2(0.0); + //float d = length(worldpos.xy - footPos.xy); + //if (d < 150.0 && d > 0.0) + //{ + // stomp = (60.0 / d - 0.4) * (worldpos.xy - footPos.xy); + //} + + return clamp(0.02 * h, 0.0, 1.0) * (harmonics * displace + stomp); +} + +mat4 rotation(in vec3 angle) +{ + float sin_x = sin(angle.x); + float cos_x = cos(angle.x); + float sin_y = sin(angle.y); + float cos_y = cos(angle.y); + float sin_z = sin(angle.z); + float cos_z = cos(angle.z); + + return mat4( + cos_z*cos_y+sin_x*sin_y*sin_z, -sin_z*cos_x, cos_z*sin_y+sin_z*sin_x*cos_y, 0.0, + sin_z*cos_y+cos_z*sin_x*sin_y, cos_z*cos_x, sin_z*sin_y-cos_z*sin_x*cos_y, 0.0, + -sin_y*cos_x, sin_x, cos_x*cos_y, 0.0, + 0.0, 0.0, 0.0, 1.0); +} + +mat3 rotation3(in mat4 rot4) +{ + return mat3( + rot4[0].xyz, + rot4[1].xyz, + rot4[2].xyz); +} + +void main(void) +{ + vec3 position = aOffset.xyz; + float scale = aOffset.w; + + mat4 rotation = rotation(aRotation); + vec4 displacedVertex = rotation * scale * gl_Vertex; + + displacedVertex = vec4(displacedVertex.xyz + position, 1.0); + + vec4 worldPos = osg_ViewMatrixInverse * gl_ModelViewMatrix * displacedVertex; + worldPos.xy += groundcoverDisplacement(worldPos.xyz, gl_Vertex.z); + vec4 viewPos = osg_ViewMatrix * worldPos; + + gl_ClipVertex = viewPos; + euclideanDepth = length(viewPos.xyz); + + if (length(gl_ModelViewMatrix * vec4(position, 1.0)) > @groundcoverFadeEnd) + gl_Position = vec4(0.0, 0.0, 0.0, 1.0); + else + gl_Position = gl_ProjectionMatrix * viewPos; + + linearDepth = gl_Position.z; + +#if (!PER_PIXEL_LIGHTING || @shadows_enabled) + vec3 viewNormal = normalize((gl_NormalMatrix * rotation3(rotation) * gl_Normal).xyz); +#endif + +#if @diffuseMap + diffuseMapUV = (gl_TextureMatrix[@diffuseMapUV] * gl_MultiTexCoord@diffuseMapUV).xy; +#endif + +#if @normalMap + normalMapUV = (gl_TextureMatrix[@normalMapUV] * gl_MultiTexCoord@normalMapUV).xy; + passTangent = gl_MultiTexCoord7.xyzw * rotation; +#endif + +#if PER_PIXEL_LIGHTING + passViewPos = viewPos.xyz; + passNormal = rotation3(rotation) * gl_Normal.xyz; +#else + vec3 diffuseLight, ambientLight; + doLighting(viewPos.xyz, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting); + passLighting = diffuseLight + ambientLight; +#endif + +#if (@shadows_enabled) + setupShadowCoords(viewPos, viewNormal); +#endif +} diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 930f4de26..1b3ff288a 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -8,7 +8,20 @@ void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 vie float illumination = clamp(1.0 / (gl_LightSource[lightIndex].constantAttenuation + gl_LightSource[lightIndex].linearAttenuation * lightDistance + gl_LightSource[lightIndex].quadraticAttenuation * lightDistance * lightDistance), 0.0, 1.0); ambientOut = gl_LightSource[lightIndex].ambient.xyz * illumination; - diffuseOut = gl_LightSource[lightIndex].diffuse.xyz * max(dot(viewNormal, lightDir), 0.0) * illumination; + + float lambert = dot(viewNormal.xyz, lightDir) * illumination; +#ifndef GROUNDCOVER + lambert = max(lambert, 0.0); +#else + { + // might need to be < 0 depending on direction of viewPos + if (dot(viewPos, viewNormal.xyz) > 0) + lambert = -lambert; + if (lambert < 0) + lambert *= -0.3; + } +#endif + diffuseOut = gl_LightSource[lightIndex].diffuse.xyz * lambert; } #if PER_PIXEL_LIGHTING From 5124e81348ecdc915dd7248c8b088353f2037ea2 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 22 Nov 2020 10:17:20 +0400 Subject: [PATCH 085/117] Use linear interpolation instead of abrupt transitions for groundcover lighting --- files/shaders/lighting.glsl | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 1b3ff288a..5eae89029 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -14,11 +14,20 @@ void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 vie lambert = max(lambert, 0.0); #else { - // might need to be < 0 depending on direction of viewPos - if (dot(viewPos, viewNormal.xyz) > 0) - lambert = -lambert; - if (lambert < 0) - lambert *= -0.3; + float cosine = dot(normalize(viewPos), normalize(viewNormal.xyz)); + if (lambert >= 0.0) + cosine = -cosine; + + float mult = 1.0; + float divisor = 8.0; + + if (cosine < 0.0 && cosine >= -1.0/divisor) + mult = mix(1.0, 0.3, -cosine*divisor); + else if (cosine < -1.0/divisor) + mult = 0.3; + + lambert *= mult; + lambert = abs(lambert); } #endif diffuseOut = gl_LightSource[lightIndex].diffuse.xyz * lambert; From 36fc573375dcea73cc412d9452e6684f0de9f9ec Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 17 Dec 2020 13:55:20 +0400 Subject: [PATCH 086/117] Take in account Z direction for stomping --- files/shaders/groundcover_vertex.glsl | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/files/shaders/groundcover_vertex.glsl b/files/shaders/groundcover_vertex.glsl index 4f3303b03..407599eff 100644 --- a/files/shaders/groundcover_vertex.glsl +++ b/files/shaders/groundcover_vertex.glsl @@ -53,18 +53,14 @@ vec2 groundcoverDisplacement(in vec3 worldpos, float h) harmonics += vec2((1.0 + 0.14*v) * sin(3.0*osg_SimulationTime + worldpos.xy / 500.0)); harmonics += vec2((1.0 + 0.28*v) * sin(5.0*osg_SimulationTime + worldpos.xy / 200.0)); - // FIXME: stomping function does not work well in MGE: - // 1. It does not take in account Z coordinate, so it works even when player levitates. - // 2. It works more-or-less well only for grass meshes, but not for other types of plants. - // So disable this function for now, until we find a better one. - vec2 stomp = vec2(0.0); - //float d = length(worldpos.xy - footPos.xy); - //if (d < 150.0 && d > 0.0) - //{ - // stomp = (60.0 / d - 0.4) * (worldpos.xy - footPos.xy); - //} + float d = length(worldpos - footPos.xyz); + vec3 stomp = vec3(0.0); + if (d < 150.0 && d > 0.0) + { + stomp = (60.0 / d - 0.4) * (worldpos - footPos.xyz); + } - return clamp(0.02 * h, 0.0, 1.0) * (harmonics * displace + stomp); + return clamp(0.02 * h, 0.0, 1.0) * (harmonics * displace + stomp.xy); } mat4 rotation(in vec3 angle) From a09f03c85062180d5ae82796a92b5eab4ce27543 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 18 Dec 2020 14:52:30 +0400 Subject: [PATCH 087/117] Drop groundcover materials - they are not used --- apps/openmw/mwrender/groundcover.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index b9fdc2e28..1a5ccc29f 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -58,6 +58,7 @@ namespace MWRender osg::ref_ptr ss = node.getStateSet(); if (ss != nullptr) { + ss->removeAttribute(osg::StateAttribute::MATERIAL); removeAlpha(ss); } @@ -101,6 +102,7 @@ namespace MWRender ss->setAttribute(new osg::VertexAttribDivisor(6, 1)); ss->setAttribute(new osg::VertexAttribDivisor(7, 1)); + ss->removeAttribute(osg::StateAttribute::MATERIAL); removeAlpha(ss); traverse(geom); From 859cd0fd0c34ef6256b0c70cd4be0bef9797a5a0 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 22:47:19 +0400 Subject: [PATCH 088/117] Do not use display lists for instanced meshes --- apps/openmw/mwrender/groundcover.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 1a5ccc29f..11a155f9f 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -95,6 +95,9 @@ namespace MWRender (*rotations)[i] = mInstances[i].mPos.asRotationVec3(); } + // Display lists do not support instancing in OSG 3.4 + geom.setUseDisplayList(false); + geom.setVertexAttribArray(6, transforms.get(), osg::Array::BIND_PER_VERTEX); geom.setVertexAttribArray(7, rotations.get(), osg::Array::BIND_PER_VERTEX); From 1a6c06f7b59c05c60b31693f46f9a25940881332 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 10 Jan 2021 16:36:25 +0400 Subject: [PATCH 089/117] Do not allow to set distance to non-positive values --- apps/openmw/mwrender/renderingmanager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c755f46f8..1b4b0cf27 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -244,7 +244,7 @@ namespace MWRender globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0"; globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"; - float groundcoverDistance = (Constants::CellSizeInUnits * Settings::Manager::getInt("distance", "Groundcover") - 1024) * 0.93; + float groundcoverDistance = (Constants::CellSizeInUnits * std::max(1, Settings::Manager::getInt("distance", "Groundcover")) - 1024) * 0.93; globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * Settings::Manager::getFloat("fade start", "Groundcover")); globalDefines["groundcoverFadeEnd"] = std::to_string(groundcoverDistance); @@ -1024,7 +1024,7 @@ namespace MWRender if (mGroundcoverWorld) { - int groundcoverDistance = Constants::CellSizeInUnits * Settings::Manager::getInt("distance", "Groundcover"); + int groundcoverDistance = Constants::CellSizeInUnits * std::max(1, Settings::Manager::getInt("distance", "Groundcover")); mGroundcoverWorld->setViewDistance(groundcoverDistance * (distanceMult ? 1.f/distanceMult : 1.f)); } } From 8874e88ff1e0339afdf8365ed793a43bf4a750ea Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 11 Jan 2021 09:59:57 +0400 Subject: [PATCH 090/117] Drop fading setting --- apps/openmw/mwrender/renderingmanager.cpp | 2 +- .../reference/modding/settings/groundcover.rst | 12 ------------ files/settings-default.cfg | 3 --- 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 1b4b0cf27..68576cf44 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -245,7 +245,7 @@ namespace MWRender globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"; float groundcoverDistance = (Constants::CellSizeInUnits * std::max(1, Settings::Manager::getInt("distance", "Groundcover")) - 1024) * 0.93; - globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * Settings::Manager::getFloat("fade start", "Groundcover")); + globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * 0.9f); globalDefines["groundcoverFadeEnd"] = std::to_string(groundcoverDistance); // It is unnecessary to stop/start the viewer as no frames are being rendered yet. diff --git a/docs/source/reference/modding/settings/groundcover.rst b/docs/source/reference/modding/settings/groundcover.rst index 9b00d85a1..f0c37b738 100644 --- a/docs/source/reference/modding/settings/groundcover.rst +++ b/docs/source/reference/modding/settings/groundcover.rst @@ -16,18 +16,6 @@ so we can merge them to pages and animate them indifferently from distance from This setting can only be configured by editing the settings configuration file. -fade start ----------- - -:Type: floating point -:Range: 0.0 to 1.0 -:Default: 0.85 - -Determines on which distance from player groundcover fading starts. -Does not work for meshes which textures do not have transparency (e.g. rocks). - -This setting can only be configured by editing the settings configuration file. - density ------- diff --git a/files/settings-default.cfg b/files/settings-default.cfg index f6bfff7b1..3660f56f0 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -960,9 +960,6 @@ load unsupported nif files = false # enable separate groundcover handling enabled = false -# configure groundcover fade out threshold -fade start = 0.85 - # A groundcover density (0.0 <= value <= 1.0) # 1.0 means 100% density density = 1.0 From d12a0fdcb3dbbe056ae4734f8e931fd3e060274f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 11 Jan 2021 10:02:55 +0400 Subject: [PATCH 091/117] Mark only instances from groundcover files as groundcover objects --- apps/openmw/mwrender/groundcover.cpp | 21 +++++++++++++++++---- apps/openmw/mwrender/objectpaging.cpp | 3 +-- apps/openmw/mwworld/cellpreloader.cpp | 8 ++------ apps/openmw/mwworld/cellstore.cpp | 20 +++++--------------- apps/openmw/mwworld/contentloader.hpp | 2 +- apps/openmw/mwworld/esmloader.cpp | 6 +++--- apps/openmw/mwworld/esmloader.hpp | 2 +- apps/openmw/mwworld/esmstore.cpp | 9 --------- apps/openmw/mwworld/esmstore.hpp | 17 ----------------- apps/openmw/mwworld/worldimp.cpp | 10 ++++++---- components/esm/cellref.cpp | 5 +++++ components/esm/cellref.hpp | 5 +++++ components/esm/esmreader.cpp | 9 +++------ components/esm/esmreader.hpp | 7 ++----- components/esm/loadstat.cpp | 2 -- components/esm/loadstat.hpp | 2 -- 16 files changed, 51 insertions(+), 77 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 11a155f9f..049118c90 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -13,6 +13,17 @@ namespace MWRender { + std::string getGroundcoverModel(int type, const std::string& id, const MWWorld::ESMStore& store) + { + switch (type) + { + case ESM::REC_STAT: + return store.get().searchStatic(id)->mModel; + default: + return std::string(); + } + } + void GroundcoverUpdater::setWindSpeed(float windSpeed) { mWindSpeed = windSpeed; @@ -217,15 +228,17 @@ namespace MWRender while(cell->getNextRef(esm[index], ref, deleted)) { if (deleted) continue; - Misc::StringUtils::lowerCaseInPlace(ref.mRefID); - std::string model; - if (!store.isGroundcover(ref.mRefID, model)) continue; - if (model.empty()) continue; + if (!ref.mRefNum.fromGroundcoverFile()) continue; if (!calculator.isInstanceEnabled()) continue; if (!isInChunkBorders(ref, minBound, maxBound)) continue; + Misc::StringUtils::lowerCaseInPlace(ref.mRefID); + int type = store.findStatic(ref.mRefID); + std::string model = getGroundcoverModel(type, ref.mRefID, store); + if (model.empty()) continue; model = "meshes/" + model; + instances[model].emplace_back(ref, model); } } diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 478fde0f8..b85358c20 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -373,7 +373,6 @@ namespace MWRender std::map refs; std::vector esm; const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX) { for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY) @@ -398,7 +397,7 @@ namespace MWRender int type = store.findStatic(ref.mRefID); if (!typeFilter(type,size>=2)) continue; if (deleted) { refs.erase(ref.mRefNum); continue; } - if (store.isGroundcover(ref.mRefID)) continue; + if (ref.mRefNum.fromGroundcoverFile()) continue; refs[ref.mRefNum] = ref; } } diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 937491f62..421de4a7d 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -1,5 +1,6 @@ #include "cellpreloader.hpp" +#include #include #include @@ -36,12 +37,7 @@ namespace MWWorld virtual bool operator()(const MWWorld::Ptr& ptr) { - if (ptr.getTypeName()==typeid (ESM::Static).name()) - { - const MWWorld::LiveCellRef *ref = ptr.get(); - if (ref->mBase->mIsGroundcover) - return true; - } + if (ptr.getCellRef().getRefNum().fromGroundcoverFile()) return true; ptr.getClass().getModelsToPreload(ptr, mOut); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index d8e2eb65f..7ca35a1df 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -169,18 +169,6 @@ namespace namespace MWWorld { - template - bool CellRefList::ignoreInstance (const X* ptr) - { - return false; - } - - template <> - bool CellRefList::ignoreInstance (const ESM::Static* ptr) - { - return ptr->mIsGroundcover; - } - template void CellRefList::load(ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore) { @@ -188,8 +176,6 @@ namespace MWWorld if (const X *ptr = store.search (ref.mRefID)) { - if (ignoreInstance(ptr)) return; - typename std::list::iterator iter = std::find(mList.begin(), mList.end(), ref.mRefNum); @@ -700,7 +686,11 @@ namespace MWWorld case ESM::REC_NPC_: mNpcs.load(ref, deleted, store); break; case ESM::REC_PROB: mProbes.load(ref, deleted, store); break; case ESM::REC_REPA: mRepairs.load(ref, deleted, store); break; - case ESM::REC_STAT: mStatics.load(ref, deleted, store); break; + case ESM::REC_STAT: + { + if (ref.mRefNum.fromGroundcoverFile()) return; + mStatics.load(ref, deleted, store); break; + } case ESM::REC_WEAP: mWeapons.load(ref, deleted, store); break; case ESM::REC_BODY: mBodyParts.load(ref, deleted, store); break; diff --git a/apps/openmw/mwworld/contentloader.hpp b/apps/openmw/mwworld/contentloader.hpp index b559df083..b529ae9db 100644 --- a/apps/openmw/mwworld/contentloader.hpp +++ b/apps/openmw/mwworld/contentloader.hpp @@ -21,7 +21,7 @@ struct ContentLoader { } - virtual void load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) + virtual void load(const boost::filesystem::path& filepath, int& index) { Log(Debug::Info) << "Loading content file " << filepath.string(); mListener.setLabel(MyGUI::TextIterator::toTagsString(filepath.string())); diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index c96618215..46b806582 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -15,15 +15,15 @@ EsmLoader::EsmLoader(MWWorld::ESMStore& store, std::vector& read { } -void EsmLoader::load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) +void EsmLoader::load(const boost::filesystem::path& filepath, int& index) { - ContentLoader::load(filepath.filename(), index, isGroundcover); + ContentLoader::load(filepath.filename(), index); ESM::ESMReader lEsm; lEsm.setEncoder(mEncoder); lEsm.setIndex(index); lEsm.setGlobalReaderList(&mEsm); - lEsm.open(filepath.string(), isGroundcover); + lEsm.open(filepath.string()); mEsm[index] = lEsm; mStore.load(mEsm[index], &mListener); } diff --git a/apps/openmw/mwworld/esmloader.hpp b/apps/openmw/mwworld/esmloader.hpp index cc4c15a15..506105beb 100644 --- a/apps/openmw/mwworld/esmloader.hpp +++ b/apps/openmw/mwworld/esmloader.hpp @@ -25,7 +25,7 @@ struct EsmLoader : public ContentLoader EsmLoader(MWWorld::ESMStore& store, std::vector& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener& listener); - void load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) override; + void load(const boost::filesystem::path& filepath, int& index) override; private: std::vector& mEsm; diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 2731d7eb1..90bc80b48 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -190,15 +190,6 @@ void ESMStore::setUp(bool validateRecords) { validate(); countRecords(); - - if (mGroundcovers.empty()) - { - for (const ESM::Static& record : mStatics) - { - if (record.mIsGroundcover) - mGroundcovers[record.mId] = record.mModel; - } - } } } diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 18bb95580..d69c56d8c 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -75,7 +75,6 @@ namespace MWWorld // maps the id name to the record type. std::map mIds; std::map mStaticIds; - std::map mGroundcovers; std::map mRefCount; @@ -122,22 +121,6 @@ namespace MWWorld return it->second; } - bool isGroundcover(const std::string &id, std::string &model) const - { - std::map::const_iterator it = mGroundcovers.find(id); - if (it == mGroundcovers.end()) { - return false; - } - model = it->second; - return true; - } - - bool isGroundcover(const std::string &id) const - { - std::map::const_iterator it = mGroundcovers.find(id); - return (it != mGroundcovers.end()); - } - ESMStore() : mDynamicCount(0) { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 93d1a799c..98af121a5 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -103,12 +103,12 @@ namespace MWWorld return mLoaders.insert(std::make_pair(extension, loader)).second; } - void load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) override + void load(const boost::filesystem::path& filepath, int& index) override { LoadersContainer::iterator it(mLoaders.find(Misc::StringUtils::lowerCase(filepath.extension().string()))); if (it != mLoaders.end()) { - it->second->load(filepath, index, isGroundcover); + it->second->load(filepath, index); } else { @@ -2951,7 +2951,7 @@ namespace MWWorld const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); if (col.doesExist(file)) { - contentLoader.load(col.getPath(file), idx, false); + contentLoader.load(col.getPath(file), idx); } else { @@ -2961,13 +2961,15 @@ namespace MWWorld idx++; } + ESM::GroundcoverIndex = idx; + for (const std::string &file : groundcover) { boost::filesystem::path filename(file); const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); if (col.doesExist(file)) { - contentLoader.load(col.getPath(file), idx, true); + contentLoader.load(col.getPath(file), idx); } else { diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 4b9852d65..b4d6ac7a7 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -5,6 +5,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +namespace ESM +{ + int GroundcoverIndex = std::numeric_limits::max(); +} + void ESM::RefNum::load (ESMReader& esm, bool wide, const std::string& tag) { if (wide) diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp index 5bb7fbc53..c2f7ff6de 100644 --- a/components/esm/cellref.hpp +++ b/components/esm/cellref.hpp @@ -12,6 +12,7 @@ namespace ESM class ESMReader; const int UnbreakableLock = std::numeric_limits::max(); + extern int GroundcoverIndex; struct RefNum { @@ -25,6 +26,10 @@ namespace ESM enum { RefNum_NoContentFile = -1 }; inline bool hasContentFile() const { return mContentFile != RefNum_NoContentFile; } inline void unset() { mIndex = 0; mContentFile = RefNum_NoContentFile; } + + // Note: this method should not be used for objects with invalid RefNum + // (for example, for objects from disabled plugins in savegames). + inline bool fromGroundcoverFile() const { return mContentFile >= GroundcoverIndex; } }; /* Cell reference. This represents ONE object (of many) inside the diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 63d2f4d4f..1b6eca734 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -25,7 +25,6 @@ ESMReader::ESMReader() , mGlobalReaderList(nullptr) , mEncoder(nullptr) , mFileSize(0) - , mIsGroundcoverFile(false) { clearCtx(); } @@ -81,10 +80,8 @@ void ESMReader::openRaw(const std::string& filename) openRaw(Files::openConstrainedFileStream(filename.c_str()), filename); } -void ESMReader::open(Files::IStreamPtr _esm, const std::string &name, bool isGroundcover) +void ESMReader::open(Files::IStreamPtr _esm, const std::string &name) { - mIsGroundcoverFile = isGroundcover; - openRaw(_esm, name); if (getRecName() != "TES3") @@ -95,9 +92,9 @@ void ESMReader::open(Files::IStreamPtr _esm, const std::string &name, bool isGro mHeader.load (*this); } -void ESMReader::open(const std::string &file, bool isGroundcover) +void ESMReader::open(const std::string &file) { - open (Files::openConstrainedFileStream (file.c_str ()), file, isGroundcover); + open (Files::openConstrainedFileStream (file.c_str ()), file); } int64_t ESMReader::getHNLong(const char *name) diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index 600cd497b..c660b0dda 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -31,7 +31,6 @@ public: int getVer() const { return mHeader.mData.version; } int getRecordCount() const { return mHeader.mData.records; } - bool isGroundcoverFile() const { return mIsGroundcoverFile; } float getFVer() const { return (mHeader.mData.version == VER_12) ? 1.2f : 1.3f; } const std::string getAuthor() const { return mHeader.mData.author; } const std::string getDesc() const { return mHeader.mData.desc; } @@ -67,9 +66,9 @@ public: /// Load ES file from a new stream, parses the header. Closes the /// currently open file first, if any. - void open(Files::IStreamPtr _esm, const std::string &name, bool isGroundcover = false); + void open(Files::IStreamPtr _esm, const std::string &name); - void open(const std::string &file, bool isGroundcover = false); + void open(const std::string &file); void openRaw(const std::string &filename); @@ -290,8 +289,6 @@ private: ToUTF8::Utf8Encoder* mEncoder; size_t mFileSize; - - bool mIsGroundcoverFile; }; } #endif diff --git a/components/esm/loadstat.cpp b/components/esm/loadstat.cpp index 85e366f1c..6c9de22bd 100644 --- a/components/esm/loadstat.cpp +++ b/components/esm/loadstat.cpp @@ -37,8 +37,6 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - - mIsGroundcover = esm.isGroundcoverFile(); } void Static::save(ESMWriter &esm, bool isDeleted) const { diff --git a/components/esm/loadstat.hpp b/components/esm/loadstat.hpp index da94c4b8d..3d9144040 100644 --- a/components/esm/loadstat.hpp +++ b/components/esm/loadstat.hpp @@ -28,8 +28,6 @@ struct Static std::string mId, mModel; - bool mIsGroundcover = false; - void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; From b975f16e6b9ef5ee8f11dbed504d067a2087b9b1 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 12 Jan 2021 11:23:36 +0400 Subject: [PATCH 092/117] Remove redundant check - groundcover is not present in the CellStore --- apps/openmw/mwworld/cellpreloader.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 421de4a7d..31af5b24b 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -1,6 +1,5 @@ #include "cellpreloader.hpp" -#include #include #include @@ -37,8 +36,6 @@ namespace MWWorld virtual bool operator()(const MWWorld::Ptr& ptr) { - if (ptr.getCellRef().getRefNum().fromGroundcoverFile()) return true; - ptr.getClass().getModelsToPreload(ptr, mOut); return true; From f40e22768673eb9a7d8ffe2126f853659111d599 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 12 Jan 2021 12:39:19 +0400 Subject: [PATCH 093/117] Remove redundant formatting changes --- apps/openmw/engine.cpp | 2 +- apps/openmw/mwrender/objectpaging.cpp | 1 + apps/openmw/mwrender/renderingmanager.cpp | 1 + apps/openmw/mwworld/cellreflist.hpp | 2 -- apps/openmw/mwworld/cellstore.cpp | 1 + apps/openmw/mwworld/esmloader.cpp | 16 ++++++++-------- components/esm/esmreader.hpp | 1 + 7 files changed, 13 insertions(+), 11 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 103d06f31..dfaf09c21 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -725,7 +725,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) } // Create the world - mEnvironment.setWorld(new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), + mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder, mActivationDistanceOverride, mCellName, mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string())); mEnvironment.getWorld()->setupPlayer(); diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index b85358c20..7386c0069 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -373,6 +373,7 @@ namespace MWRender std::map refs; std::vector esm; const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX) { for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 68576cf44..6ba4baec5 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -73,6 +73,7 @@ namespace MWRender { + class StateUpdater : public SceneUtil::StateSetUpdater { public: diff --git a/apps/openmw/mwworld/cellreflist.hpp b/apps/openmw/mwworld/cellreflist.hpp index 69161c840..30be4a661 100644 --- a/apps/openmw/mwworld/cellreflist.hpp +++ b/apps/openmw/mwworld/cellreflist.hpp @@ -24,8 +24,6 @@ namespace MWWorld /// all methods are known. void load (ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore); - inline bool ignoreInstance (const X* ptr); - LiveRef &insert (const LiveRef &item) { mList.push_back(item); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 7ca35a1df..3f98684ae 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -169,6 +169,7 @@ namespace namespace MWWorld { + template void CellRefList::load(ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore) { diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index 46b806582..b12d646e7 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -17,15 +17,15 @@ EsmLoader::EsmLoader(MWWorld::ESMStore& store, std::vector& read void EsmLoader::load(const boost::filesystem::path& filepath, int& index) { - ContentLoader::load(filepath.filename(), index); + ContentLoader::load(filepath.filename(), index); - ESM::ESMReader lEsm; - lEsm.setEncoder(mEncoder); - lEsm.setIndex(index); - lEsm.setGlobalReaderList(&mEsm); - lEsm.open(filepath.string()); - mEsm[index] = lEsm; - mStore.load(mEsm[index], &mListener); + ESM::ESMReader lEsm; + lEsm.setEncoder(mEncoder); + lEsm.setIndex(index); + lEsm.setGlobalReaderList(&mEsm); + lEsm.open(filepath.string()); + mEsm[index] = lEsm; + mStore.load(mEsm[index], &mListener); } } /* namespace MWWorld */ diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index c660b0dda..761756e8f 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -289,6 +289,7 @@ private: ToUTF8::Utf8Encoder* mEncoder; size_t mFileSize; + }; } #endif From 24e1dfcddc50bd51a019d062cc8ad7f4ea2b1515 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Jan 2021 14:30:51 +0400 Subject: [PATCH 094/117] Use default argument --- components/resource/scenemanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 0a4faed01..71f11e382 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -514,7 +514,7 @@ namespace Resource SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor(mMinFilter, mMagFilter, mMaxAnisotropy); loaded->accept(setFilterSettingsControllerVisitor); - osg::ref_ptr shaderVisitor (createShaderVisitor("objects")); + osg::ref_ptr shaderVisitor (createShaderVisitor()); loaded->accept(*shaderVisitor); // share state From e3490c8606c3c83a981421ddfc5bdd74e25c88ae Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 23 Jan 2021 09:30:57 +0400 Subject: [PATCH 095/117] Remove dead code --- files/shaders/groundcover_fragment.glsl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/files/shaders/groundcover_fragment.glsl b/files/shaders/groundcover_fragment.glsl index 9c680853a..77fd32e58 100644 --- a/files/shaders/groundcover_fragment.glsl +++ b/files/shaders/groundcover_fragment.glsl @@ -49,10 +49,6 @@ void main() vec3 viewNormal = gl_NormalMatrix * normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); #endif -#if (!@normalMap && @forcePPL && false) - vec3 viewNormal = gl_NormalMatrix * normalize(passNormal); -#endif - #if @diffuseMap gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); #else From 5225ec9e5045142f7191b6d81a19350f694682b3 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 26 Jan 2021 22:32:06 +0400 Subject: [PATCH 096/117] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96f0460f1..c6c7b5757 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -127,6 +127,7 @@ Feature #5692: Improve spell/magic item search to factor in magic effect names Feature #5730: Add graphic herbalism option to the launcher and documents Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used. + Feature #5813: Instanced groundcover support Task #5480: Drop Qt4 support Task #5520: Improve cell name autocompleter implementation From 64475ebedb803a853c62062eb199df2183076005 Mon Sep 17 00:00:00 2001 From: fredzio Date: Wed, 27 Jan 2021 07:15:09 +0100 Subject: [PATCH 097/117] Remove a brainfart from precise projectile handling: all non-actor non-projectile objects were treated as ground. --- apps/openmw/mwphysics/projectileconvexcallback.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwphysics/projectileconvexcallback.cpp b/apps/openmw/mwphysics/projectileconvexcallback.cpp index 0d0ac8720..b803c4400 100644 --- a/apps/openmw/mwphysics/projectileconvexcallback.cpp +++ b/apps/openmw/mwphysics/projectileconvexcallback.cpp @@ -55,7 +55,9 @@ namespace MWPhysics } default: { - mProjectile->hit(MWWorld::Ptr(), m_hitPointWorld, m_hitNormalWorld); + auto* target = static_cast(result.m_hitCollisionObject->getUserPointer()); + auto ptr = target ? target->getPtr() : MWWorld::Ptr(); + mProjectile->hit(ptr, m_hitPointWorld, m_hitNormalWorld); break; } } From 7cd7fa2f08eaba7de05532a2a1998f2f751959f7 Mon Sep 17 00:00:00 2001 From: Frederic Chardon Date: Wed, 27 Jan 2021 08:04:33 +0000 Subject: [PATCH 098/117] Collect all available stats if OPENMW_OSG_STATS_FILE is set and point to a valid file. --- apps/openmw/engine.cpp | 27 ++++--- apps/openmw/mwphysics/mtphysics.cpp | 2 + components/resource/stats.cpp | 110 +++++++++++++++++++++++++++- components/resource/stats.hpp | 11 ++- 4 files changed, 135 insertions(+), 15 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index ead2726cd..a81a37337 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -177,6 +177,8 @@ namespace ~ScopedProfile() { + if (!mStats.collectStats("engine")) + return; const osg::Timer_t end = mTimer.tick(); const UserStats& stats = UserStatsValue::sValue; @@ -863,16 +865,29 @@ void OMW::Engine::go() prepareEngine (settings); + std::ofstream stats; + if (const auto path = std::getenv("OPENMW_OSG_STATS_FILE")) + { + stats.open(path, std::ios_base::out); + if (stats.is_open()) + Log(Debug::Info) << "Stats will be written to: " << path; + else + Log(Debug::Warning) << "Failed to open file for stats: " << path; + } + // Setup profiler - osg::ref_ptr statshandler = new Resource::Profiler; + osg::ref_ptr statshandler = new Resource::Profiler(stats.is_open()); initStatsHandler(*statshandler); mViewer->addEventHandler(statshandler); - osg::ref_ptr resourceshandler = new Resource::StatsHandler; + osg::ref_ptr resourceshandler = new Resource::StatsHandler(stats.is_open()); mViewer->addEventHandler(resourceshandler); + if (stats.is_open()) + Resource::CollectStatistics(mViewer); + // Start the game if (!mSaveGameFile.empty()) { @@ -897,14 +912,6 @@ void OMW::Engine::go() mEnvironment.getWindowManager()->executeInConsole(mStartupScript); } - std::ofstream stats; - if (const auto path = std::getenv("OPENMW_OSG_STATS_FILE")) - { - stats.open(path, std::ios_base::out); - if (!stats) - Log(Debug::Warning) << "Failed to open file for stats: " << path; - } - // Start the main rendering loop osg::Timer frameTimer; double simulationTime = 0.0; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 6c7c573a4..754bb60af 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -540,6 +540,8 @@ namespace MWPhysics void PhysicsTaskScheduler::updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { + if (!stats.collectStats("engine")) + return; if (mFrameNumber == frameNumber - 1) { stats.setAttribute(mFrameNumber, "physicsworker_time_begin", mTimer->delta_s(mFrameStart, mTimeBegin)); diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 942bd92d8..28b90c31c 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -18,10 +18,72 @@ namespace Resource { -StatsHandler::StatsHandler(): +static bool collectStatRendering = false; +static bool collectStatCameraObjects = false; +static bool collectStatViewerObjects = false; +static bool collectStatResource = false; +static bool collectStatGPU = false; +static bool collectStatEvent = false; +static bool collectStatFrameRate = false; +static bool collectStatUpdate = false; +static bool collectStatEngine = false; + +static void setupStatCollection() +{ + const char* envList = getenv("OPENMW_OSG_STATS_LIST"); + if (envList == nullptr) + return; + + std::string_view kwList(envList); + + auto kwBegin = kwList.begin(); + + while (kwBegin != kwList.end()) + { + auto kwEnd = std::find(kwBegin, kwList.end(), ';'); + + const auto kw = kwList.substr(std::distance(kwList.begin(), kwBegin), std::distance(kwBegin, kwEnd)); + + if (kw.compare("gpu") == 0) + collectStatGPU = true; + else if (kw.compare("event") == 0) + collectStatEvent = true; + else if (kw.compare("frame_rate") == 0) + collectStatFrameRate = true; + else if (kw.compare("update") == 0) + collectStatUpdate = true; + else if (kw.compare("engine") == 0) + collectStatEngine = true; + else if (kw.compare("rendering") == 0) + collectStatRendering = true; + else if (kw.compare("cameraobjects") == 0) + collectStatCameraObjects = true; + else if (kw.compare("viewerobjects") == 0) + collectStatViewerObjects = true; + else if (kw.compare("resource") == 0) + collectStatResource = true; + else if (kw.compare("times") == 0) + { + collectStatGPU = true; + collectStatEvent = true; + collectStatFrameRate = true; + collectStatUpdate = true; + collectStatEngine = true; + collectStatRendering = true; + } + + if (kwEnd == kwList.end()) + break; + + kwBegin = std::next(kwEnd); + } +} + +StatsHandler::StatsHandler(bool offlineCollect): _key(osgGA::GUIEventAdapter::KEY_F4), _initialized(false), _statsType(false), + _offlineCollect(offlineCollect), _statsWidth(1280.0f), _statsHeight(1024.0f), _font(""), @@ -38,7 +100,8 @@ StatsHandler::StatsHandler(): _font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf"); } -Profiler::Profiler() +Profiler::Profiler(bool offlineCollect): + _offlineCollect(offlineCollect) { if (osgDB::Registry::instance()->getReaderWriterForExtension("ttf")) _font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf"); @@ -48,6 +111,28 @@ Profiler::Profiler() _characterSize = 18; setKeyEventTogglesOnScreenStats(osgGA::GUIEventAdapter::KEY_F3); + setupStatCollection(); +} + +bool Profiler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa) +{ + osgViewer::ViewerBase* viewer = nullptr; + + bool handled = StatsHandler::handle(ea, aa); + + auto* view = dynamic_cast(&aa); + if (view) + viewer = view->getViewerBase(); + + if (viewer) + { + // Add/remove openmw stats to the osd as necessary + viewer->getViewerStats()->collectStats("engine", _statsType == StatsHandler::StatsType::VIEWER_STATS); + + if (_offlineCollect) + CollectStatistics(viewer); + } + return handled; } bool StatsHandler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa) @@ -67,6 +152,9 @@ bool StatsHandler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdap toggle(viewer); + if (_offlineCollect) + CollectStatistics(viewer); + aa.requestRedraw(); return true; } @@ -370,6 +458,22 @@ void StatsHandler::getUsage(osg::ApplicationUsage &usage) const usage.addKeyboardMouseBinding(_key, "On screen resource usage stats."); } - +void CollectStatistics(osgViewer::ViewerBase* viewer) +{ + osgViewer::Viewer::Cameras cameras; + viewer->getCameras(cameras); + for (auto* camera : cameras) + { + if (collectStatGPU) camera->getStats()->collectStats("gpu", true); + if (collectStatRendering) camera->getStats()->collectStats("rendering", true); + if (collectStatCameraObjects) camera->getStats()->collectStats("scene", true); + } + if (collectStatEvent) viewer->getViewerStats()->collectStats("event", true); + if (collectStatFrameRate) viewer->getViewerStats()->collectStats("frame_rate", true); + if (collectStatUpdate) viewer->getViewerStats()->collectStats("update", true); + if (collectStatResource) viewer->getViewerStats()->collectStats("resource", true); + if (collectStatViewerObjects) viewer->getViewerStats()->collectStats("scene", true); + if (collectStatEngine) viewer->getViewerStats()->collectStats("engine", true); +} } diff --git a/components/resource/stats.hpp b/components/resource/stats.hpp index 9fa583cca..560275d70 100644 --- a/components/resource/stats.hpp +++ b/components/resource/stats.hpp @@ -18,13 +18,17 @@ namespace Resource class Profiler : public osgViewer::StatsHandler { public: - Profiler(); + Profiler(bool offlineCollect); + bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) override; + + private: + bool _offlineCollect; }; class StatsHandler : public osgGA::GUIEventHandler { public: - StatsHandler(); + StatsHandler(bool offlineCollect); void setKey(int key) { _key = key; } int getKey() const { return _key; } @@ -47,6 +51,7 @@ namespace Resource osg::ref_ptr _camera; bool _initialized; bool _statsType; + bool _offlineCollect; float _statsWidth; float _statsHeight; @@ -58,6 +63,8 @@ namespace Resource }; + void CollectStatistics(osgViewer::ViewerBase* viewer); + } #endif From 23137d0c54f555b30323359953e3db81ded3f361 Mon Sep 17 00:00:00 2001 From: fredzio Date: Wed, 27 Jan 2021 16:24:11 +0100 Subject: [PATCH 099/117] Revert a wrong change introduced in MR 546 A prerequisite to create physics objects for statics was to remove the dependency on base node (since it doesn't yet exists) for object position. It is still necessary for animation though. Restore the basenode (and the associated FIXME) so that animated objects works properly. --- apps/openmw/mwphysics/object.cpp | 3 --- apps/openmw/mwworld/scene.cpp | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 0a7b9540c..6d3954088 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -116,9 +116,6 @@ namespace MWPhysics if (mShapeInstance->mAnimatedShapes.empty()) return false; - if (mPtr.getRefData().getBaseNode() == nullptr) - return true; - assert (mShapeInstance->getCollisionShape()->isCompound()); btCompoundShape* compound = static_cast(mShapeInstance->getCollisionShape()); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index fcf2c4b38..2abedcd78 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -122,6 +122,8 @@ namespace const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) ptr.getClass().insertObjectRendering(ptr, model, rendering); + else + ptr.getRefData().setBaseNode(new SceneUtil::PositionAttitudeTransform); // FIXME remove this when physics code is fixed not to depend on basenode setNodeRotation(ptr, rendering, rotation); From a3ab8dfbb44aef52e895e8746e525d82519799b5 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 28 Jan 2021 12:48:19 +0000 Subject: [PATCH 100/117] Revert "Merge branch 'movement_fix2' into 'master'" This reverts merge request !496 --- apps/openmw/mwmechanics/pathfinding.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 595f9d629..276321b81 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -309,7 +309,7 @@ namespace MWMechanics if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance)) mPath.pop_front(); - if (mPath.size() == 1 && (mPath.front() - position).length2() < destinationTolerance * destinationTolerance) + if (mPath.size() == 1 && sqrDistanceIgnoreZ(mPath.front(), position) < destinationTolerance * destinationTolerance) mPath.pop_front(); } From 3b9f8b5fa2318e35bae594ad94a759e2d6d4a998 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 28 Jan 2021 18:37:47 +0400 Subject: [PATCH 101/117] Avoid null dereference for objects without cells --- apps/openmw/mwworld/worldimp.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 98af121a5..ef00315cb 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1337,6 +1337,12 @@ namespace MWWorld void World::adjustPosition(const Ptr &ptr, bool force) { + if (ptr.isEmpty()) + { + Log(Debug::Warning) << "Unable to adjust position for empty object"; + return; + } + osg::Vec3f pos (ptr.getRefData().getPosition().asVec3()); if(!ptr.getRefData().getBaseNode()) @@ -1345,6 +1351,12 @@ namespace MWWorld return; } + if (!ptr.isInCell()) + { + Log(Debug::Warning) << "Unable to adjust position for object '" << ptr.getCellRef().getRefId() << "' - it has no cell"; + return; + } + const float terrainHeight = ptr.getCell()->isExterior() ? getTerrainHeightAt(pos) : -std::numeric_limits::max(); pos.z() = std::max(pos.z(), terrainHeight) + 20; // place slightly above terrain. will snap down to ground with code below From 36cd81815504cef195fe853b0b93046ac04c3c19 Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 28 Jan 2021 22:10:33 +0100 Subject: [PATCH 102/117] Fix separate drop, refactor for code reuse --- apps/opencs/view/render/instancemode.cpp | 116 ++++++++--------------- apps/opencs/view/render/instancemode.hpp | 21 ++-- 2 files changed, 54 insertions(+), 83 deletions(-) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 4f6759cdb..2489f4f57 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -802,39 +802,15 @@ void CSVRender::InstanceMode::deleteSelectedInstances(bool active) getWorldspaceWidget().clearSelection (Mask_Reference); } -void CSVRender::InstanceMode::dropInstance(DropMode dropMode, CSVRender::Object* object, float objectHeight) +void CSVRender::InstanceMode::dropInstance(CSVRender::Object* object, float dropHeight) { - osg::Vec3d point = object->getPosition().asVec3(); - - osg::Vec3d start = point; - start.z() += objectHeight; - osg::Vec3d end = point; - end.z() = std::numeric_limits::lowest(); - - osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector( - osgUtil::Intersector::MODEL, start, end) ); - intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); - osgUtil::IntersectionVisitor visitor(intersector); - - if (dropMode == TerrainSep) - visitor.setTraversalMask(Mask_Terrain); - if (dropMode == CollisionSep) - visitor.setTraversalMask(Mask_Terrain | Mask_Reference); - - mParentNode->accept(visitor); - - osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin(); - if (it != intersector->getIntersections().end()) - { - osgUtil::LineSegmentIntersector::Intersection intersection = *it; - ESM::Position position = object->getPosition(); - object->setEdited (Object::Override_Position); - position.pos[2] = intersection.getWorldIntersectPoint().z() + objectHeight; - object->setPosition(position.pos); - } + object->setEdited(Object::Override_Position); + ESM::Position position = object->getPosition(); + position.pos[2] -= dropHeight; + object->setPosition(position.pos); } -float CSVRender::InstanceMode::getDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight) +float CSVRender::InstanceMode::calculateDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight) { osg::Vec3d point = object->getPosition().asVec3(); @@ -848,9 +824,9 @@ float CSVRender::InstanceMode::getDropHeight(DropMode dropMode, CSVRender::Objec intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); osgUtil::IntersectionVisitor visitor(intersector); - if (dropMode == Terrain) + if (dropMode & Terrain) visitor.setTraversalMask(Mask_Terrain); - if (dropMode == Collision) + if (dropMode & Collision) visitor.setTraversalMask(Mask_Terrain | Mask_Reference); mParentNode->accept(visitor); @@ -897,52 +873,44 @@ void CSVRender::InstanceMode::handleDropMethod(DropMode dropMode, QString comman CSMWorld::CommandMacro macro (undoStack, commandMsg); - DropObjectDataHandler dropObjectDataHandler(&getWorldspaceWidget()); + DropObjectHeightHandler dropObjectDataHandler(&getWorldspaceWidget()); - switch (dropMode) + if(dropMode & Separate) { - case Terrain: - case Collision: - { - float smallestDropHeight = std::numeric_limits::max(); - int counter = 0; - for(osg::ref_ptr tag: selection) - if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) - { - float thisDrop = getDropHeight(dropMode, objectTag->mObject, dropObjectDataHandler.mObjectHeights[counter]); - if (thisDrop < smallestDropHeight) - smallestDropHeight = thisDrop; - counter++; - } - for(osg::ref_ptr tag: selection) - if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) - { - objectTag->mObject->setEdited (Object::Override_Position); - ESM::Position position = objectTag->mObject->getPosition(); - position.pos[2] -= smallestDropHeight; - objectTag->mObject->setPosition(position.pos); - objectTag->mObject->apply (macro); - } - } - break; - - case TerrainSep: - case CollisionSep: - { - int counter = 0; - for(osg::ref_ptr tag: selection) - if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) - { - dropInstance(dropMode, objectTag->mObject, dropObjectDataHandler.mObjectHeights[counter]); - objectTag->mObject->apply (macro); - counter++; - } - } - break; + int counter = 0; + for (osg::ref_ptr tag : selection) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) + { + float objectHeight = dropObjectDataHandler.mObjectHeights[counter]; + float dropHeight = calculateDropHeight(dropMode, objectTag->mObject, objectHeight); + dropInstance(objectTag->mObject, dropHeight); + objectTag->mObject->apply(macro); + counter++; + } + } + else + { + float smallestDropHeight = std::numeric_limits::max(); + int counter = 0; + for (osg::ref_ptr tag : selection) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) + { + float objectHeight = dropObjectDataHandler.mObjectHeights[counter]; + float thisDrop = calculateDropHeight(dropMode, objectTag->mObject, objectHeight); + if (thisDrop < smallestDropHeight) + smallestDropHeight = thisDrop; + counter++; + } + for (osg::ref_ptr tag : selection) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) + { + dropInstance(objectTag->mObject, smallestDropHeight); + objectTag->mObject->apply(macro); + } } } -CSVRender::DropObjectDataHandler::DropObjectDataHandler(WorldspaceWidget* worldspacewidget) +CSVRender::DropObjectHeightHandler::DropObjectHeightHandler(WorldspaceWidget* worldspacewidget) : mWorldspaceWidget(worldspacewidget) { std::vector > selection = mWorldspaceWidget->getSelection (Mask_Reference); @@ -969,7 +937,7 @@ CSVRender::DropObjectDataHandler::DropObjectDataHandler(WorldspaceWidget* worlds } } -CSVRender::DropObjectDataHandler::~DropObjectDataHandler() +CSVRender::DropObjectHeightHandler::~DropObjectHeightHandler() { std::vector > selection = mWorldspaceWidget->getSelection (Mask_Reference); int counter = 0; diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 0a4f2e478..d0a263ed8 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -28,10 +28,13 @@ namespace CSVRender enum DropMode { - Collision, - Terrain, - CollisionSep, - TerrainSep + Separate = 0x1, + + Collision = 0b10, + Terrain = 0b100, + + CollisionSep = Collision | Separate, + TerrainSep = Terrain | Separate, }; CSVWidget::SceneToolMode *mSubMode; @@ -53,8 +56,8 @@ namespace CSVRender osg::Vec3f getProjectionSpaceCoords(const osg::Vec3f& pos); osg::Vec3f getMousePlaneCoords(const QPoint& point, const osg::Vec3d& dragStart); void handleSelectDrag(const QPoint& pos); - void dropInstance(DropMode dropMode, CSVRender::Object* object, float objectHeight); - float getDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight); + void dropInstance(CSVRender::Object* object, float objectHeight); + float calculateDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight); public: @@ -116,11 +119,11 @@ namespace CSVRender }; /// \brief Helper class to handle object mask data in safe way - class DropObjectDataHandler + class DropObjectHeightHandler { public: - DropObjectDataHandler(WorldspaceWidget* worldspacewidget); - ~DropObjectDataHandler(); + DropObjectHeightHandler(WorldspaceWidget* worldspacewidget); + ~DropObjectHeightHandler(); std::vector mObjectHeights; private: From edc6d5c3e725a2da0c53d82dc025f718008e185d Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 28 Jan 2021 22:22:48 +0100 Subject: [PATCH 103/117] Fix a typo in separate drop binds --- apps/opencs/view/render/instancemode.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 2489f4f57..99ddce7f7 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -854,12 +854,12 @@ void CSVRender::InstanceMode::dropSelectedInstancesToTerrain() void CSVRender::InstanceMode::dropSelectedInstancesToCollisionSeparately() { - handleDropMethod(TerrainSep, "Drop instances to next collision level separately"); + handleDropMethod(CollisionSep, "Drop instances to next collision level separately"); } void CSVRender::InstanceMode::dropSelectedInstancesToTerrainSeparately() { - handleDropMethod(CollisionSep, "Drop instances to terrain level separately"); + handleDropMethod(TerrainSep, "Drop instances to terrain level separately"); } void CSVRender::InstanceMode::handleDropMethod(DropMode dropMode, QString commandMsg) From eca0d8b7ea9a9e63779a8dff55623c12a151f870 Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 28 Jan 2021 22:40:44 +0100 Subject: [PATCH 104/117] Fix typo in DropMode enum --- apps/opencs/view/render/instancemode.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index d0a263ed8..c81c153d4 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -28,7 +28,7 @@ namespace CSVRender enum DropMode { - Separate = 0x1, + Separate = 0b1, Collision = 0b10, Terrain = 0b100, From ee2f0e7eb33d1226f4e9b961f8a355224bff079f Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 28 Jan 2021 23:47:38 +0100 Subject: [PATCH 105/117] Fix inconsistent argument name --- apps/opencs/view/render/instancemode.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index c81c153d4..73b7fff12 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -56,7 +56,7 @@ namespace CSVRender osg::Vec3f getProjectionSpaceCoords(const osg::Vec3f& pos); osg::Vec3f getMousePlaneCoords(const QPoint& point, const osg::Vec3d& dragStart); void handleSelectDrag(const QPoint& pos); - void dropInstance(CSVRender::Object* object, float objectHeight); + void dropInstance(CSVRender::Object* object, float dropHeight); float calculateDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight); public: From f8e8496d36b2a232c15c421b1d55c3c8a9cf49f4 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 29 Jan 2021 16:50:39 +0400 Subject: [PATCH 106/117] Revert "Revert a wrong change introduced in MR 546" This reverts commit 23137d0c54f555b30323359953e3db81ded3f361. --- apps/openmw/mwphysics/object.cpp | 3 +++ apps/openmw/mwworld/scene.cpp | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 6d3954088..0a7b9540c 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -116,6 +116,9 @@ namespace MWPhysics if (mShapeInstance->mAnimatedShapes.empty()) return false; + if (mPtr.getRefData().getBaseNode() == nullptr) + return true; + assert (mShapeInstance->getCollisionShape()->isCompound()); btCompoundShape* compound = static_cast(mShapeInstance->getCollisionShape()); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 2abedcd78..fcf2c4b38 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -122,8 +122,6 @@ namespace const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) ptr.getClass().insertObjectRendering(ptr, model, rendering); - else - ptr.getRefData().setBaseNode(new SceneUtil::PositionAttitudeTransform); // FIXME remove this when physics code is fixed not to depend on basenode setNodeRotation(ptr, rendering, rotation); From 8019fd594defac7d18e88c690f1bde910811a85d Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 29 Jan 2021 16:50:56 +0400 Subject: [PATCH 107/117] Revert "Add changelog" This reverts commit f219c5992bc6efe688ab128915f2d36c39da47a0. --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6c7b5757..f347fb46e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,7 +44,6 @@ Bug #5367: Selecting a spell on an enchanted item per hotkey always plays the equip sound Bug #5369: Spawnpoint in the Grazelands doesn't produce oversized creatures Bug #5370: Opening an unlocked but trapped door uses the key - Bug #5379: Wandering NPCs falling through cantons Bug #5384: openmw-cs: deleting an instance requires reload of scene window to show in editor Bug #5387: Move/MoveWorld don't update the object's cell properly Bug #5391: Races Redone 1.2 bodies don't show on the inventory From 165af1c365628a4e2ae1bec6a54cc39b335db83a Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 29 Jan 2021 16:51:05 +0400 Subject: [PATCH 108/117] Revert "Some actors are supposed to spawn on a static object that belong to an adjacent cell." This reverts commit f031a191b847443c848637b17d0936a43b5070b5. --- apps/openmw/mwclass/static.cpp | 5 - apps/openmw/mwclass/static.hpp | 2 - apps/openmw/mwphysics/physicssystem.cpp | 5 +- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwworld/cellvisitors.hpp | 13 +- apps/openmw/mwworld/class.hpp | 4 - apps/openmw/mwworld/scene.cpp | 421 ++++++++++-------------- apps/openmw/mwworld/scene.hpp | 15 +- 8 files changed, 190 insertions(+), 277 deletions(-) diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 28156c97f..108c4eaa2 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -63,9 +63,4 @@ namespace MWClass return MWWorld::Ptr(cell.insert(ref), &cell); } - - bool Static::isStatic() const - { - return true; - } } diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index d0f4913f0..f856e9fd9 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -25,8 +25,6 @@ namespace MWClass static void registerSelf(); std::string getModel(const MWWorld::ConstPtr &ptr) const override; - - bool isStatic() const override; }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 5a9a0be83..68cec48bc 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -469,7 +469,7 @@ namespace MWPhysics mAnimatedObjects.insert(obj.get()); } - void PhysicsSystem::remove(const MWWorld::Ptr &ptr, bool keepObject) + void PhysicsSystem::remove(const MWWorld::Ptr &ptr) { ObjectMap::iterator found = mObjects.find(ptr); if (found != mObjects.end()) @@ -479,8 +479,7 @@ namespace MWPhysics mAnimatedObjects.erase(found->second.get()); - if (!keepObject) - mObjects.erase(found); + mObjects.erase(found); } ActorMap::iterator foundActor = mActors.find(ptr); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 715a6cd1a..c61b368f8 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -138,7 +138,7 @@ namespace MWPhysics Projectile* getProjectile(int projectileId) const; // Object or Actor - void remove (const MWWorld::Ptr& ptr, bool keepObject = false); + void remove (const MWWorld::Ptr& ptr); void updateScale (const MWWorld::Ptr& ptr); void updateRotation (const MWWorld::Ptr& ptr, osg::Quat rotate); diff --git a/apps/openmw/mwworld/cellvisitors.hpp b/apps/openmw/mwworld/cellvisitors.hpp index 5985d06fb..e68b383b7 100644 --- a/apps/openmw/mwworld/cellvisitors.hpp +++ b/apps/openmw/mwworld/cellvisitors.hpp @@ -18,23 +18,12 @@ namespace MWWorld if (ptr.getRefData().getBaseNode()) { ptr.getRefData().setBaseNode(nullptr); + mObjects.push_back (ptr); } - mObjects.push_back (ptr); return true; } }; - - struct ListObjectsVisitor - { - std::vector mObjects; - - bool operator() (MWWorld::Ptr ptr) - { - mObjects.push_back (ptr); - return true; - } - }; } #endif diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 592552d47..39fb6fe4c 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -318,10 +318,6 @@ namespace MWWorld return false; } - virtual bool isStatic() const { - return false; - } - virtual bool isBipedal(const MWWorld::ConstPtr& ptr) const; virtual bool canFly(const MWWorld::ConstPtr& ptr) const; virtual bool canSwim(const MWWorld::ConstPtr& ptr) const; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index fcf2c4b38..313e9a152 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -105,7 +105,7 @@ namespace } void addObject(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, - MWRender::RenderingManager& rendering, std::set& pagedRefs, bool onlyPhysics) + MWRender::RenderingManager& rendering, std::set& pagedRefs) { if (ptr.getRefData().getBaseNode() || physics.getActor(ptr)) { @@ -113,29 +113,25 @@ namespace return; } + bool useAnim = ptr.getClass().useAnim(); std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS()); + + const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); + if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) + ptr.getClass().insertObjectRendering(ptr, model, rendering); + const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); - if (!onlyPhysics) - { - bool useAnim = ptr.getClass().useAnim(); + setNodeRotation(ptr, rendering, rotation); + ptr.getClass().insertObject (ptr, model, rotation, physics); - const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); - if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) - ptr.getClass().insertObjectRendering(ptr, model, rendering); + if (useAnim) + MWBase::Environment::get().getMechanicsManager()->add(ptr); - setNodeRotation(ptr, rendering, rotation); + if (ptr.getClass().isActor()) + rendering.addWaterRippleEmitter(ptr); - if (useAnim) - MWBase::Environment::get().getMechanicsManager()->add(ptr); - - if (ptr.getClass().isActor()) - rendering.addWaterRippleEmitter(ptr); - - // Restore effect particles - MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); - } - if (!physics.getObject(ptr)) - ptr.getClass().insertObject (ptr, model, rotation, physics); + // Restore effect particles + MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); } void addObject(const MWWorld::Ptr& ptr, const MWPhysics::PhysicsSystem& physics, DetourNavigator::Navigator& navigator) @@ -206,12 +202,11 @@ namespace { MWWorld::CellStore& mCell; Loading::Listener& mLoadingListener; - bool mOnlyStatics; bool mTest; std::vector mToInsert; - InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool onlyStatics, bool test); + InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool test); bool operator() (const MWWorld::Ptr& ptr); @@ -219,8 +214,8 @@ namespace void insert(AddObject&& addObject); }; - InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool onlyStatics, bool test) - : mCell (cell), mLoadingListener (loadingListener), mOnlyStatics(onlyStatics), mTest(test) + InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool test) + : mCell (cell), mLoadingListener (loadingListener), mTest(test) {} bool InsertVisitor::operator() (const MWWorld::Ptr& ptr) @@ -236,7 +231,7 @@ namespace { for (MWWorld::Ptr& ptr : mToInsert) { - if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled() && ((mOnlyStatics && ptr.getClass().isStatic()) || !mOnlyStatics)) + if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled()) { try { @@ -269,16 +264,6 @@ namespace return std::abs(cellPosition.first) + std::abs(cellPosition.second); } - bool isCellInCollection(int x, int y, MWWorld::Scene::CellStoreCollection& collection) - { - for (auto *cell : collection) - { - assert(cell->getCell()->isExterior()); - if (x == cell->getCell()->getGridX() && y == cell->getCell()->getGridY()) - return true; - } - return false; - } } @@ -330,41 +315,15 @@ namespace MWWorld mRendering.update (duration, paused); } - void Scene::unloadInactiveCell (CellStore* cell, bool test) + void Scene::unloadCell (CellStoreCollection::iterator iter, bool test) { - assert(mActiveCells.find(cell) == mActiveCells.end()); - assert(mInactiveCells.find(cell) != mInactiveCells.end()); if (!test) - Log(Debug::Info) << "Unloading cell " << cell->getCell()->getDescription(); - - ListObjectsVisitor visitor; - - cell->forEach(visitor); - for (const auto& ptr : visitor.mObjects) - mPhysics->remove(ptr); - - if (cell->getCell()->isExterior()) - { - const auto cellX = cell->getCell()->getGridX(); - const auto cellY = cell->getCell()->getGridY(); - mPhysics->removeHeightField(cellX, cellY); - } - - mInactiveCells.erase(cell); - } - - void Scene::deactivateCell(CellStore* cell, bool test) - { - assert(mInactiveCells.find(cell) != mInactiveCells.end()); - if (mActiveCells.find(cell) == mActiveCells.end()) - return; - if (!test) - Log(Debug::Info) << "Deactivate cell " << cell->getCell()->getDescription(); + Log(Debug::Info) << "Unloading cell " << (*iter)->getCell()->getDescription(); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); ListAndResetObjectsVisitor visitor; - cell->forEach(visitor); + (*iter)->forEach(visitor); const auto world = MWBase::Environment::get().getWorld(); for (const auto& ptr : visitor.mObjects) { @@ -375,157 +334,140 @@ namespace MWWorld navigator->removeAgent(world->getPathfindingHalfExtents(ptr)); mRendering.removeActorPath(ptr); } - mPhysics->remove(ptr, ptr.getClass().isStatic()); + mPhysics->remove(ptr); } - const auto cellX = cell->getCell()->getGridX(); - const auto cellY = cell->getCell()->getGridY(); + const auto cellX = (*iter)->getCell()->getGridX(); + const auto cellY = (*iter)->getCell()->getGridY(); - if (cell->getCell()->isExterior()) + if ((*iter)->getCell()->isExterior()) { if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) navigator->removeObject(DetourNavigator::ObjectId(heightField)); + mPhysics->removeHeightField(cellX, cellY); } - if (cell->getCell()->hasWater()) + if ((*iter)->getCell()->hasWater()) navigator->removeWater(osg::Vec2i(cellX, cellY)); - if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) + if (const auto pathgrid = world->getStore().get().search(*(*iter)->getCell())) navigator->removePathgrid(*pathgrid); const auto player = world->getPlayerPtr(); navigator->update(player.getRefData().getPosition().asVec3()); - MWBase::Environment::get().getMechanicsManager()->drop (cell); + MWBase::Environment::get().getMechanicsManager()->drop (*iter); - mRendering.removeCell(cell); - MWBase::Environment::get().getWindowManager()->removeCell(cell); + mRendering.removeCell(*iter); + MWBase::Environment::get().getWindowManager()->removeCell(*iter); - MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (cell); + MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (*iter); - MWBase::Environment::get().getSoundManager()->stopSound (cell); - mActiveCells.erase(cell); + MWBase::Environment::get().getSoundManager()->stopSound (*iter); + mActiveCells.erase(*iter); } - void Scene::activateCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test) + void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test) { - assert(mActiveCells.find(cell) == mActiveCells.end()); - assert(mInactiveCells.find(cell) != mInactiveCells.end()); - mActiveCells.insert(cell); + std::pair result = mActiveCells.insert(cell); - if (test) - Log(Debug::Info) << "Testing cell " << cell->getCell()->getDescription(); - else - Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); - - const auto world = MWBase::Environment::get().getWorld(); - const auto navigator = world->getNavigator(); - - const int cellX = cell->getCell()->getGridX(); - const int cellY = cell->getCell()->getGridY(); - - if (!test && cell->getCell()->isExterior()) + if(result.second) { - if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) - navigator->addObject(DetourNavigator::ObjectId(heightField), *heightField->getShape(), - heightField->getCollisionObject()->getWorldTransform()); - } + if (test) + Log(Debug::Info) << "Testing cell " << cell->getCell()->getDescription(); + else + Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); - if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) - navigator->addPathgrid(*cell->getCell(), *pathgrid); + float verts = ESM::Land::LAND_SIZE; + float worldsize = ESM::Land::REAL_SIZE; - // register local scripts - // do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice - MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); + const auto world = MWBase::Environment::get().getWorld(); + const auto navigator = world->getNavigator(); - if (respawn) - cell->respawn(); + const int cellX = cell->getCell()->getGridX(); + const int cellY = cell->getCell()->getGridY(); - insertCell (*cell, loadingListener, false, test); - - mRendering.addCell(cell); - if (!test) - { - MWBase::Environment::get().getWindowManager()->addCell(cell); - bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior(); - float waterLevel = cell->getWaterLevel(); - mRendering.setWaterEnabled(waterEnabled); - if (waterEnabled) + // Load terrain physics first... + if (!test && cell->getCell()->isExterior()) { - mPhysics->enableWater(waterLevel); - mRendering.setWaterHeight(waterLevel); - - if (cell->getCell()->isExterior()) + osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; + if (data) { - if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) - navigator->addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, - cell->getWaterLevel(), heightField->getCollisionObject()->getWorldTransform()); + mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get()); } else { - navigator->addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), + static std::vector defaultHeight; + defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); + mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); + } + + if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) + navigator->addObject(DetourNavigator::ObjectId(heightField), *heightField->getShape(), + heightField->getCollisionObject()->getWorldTransform()); + } + + if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) + navigator->addPathgrid(*cell->getCell(), *pathgrid); + + // register local scripts + // do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice + MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); + + if (respawn) + cell->respawn(); + + // ... then references. This is important for adjustPosition to work correctly. + insertCell (*cell, loadingListener, test); + + mRendering.addCell(cell); + if (!test) + { + MWBase::Environment::get().getWindowManager()->addCell(cell); + bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior(); + float waterLevel = cell->getWaterLevel(); + mRendering.setWaterEnabled(waterEnabled); + if (waterEnabled) + { + mPhysics->enableWater(waterLevel); + mRendering.setWaterHeight(waterLevel); + + if (cell->getCell()->isExterior()) + { + if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) + navigator->addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, + cell->getWaterLevel(), heightField->getCollisionObject()->getWorldTransform()); + } + else + { + navigator->addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), cell->getWaterLevel(), btTransform::getIdentity()); + } + } + else + mPhysics->disableWater(); + + const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + navigator->update(player.getRefData().getPosition().asVec3()); + + if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) + { + + mRendering.configureAmbient(cell->getCell()); } } - else - mPhysics->disableWater(); - - const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - navigator->update(player.getRefData().getPosition().asVec3()); - - if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) - mRendering.configureAmbient(cell->getCell()); } mPreloader->notifyLoaded(cell); } - void Scene::loadInactiveCell (CellStore *cell, Loading::Listener* loadingListener, bool test) - { - assert(mActiveCells.find(cell) == mActiveCells.end()); - assert(mInactiveCells.find(cell) == mInactiveCells.end()); - mInactiveCells.insert(cell); - - if (test) - Log(Debug::Info) << "Testing inactive cell " << cell->getCell()->getDescription(); - else - Log(Debug::Info) << "Loading inactive cell " << cell->getCell()->getDescription(); - - if (!test && cell->getCell()->isExterior()) - { - float verts = ESM::Land::LAND_SIZE; - float worldsize = ESM::Land::REAL_SIZE; - - const int cellX = cell->getCell()->getGridX(); - const int cellY = cell->getCell()->getGridY(); - - osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); - const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; - if (data) - { - mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get()); - } - else - { - static std::vector defaultHeight; - defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); - mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); - } - } - - insertCell (*cell, loadingListener, true, test); - } - void Scene::clear() { - for (auto iter = mInactiveCells.begin(); iter!=mInactiveCells.end(); ) - { - auto* cell = *iter++; - deactivateCell(cell); - unloadInactiveCell (cell); - } + CellStoreCollection::iterator active = mActiveCells.begin(); + while (active!=mActiveCells.end()) + unloadCell (active++); assert(mActiveCells.empty()); - assert(mInactiveCells.empty()); mCurrentCell = nullptr; mPreloader->clear(); @@ -568,24 +510,20 @@ namespace MWWorld void Scene::changeCellGrid (const osg::Vec3f &pos, int playerCellX, int playerCellY, bool changeEvent) { - for (auto iter = mInactiveCells.begin(); iter != mInactiveCells.end(); ) + CellStoreCollection::iterator active = mActiveCells.begin(); + while (active!=mActiveCells.end()) { - auto* cell = *iter++; - if (cell->getCell()->isExterior()) + if ((*active)->getCell()->isExterior()) { - const auto dx = std::abs(playerCellX - cell->getCell()->getGridX()); - const auto dy = std::abs(playerCellY - cell->getCell()->getGridY()); - if (dx > mHalfGridSize || dy > mHalfGridSize) - deactivateCell(cell); - - if (dx > mHalfGridSize+1 || dy > mHalfGridSize+1) - unloadInactiveCell(cell); - } - else - { - deactivateCell(cell); - unloadInactiveCell(cell); + if (std::abs (playerCellX-(*active)->getCell()->getGridX())<=mHalfGridSize && + std::abs (playerCellY-(*active)->getCell()->getGridY())<=mHalfGridSize) + { + // keep cells within the new grid + ++active; + continue; + } } + unloadCell (active++); } mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY); @@ -597,24 +535,32 @@ namespace MWWorld mRendering.getPagedRefnums(newGrid, mPagedRefs); std::size_t refsToLoad = 0; - const auto cellsToLoad = [&playerCellX,&playerCellY,&refsToLoad](CellStoreCollection& collection, int range) -> std::vector> + std::vector> cellsPositionsToLoad; + // get the number of refs to load + for (int x = playerCellX - mHalfGridSize; x <= playerCellX + mHalfGridSize; ++x) { - std::vector> cellsPositionsToLoad; - for (int x = playerCellX - range; x <= playerCellX + range; ++x) + for (int y = playerCellY - mHalfGridSize; y <= playerCellY + mHalfGridSize; ++y) { - for (int y = playerCellY - range; y <= playerCellY + range; ++y) + CellStoreCollection::iterator iter = mActiveCells.begin(); + + while (iter!=mActiveCells.end()) { - if (!isCellInCollection(x, y, collection)) - { - refsToLoad += MWBase::Environment::get().getWorld()->getExterior(x, y)->count(); - cellsPositionsToLoad.emplace_back(x, y); - } + assert ((*iter)->getCell()->isExterior()); + + if (x==(*iter)->getCell()->getGridX() && + y==(*iter)->getCell()->getGridY()) + break; + + ++iter; + } + + if (iter==mActiveCells.end()) + { + refsToLoad += MWBase::Environment::get().getWorld()->getExterior(x, y)->count(); + cellsPositionsToLoad.emplace_back(x, y); } } - return cellsPositionsToLoad; - }; - auto cellsPositionsToLoad = cellsToLoad(mActiveCells,mHalfGridSize); - auto cellsPositionsToLoadInactive = cellsToLoad(mInactiveCells,mHalfGridSize+1); + } Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); @@ -638,26 +584,30 @@ namespace MWWorld return getCellPositionPriority(lhs) < getCellPositionPriority(rhs); }); - std::sort(cellsPositionsToLoadInactive.begin(), cellsPositionsToLoadInactive.end(), - [&] (const std::pair& lhs, const std::pair& rhs) { - return getCellPositionPriority(lhs) < getCellPositionPriority(rhs); - }); - // Load cells - for (const auto& [x,y] : cellsPositionsToLoadInactive) + for (const auto& cellPosition : cellsPositionsToLoad) { - if (!isCellInCollection(x, y, mInactiveCells)) + const auto x = cellPosition.first; + const auto y = cellPosition.second; + + CellStoreCollection::iterator iter = mActiveCells.begin(); + + while (iter != mActiveCells.end()) { - CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); - loadInactiveCell (cell, loadingListener); + assert ((*iter)->getCell()->isExterior()); + + if (x == (*iter)->getCell()->getGridX() && + y == (*iter)->getCell()->getGridY()) + break; + + ++iter; } - } - for (const auto& [x,y] : cellsPositionsToLoad) - { - if (!isCellInCollection(x, y, mActiveCells)) + + if (iter == mActiveCells.end()) { CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); - activateCell (cell, loadingListener, changeEvent); + + loadCell (cell, loadingListener, changeEvent); } } @@ -690,8 +640,7 @@ namespace MWWorld CellStoreCollection::iterator iter = mActiveCells.begin(); CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(it->mData.mX, it->mData.mY); - loadInactiveCell (cell, loadingListener, true); - activateCell (cell, loadingListener, false, true); + loadCell (cell, loadingListener, false, true); iter = mActiveCells.begin(); while (iter != mActiveCells.end()) @@ -699,8 +648,7 @@ namespace MWWorld if (it->isExterior() && it->mData.mX == (*iter)->getCell()->getGridX() && it->mData.mY == (*iter)->getCell()->getGridY()) { - deactivateCell(*iter, true); - unloadInactiveCell (*iter, true); + unloadCell(iter, true); break; } @@ -738,8 +686,7 @@ namespace MWWorld loadingListener->setLabel("Testing interior cells ("+std::to_string(i)+"/"+std::to_string(cells.getIntSize())+")..."); CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(it->mName); - loadInactiveCell (cell, loadingListener, true); - activateCell (cell, loadingListener, false, true); + loadCell (cell, loadingListener, false, true); CellStoreCollection::iterator iter = mActiveCells.begin(); while (iter != mActiveCells.end()) @@ -748,8 +695,7 @@ namespace MWWorld if (it->mName == (*iter)->getCell()->mName) { - deactivateCell(*iter, true); - unloadInactiveCell (*iter, true); + unloadCell(iter, true); break; } @@ -872,21 +818,15 @@ namespace MWWorld Log(Debug::Info) << "Changing to interior"; // unload - for (auto iter = mInactiveCells.begin(); iter!=mInactiveCells.end(); ) - { - auto* cell = *iter++; - deactivateCell(cell); - unloadInactiveCell(cell); - } - assert(mActiveCells.empty()); - assert(mInactiveCells.empty()); + CellStoreCollection::iterator active = mActiveCells.begin(); + while (active!=mActiveCells.end()) + unloadCell (active++); loadingListener->setProgressRange(cell->count()); // Load cell. mPagedRefs.clear(); - loadInactiveCell (cell, loadingListener); - activateCell (cell, loadingListener, changeEvent); + loadCell (cell, loadingListener, changeEvent); changePlayerCell(cell, position, adjustPlayerPos); @@ -934,26 +874,23 @@ namespace MWWorld mCellChanged = false; } - void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyStatics, bool test) + void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test) { - InsertVisitor insertVisitor (cell, *loadingListener, onlyStatics, test); + InsertVisitor insertVisitor (cell, *loadingListener, test); cell.forEach (insertVisitor); - insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs, onlyStatics); }); - if (!onlyStatics) - { - insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs); }); + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); - // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order - PositionVisitor posVisitor; - cell.forEach (posVisitor); - } + // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order + PositionVisitor posVisitor; + cell.forEach (posVisitor); } void Scene::addObjectToScene (const Ptr& ptr) { try { - addObject(ptr, *mPhysics, mRendering, mPagedRefs, false); + addObject(ptr, *mPhysics, mRendering, mPagedRefs); addObject(ptr, *mPhysics, mNavigator); MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 33c7b78d0..a70d3ccdd 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -65,13 +65,13 @@ namespace MWWorld class Scene { public: - using CellStoreCollection = std::set; + + typedef std::set CellStoreCollection; private: CellStore* mCurrentCell; // the cell the player is in CellStoreCollection mActiveCells; - CellStoreCollection mInactiveCells; bool mCellChanged; MWPhysics::PhysicsSystem *mPhysics; MWRender::RenderingManager& mRendering; @@ -92,7 +92,7 @@ namespace MWWorld std::set mPagedRefs; - void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyStatics, bool test = false); + void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test = false); osg::Vec2i mCurrentGridCenter; // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center @@ -108,11 +108,6 @@ namespace MWWorld osg::Vec4i gridCenterToBounds(const osg::Vec2i ¢erCell) const; osg::Vec2i getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i *currentGridCenter = nullptr) const; - void unloadInactiveCell (CellStore* cell, bool test = false); - void deactivateCell (CellStore* cell, bool test = false); - void activateCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test = false); - void loadInactiveCell (CellStore *cell, Loading::Listener* loadingListener, bool test = false); - public: Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics, @@ -124,6 +119,10 @@ namespace MWWorld void preloadTerrain(const osg::Vec3f& pos, bool sync=false); void reloadTerrain(); + void unloadCell (CellStoreCollection::iterator iter, bool test = false); + + void loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test = false); + void playerMoved (const osg::Vec3f& pos); void changePlayerCell (CellStore* newCell, const ESM::Position& position, bool adjustPlayerPos); From 7b727e4d70df35560fe08da8a0ba839e8d6790de Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 29 Jan 2021 16:51:13 +0400 Subject: [PATCH 109/117] Revert "Remove physics dependency on basenode" This reverts commit 165c7314928dc281a364fa1a0143c45fd6d2adfd. --- apps/openmw/mwclass/activator.cpp | 4 +-- apps/openmw/mwclass/activator.hpp | 2 +- apps/openmw/mwclass/actor.cpp | 6 ++++- apps/openmw/mwclass/actor.hpp | 10 +++---- apps/openmw/mwclass/apparatus.cpp | 5 ++++ apps/openmw/mwclass/apparatus.hpp | 2 ++ apps/openmw/mwclass/armor.cpp | 5 ++++ apps/openmw/mwclass/armor.hpp | 2 ++ apps/openmw/mwclass/bodypart.cpp | 4 +++ apps/openmw/mwclass/bodypart.hpp | 2 ++ apps/openmw/mwclass/book.cpp | 5 ++++ apps/openmw/mwclass/book.hpp | 2 ++ apps/openmw/mwclass/clothing.cpp | 5 ++++ apps/openmw/mwclass/clothing.hpp | 2 ++ apps/openmw/mwclass/container.cpp | 4 +-- apps/openmw/mwclass/container.hpp | 2 +- apps/openmw/mwclass/door.cpp | 4 +-- apps/openmw/mwclass/door.hpp | 2 +- apps/openmw/mwclass/ingredient.cpp | 5 ++++ apps/openmw/mwclass/ingredient.hpp | 2 ++ apps/openmw/mwclass/light.cpp | 4 +-- apps/openmw/mwclass/light.hpp | 2 +- apps/openmw/mwclass/lockpick.cpp | 5 ++++ apps/openmw/mwclass/lockpick.hpp | 2 ++ apps/openmw/mwclass/misc.cpp | 5 ++++ apps/openmw/mwclass/misc.hpp | 2 ++ apps/openmw/mwclass/potion.cpp | 5 ++++ apps/openmw/mwclass/potion.hpp | 2 ++ apps/openmw/mwclass/probe.cpp | 5 ++++ apps/openmw/mwclass/probe.hpp | 2 ++ apps/openmw/mwclass/repair.cpp | 5 ++++ apps/openmw/mwclass/repair.hpp | 2 ++ apps/openmw/mwclass/static.cpp | 4 +-- apps/openmw/mwclass/static.hpp | 2 +- apps/openmw/mwclass/weapon.cpp | 5 ++++ apps/openmw/mwclass/weapon.hpp | 2 ++ apps/openmw/mwphysics/actor.cpp | 6 ++--- apps/openmw/mwphysics/actor.hpp | 2 +- apps/openmw/mwphysics/object.cpp | 11 +++----- apps/openmw/mwphysics/object.hpp | 4 +-- apps/openmw/mwphysics/physicssystem.cpp | 10 +++---- apps/openmw/mwphysics/physicssystem.hpp | 4 +-- apps/openmw/mwworld/class.cpp | 6 ++++- apps/openmw/mwworld/class.hpp | 13 ++++----- apps/openmw/mwworld/scene.cpp | 36 ++++++++++++------------- apps/openmw/mwworld/worldimp.cpp | 2 +- 46 files changed, 155 insertions(+), 68 deletions(-) diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 716e548e1..c54b1c369 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -38,10 +38,10 @@ namespace MWClass } } - void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, rotation); + physics.addObject(ptr, model); } std::string Activator::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index c7b65ef67..10ace6f74 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -17,7 +17,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp index 1789f1b19..33aeb26bb 100644 --- a/apps/openmw/mwclass/actor.cpp +++ b/apps/openmw/mwclass/actor.cpp @@ -17,12 +17,16 @@ namespace MWClass { + Actor::Actor() {} + + Actor::~Actor() {} + void Actor::adjustPosition(const MWWorld::Ptr& ptr, bool force) const { MWBase::Environment::get().getWorld()->adjustPosition(ptr, force); } - void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if (!model.empty()) { diff --git a/apps/openmw/mwclass/actor.hpp b/apps/openmw/mwclass/actor.hpp index 98998b4eb..3d509b276 100644 --- a/apps/openmw/mwclass/actor.hpp +++ b/apps/openmw/mwclass/actor.hpp @@ -15,16 +15,16 @@ namespace MWClass { protected: - Actor() = default; + Actor(); public: - ~Actor() override = default; + virtual ~Actor(); void adjustPosition(const MWWorld::Ptr& ptr, bool force) const override; ///< Adjust position to stand on ground. Must be called post model load /// @param force do this even if the ptr is flying - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; bool useAnim() const override; @@ -46,8 +46,8 @@ namespace MWClass float getCurrentSpeed(const MWWorld::Ptr& ptr) const override; // not implemented - Actor(const Actor&) = delete; - Actor& operator= (const Actor&) = delete; + Actor(const Actor&); + Actor& operator= (const Actor&); }; } diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index e09e4804c..518695fab 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -26,6 +26,11 @@ namespace MWClass } } + void Apparatus::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Apparatus::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/apparatus.hpp b/apps/openmw/mwclass/apparatus.hpp index 828abef25..8087c57ba 100644 --- a/apps/openmw/mwclass/apparatus.hpp +++ b/apps/openmw/mwclass/apparatus.hpp @@ -17,6 +17,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 817adbc1f..3f9bfb859 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -34,6 +34,11 @@ namespace MWClass } } + void Armor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Armor::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index f64f138a2..4f04e0824 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -16,6 +16,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/bodypart.cpp b/apps/openmw/mwclass/bodypart.cpp index 7fe798e27..0315d3ddb 100644 --- a/apps/openmw/mwclass/bodypart.cpp +++ b/apps/openmw/mwclass/bodypart.cpp @@ -22,6 +22,10 @@ namespace MWClass } } + void BodyPart::insertObject(const MWWorld::Ptr &ptr, const std::string &model, MWPhysics::PhysicsSystem &physics) const + { + } + std::string BodyPart::getName(const MWWorld::ConstPtr &ptr) const { return std::string(); diff --git a/apps/openmw/mwclass/bodypart.hpp b/apps/openmw/mwclass/bodypart.hpp index 0e372b884..13d914138 100644 --- a/apps/openmw/mwclass/bodypart.hpp +++ b/apps/openmw/mwclass/bodypart.hpp @@ -15,6 +15,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 51b9e39d7..4ea71e3ac 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -31,6 +31,11 @@ namespace MWClass } } + void Book::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Book::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index f3d34c516..c58e68ad8 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 400cd97e4..6d7960aac 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -29,6 +29,11 @@ namespace MWClass } } + void Clothing::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Clothing::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index 3d5c162aa..a87e0cbe0 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 3023466f0..28305c394 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -111,10 +111,10 @@ namespace MWClass } } - void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, rotation); + physics.addObject(ptr, model); } std::string Container::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 74d9ac0da..2dc0c06ca 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -44,7 +44,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 983953c20..ba51d9c2b 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -62,10 +62,10 @@ namespace MWClass } } - void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_Door); + physics.addObject(ptr, model, MWPhysics::CollisionType_Door); // Resume the door's opening/closing animation if it wasn't finished if (ptr.getRefData().getCustomData()) diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index 6d40a840b..6c2fa26b8 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -18,7 +18,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; bool isDoor() const override; diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 20f9576df..a007ad115 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -28,6 +28,11 @@ namespace MWClass } } + void Ingredient::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Ingredient::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/ingredient.hpp b/apps/openmw/mwclass/ingredient.hpp index 2aa831f86..5219cf39c 100644 --- a/apps/openmw/mwclass/ingredient.hpp +++ b/apps/openmw/mwclass/ingredient.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index d4d196bc4..3bdf10f47 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -33,7 +33,7 @@ namespace MWClass renderingInterface.getObjects().insertModel(ptr, model, true, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)); } - void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { MWWorld::LiveCellRef *ref = ptr.get(); @@ -41,7 +41,7 @@ namespace MWClass // TODO: add option somewhere to enable collision for placeable objects if (!model.empty() && (ref->mBase->mData.mFlags & ESM::Light::Carry) == 0) - physics.addObject(ptr, model, rotation); + physics.addObject(ptr, model); if (!ref->mBase->mSound.empty() && !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)) MWBase::Environment::get().getSoundManager()->playSound3D(ptr, ref->mBase->mSound, 1.0, 1.0, diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index 1b1794d4a..e37dddc25 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -14,7 +14,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; bool useAnim() const override; diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 985b08771..9b8abc8f2 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -28,6 +28,11 @@ namespace MWClass } } + void Lockpick::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Lockpick::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index d4b265e39..fabae3343 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index facab9d51..8d3cda6fe 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -37,6 +37,11 @@ namespace MWClass } } + void Miscellaneous::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Miscellaneous::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index 18788c7ed..9bff85ca5 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 56d9dff27..4af97e634 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -30,6 +30,11 @@ namespace MWClass } } + void Potion::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Potion::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp index 75b962164..75d923f0b 100644 --- a/apps/openmw/mwclass/potion.hpp +++ b/apps/openmw/mwclass/potion.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 51273337a..dba4e8c06 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -28,6 +28,11 @@ namespace MWClass } } + void Probe::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Probe::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index ef9273a37..a0a41dcfb 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index f1b88e422..8907c8212 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -25,6 +25,11 @@ namespace MWClass } } + void Repair::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Repair::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/repair.hpp b/apps/openmw/mwclass/repair.hpp index c403449e1..b9791e9cf 100644 --- a/apps/openmw/mwclass/repair.hpp +++ b/apps/openmw/mwclass/repair.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 108c4eaa2..5551b3d73 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -23,10 +23,10 @@ namespace MWClass } } - void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, rotation); + physics.addObject(ptr, model); } std::string Static::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index f856e9fd9..6bc783dad 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -14,7 +14,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 6246c8fb0..0d6a27cf6 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -34,6 +34,11 @@ namespace MWClass } } + void Weapon::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Weapon::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index db17e6b70..f1824b7d1 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -15,6 +15,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 06600fd6a..3b52ee934 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -67,7 +67,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic updateScale(); if(!mRotationallyInvariant) - setRotation(mPtr.getRefData().getBaseNode()->getAttitude()); + updateRotation(); updatePosition(); addCollisionMask(getCollisionMask()); @@ -197,10 +197,10 @@ osg::Vec3f Actor::getPreviousPosition() const return mPreviousPosition; } -void Actor::setRotation(osg::Quat quat) +void Actor::updateRotation () { std::scoped_lock lock(mPositionMutex); - mRotation = quat; + mRotation = mPtr.getRefData().getBaseNode()->getAttitude(); } bool Actor::isRotationallyInvariant() const diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 4039f4481..9d129f2ba 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -49,7 +49,7 @@ namespace MWPhysics void enableCollisionBody(bool collision); void updateScale(); - void setRotation(osg::Quat quat); + void updateRotation(); /** * Return true if the collision shape looks the same no matter how its Z rotated. diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 0a7b9540c..e3615989d 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -14,7 +14,7 @@ namespace MWPhysics { - Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, osg::Quat rotation, int collisionType, PhysicsTaskScheduler* scheduler) + Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler) : mShapeInstance(shapeInstance) , mSolid(true) , mTaskScheduler(scheduler) @@ -27,7 +27,7 @@ namespace MWPhysics mCollisionObject->setUserPointer(this); setScale(ptr.getCellRef().getScale()); - setRotation(rotation); + setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); setOrigin(Misc::Convert::toBullet(ptr.getRefData().getPosition().asVec3())); commitPositionChange(); @@ -51,10 +51,10 @@ namespace MWPhysics mScaleUpdatePending = true; } - void Object::setRotation(osg::Quat quat) + void Object::setRotation(const btQuaternion& quat) { std::unique_lock lock(mPositionMutex); - mLocalTransform.setRotation(Misc::Convert::toBullet(quat)); + mLocalTransform.setRotation(quat); mTransformUpdatePending = true; } @@ -116,9 +116,6 @@ namespace MWPhysics if (mShapeInstance->mAnimatedShapes.empty()) return false; - if (mPtr.getRefData().getBaseNode() == nullptr) - return true; - assert (mShapeInstance->getCollisionShape()->isCompound()); btCompoundShape* compound = static_cast(mShapeInstance->getCollisionShape()); diff --git a/apps/openmw/mwphysics/object.hpp b/apps/openmw/mwphysics/object.hpp index c2273831e..cae877180 100644 --- a/apps/openmw/mwphysics/object.hpp +++ b/apps/openmw/mwphysics/object.hpp @@ -26,12 +26,12 @@ namespace MWPhysics class Object final : public PtrHolder { public: - Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, osg::Quat rotation, int collisionType, PhysicsTaskScheduler* scheduler); + Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler); ~Object() override; const Resource::BulletShapeInstance* getShapeInstance() const; void setScale(float scale); - void setRotation(osg::Quat quat); + void setRotation(const btQuaternion& quat); void setOrigin(const btVector3& vec); void commitPositionChange(); btCollisionObject* getCollisionObject(); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 68cec48bc..ed0e0c915 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -456,13 +456,13 @@ namespace MWPhysics return heightField->second.get(); } - void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType) + void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType) { osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); if (!shapeInstance || !shapeInstance->getCollisionShape()) return; - auto obj = std::make_shared(ptr, shapeInstance, rotation, collisionType, mTaskScheduler.get()); + auto obj = std::make_shared(ptr, shapeInstance, collisionType, mTaskScheduler.get()); mObjects.emplace(ptr, obj); if (obj->isAnimated()) @@ -621,12 +621,12 @@ namespace MWPhysics mTaskScheduler->updateSingleAabb(foundProjectile->second); } - void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr, osg::Quat rotate) + void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr) { ObjectMap::iterator found = mObjects.find(ptr); if (found != mObjects.end()) { - found->second->setRotation(rotate); + found->second->setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); mTaskScheduler->updateSingleAabb(found->second); return; } @@ -635,7 +635,7 @@ namespace MWPhysics { if (!foundActor->second->isRotationallyInvariant()) { - foundActor->second->setRotation(rotate); + foundActor->second->updateRotation(); mTaskScheduler->updateSingleAabb(foundActor->second); } return; diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index c61b368f8..6f901067a 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -121,7 +121,7 @@ namespace MWPhysics void setWaterHeight(float height); void disableWater(); - void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType = CollisionType_World); + void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canTraverseWater); @@ -141,7 +141,7 @@ namespace MWPhysics void remove (const MWWorld::Ptr& ptr); void updateScale (const MWWorld::Ptr& ptr); - void updateRotation (const MWWorld::Ptr& ptr, osg::Quat rotate); + void updateRotation (const MWWorld::Ptr& ptr); void updatePosition (const MWWorld::Ptr& ptr); void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject); diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index becf912ea..950c8a6d4 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -25,12 +25,16 @@ namespace MWWorld { std::map > Class::sClasses; + Class::Class() {} + + Class::~Class() {} + void Class::insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const { } - void Class::insertObject(const Ptr& ptr, const std::string& mesh, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Class::insertObject(const Ptr& ptr, const std::string& mesh, MWPhysics::PhysicsSystem& physics) const { } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 39fb6fe4c..1b3d4029e 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -6,7 +6,6 @@ #include #include -#include #include #include "ptr.hpp" @@ -58,9 +57,13 @@ namespace MWWorld std::string mTypeName; + // not implemented + Class (const Class&); + Class& operator= (const Class&); + protected: - Class() = default; + Class(); std::shared_ptr defaultItemActivate(const Ptr &ptr, const Ptr &actor) const; ///< Generate default action for activating inventory items @@ -69,16 +72,14 @@ namespace MWWorld public: - virtual ~Class() = default; - Class (const Class&) = delete; - Class& operator= (const Class&) = delete; + virtual ~Class(); const std::string& getTypeName() const { return mTypeName; } virtual void insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const; - virtual void insertObject(const Ptr& ptr, const std::string& mesh, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const; + virtual void insertObject(const Ptr& ptr, const std::string& mesh, MWPhysics::PhysicsSystem& physics) const; ///< Add reference into a cell for rendering (default implementation: don't render anything). virtual std::string getName (const ConstPtr& ptr) const = 0; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 313e9a152..427ee3aa8 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -75,20 +75,18 @@ namespace * osg::Quat(xr, osg::Vec3(-1, 0, 0)); } - osg::Quat makeNodeRotation(const MWWorld::Ptr& ptr, RotationOrder order) + void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, RotationOrder order) { - const auto pos = ptr.getRefData().getPosition(); + if (!ptr.getRefData().getBaseNode()) + return; - const auto rot = ptr.getClass().isActor() ? makeActorOsgQuat(pos) - : (order == RotationOrder::inverse ? makeInversedOrderObjectOsgQuat(pos) : makeObjectOsgQuat(pos)); - - return rot; - } - - void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, osg::Quat rotation) - { - if (ptr.getRefData().getBaseNode()) - rendering.rotateObject(ptr, rotation); + rendering.rotateObject(ptr, + ptr.getClass().isActor() + ? makeActorOsgQuat(ptr.getRefData().getPosition()) + : (order == RotationOrder::inverse + ? makeInversedOrderObjectOsgQuat(ptr.getRefData().getPosition()) + : makeObjectOsgQuat(ptr.getRefData().getPosition())) + ); } std::string getModel(const MWWorld::Ptr &ptr, const VFS::Manager *vfs) @@ -119,10 +117,11 @@ namespace const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) ptr.getClass().insertObjectRendering(ptr, model, rendering); + else + ptr.getRefData().setBaseNode(new SceneUtil::PositionAttitudeTransform); // FIXME remove this when physics code is fixed not to depend on basenode + setNodeRotation(ptr, rendering, RotationOrder::direct); - const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); - setNodeRotation(ptr, rendering, rotation); - ptr.getClass().insertObject (ptr, model, rotation, physics); + ptr.getClass().insertObject (ptr, model, physics); if (useAnim) MWBase::Environment::get().getMechanicsManager()->add(ptr); @@ -277,7 +276,7 @@ namespace MWWorld { if (!ptr.getRefData().getBaseNode()) return; ptr.getClass().insertObjectRendering(ptr, getModel(ptr, mRendering.getResourceSystem()->getVFS()), mRendering); - setNodeRotation(ptr, mRendering, makeNodeRotation(ptr, RotationOrder::direct)); + setNodeRotation(ptr, mRendering, RotationOrder::direct); reloadTerrain(); } } @@ -293,9 +292,8 @@ namespace MWWorld void Scene::updateObjectRotation(const Ptr &ptr, RotationOrder order) { - const auto rot = makeNodeRotation(ptr, order); - setNodeRotation(ptr, mRendering, rot); - mPhysics->updateRotation(ptr, rot); + setNodeRotation(ptr, mRendering, order); + mPhysics->updateRotation(ptr); } void Scene::updateObjectScale(const Ptr &ptr) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ef00315cb..442672d2b 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1420,7 +1420,7 @@ namespace MWWorld mWorldScene->removeFromPagedRefs(ptr); mRendering->rotateObject(ptr, rotate); - mPhysics->updateRotation(ptr, rotate); + mPhysics->updateRotation(ptr); if (const auto object = mPhysics->getObject(ptr)) updateNavigatorObject(object); From 642ca02e3501f0d6691642b6d9476af0745d424e Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 29 Jan 2021 21:07:24 +0100 Subject: [PATCH 110/117] Shorten almost straight paths only if smooth movement is enabled; reduce angle limit for the shortening. --- apps/openmw/mwmechanics/aipackage.cpp | 4 ++-- apps/openmw/mwmechanics/pathfinding.cpp | 21 ++++++++++++--------- apps/openmw/mwmechanics/pathfinding.hpp | 2 +- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index ce5673909..da1766f4d 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -150,7 +150,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& + 1.2 * std::max(halfExtents.x(), halfExtents.y()); const float pointTolerance = std::max(MIN_TOLERANCE, actorTolerance); - mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE); + static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game"); + mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE, /*shortenIfAlmostStraight=*/smoothMovement); if (isDestReached || mPathFinder.checkPathCompleted()) // if path is finished { @@ -180,7 +181,6 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& const auto destination = mPathFinder.getPath().empty() ? dest : mPathFinder.getPath().front(); mObstacleCheck.update(actor, destination, duration); - static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game"); if (smoothMovement) { const float smoothTurnReservedDist = 150; diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 276321b81..cc28b7995 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -97,12 +97,12 @@ namespace float dotProduct = v1.x() * v3.x() + v1.y() * v3.y(); float crossProduct = v1.x() * v3.y() - v1.y() * v3.x(); - // Check that the angle between v1 and v3 is less or equal than 10 degrees. - static const float cos170 = std::cos(osg::PI / 180 * 170); - bool checkAngle = dotProduct <= cos170 * v1.length() * v3.length(); + // Check that the angle between v1 and v3 is less or equal than 5 degrees. + static const float cos175 = std::cos(osg::PI * (175.0 / 180)); + bool checkAngle = dotProduct <= cos175 * v1.length() * v3.length(); // Check that distance from p2 to the line (p1, p3) is less or equal than `pointTolerance`. - bool checkDist = std::abs(crossProduct) <= pointTolerance * (p3 - p1).length() * 2; + bool checkDist = std::abs(crossProduct) <= pointTolerance * (p3 - p1).length(); return checkAngle && checkDist; } @@ -296,7 +296,7 @@ namespace MWMechanics return getXAngleToDir(dir); } - void PathFinder::update(const osg::Vec3f& position, const float pointTolerance, const float destinationTolerance) + void PathFinder::update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, bool shortenIfAlmostStraight) { if (mPath.empty()) return; @@ -304,10 +304,13 @@ namespace MWMechanics while (mPath.size() > 1 && sqrDistanceIgnoreZ(mPath.front(), position) < pointTolerance * pointTolerance) mPath.pop_front(); - while (mPath.size() > 2 && isAlmostStraight(mPath[0], mPath[1], mPath[2], pointTolerance)) - mPath.erase(mPath.begin() + 1); - if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance)) - mPath.pop_front(); + if (shortenIfAlmostStraight) + { + while (mPath.size() > 2 && isAlmostStraight(mPath[0], mPath[1], mPath[2], pointTolerance)) + mPath.erase(mPath.begin() + 1); + if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance)) + mPath.pop_front(); + } if (mPath.size() == 1 && sqrDistanceIgnoreZ(mPath.front(), position) < destinationTolerance * destinationTolerance) mPath.pop_front(); diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 5af822fee..bd81bbfe1 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -102,7 +102,7 @@ namespace MWMechanics const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts); /// Remove front point if exist and within tolerance - void update(const osg::Vec3f& position, const float pointTolerance, const float destinationTolerance); + void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, bool shortenIfAlmostStraight); bool checkPathCompleted() const { From 9590377f224c49b62eb86ac6a8505ef023b50c9c Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 29 Jan 2021 21:27:22 +0100 Subject: [PATCH 111/117] Don't ignore Z in path finding if actor can move by Z. --- apps/openmw/mwmechanics/aipackage.cpp | 5 +++-- apps/openmw/mwmechanics/pathfinding.cpp | 15 ++++++++++++--- apps/openmw/mwmechanics/pathfinding.hpp | 3 ++- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index da1766f4d..99132b711 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -96,6 +96,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& const float distToTarget = distance(position, dest); const bool isDestReached = (distToTarget <= destTolerance); + const bool actorCanMoveByZ = canActorMoveByZAxis(actor); if (!isDestReached && mTimer > AI_REACTION_TIME) { @@ -104,7 +105,6 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& const bool wasShortcutting = mIsShortcutting; bool destInLOS = false; - const bool actorCanMoveByZ = canActorMoveByZAxis(actor); // Prohibit shortcuts for AiWander, if the actor can not move in 3 dimensions. mIsShortcutting = actorCanMoveByZ @@ -151,7 +151,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& const float pointTolerance = std::max(MIN_TOLERANCE, actorTolerance); static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game"); - mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE, /*shortenIfAlmostStraight=*/smoothMovement); + mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE, + /*shortenIfAlmostStraight=*/smoothMovement, actorCanMoveByZ); if (isDestReached || mPathFinder.checkPathCompleted()) // if path is finished { diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index cc28b7995..93ae90547 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -296,7 +296,8 @@ namespace MWMechanics return getXAngleToDir(dir); } - void PathFinder::update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, bool shortenIfAlmostStraight) + void PathFinder::update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, + bool shortenIfAlmostStraight, bool canMoveByZ) { if (mPath.empty()) return; @@ -312,8 +313,16 @@ namespace MWMechanics mPath.pop_front(); } - if (mPath.size() == 1 && sqrDistanceIgnoreZ(mPath.front(), position) < destinationTolerance * destinationTolerance) - mPath.pop_front(); + if (mPath.size() == 1) + { + float distSqr; + if (canMoveByZ) + distSqr = (mPath.front() - position).length2(); + else + distSqr = sqrDistanceIgnoreZ(mPath.front(), position); + if (distSqr < destinationTolerance * destinationTolerance) + mPath.pop_front(); + } } void PathFinder::buildStraightPath(const osg::Vec3f& endPoint) diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index bd81bbfe1..b5c376b8c 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -102,7 +102,8 @@ namespace MWMechanics const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts); /// Remove front point if exist and within tolerance - void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, bool shortenIfAlmostStraight); + void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, + bool shortenIfAlmostStraight, bool canMoveByZ); bool checkPathCompleted() const { From 157b14cdaa07395a8e2cf17c0242d57d908d3ae1 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 29 Jan 2021 22:53:02 +0100 Subject: [PATCH 112/117] Fix #5821: NPCs from mods getting removed if mod order was changed --- CHANGELOG.md | 1 + apps/openmw/mwworld/cellstore.cpp | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f347fb46e..aa74164d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -97,6 +97,7 @@ Bug #5739: Saving and loading the save a second or two before hitting the ground doesn't count fall damage Bug #5758: Paralyzed actors behavior is inconsistent with vanilla Bug #5762: Movement solver is insufficiently robust + Bug #5821: NPCs from mods getting removed if mod order was changed Feature #390: 3rd person look "over the shoulder" Feature #1536: Show more information about level on menu Feature #2386: Distant Statics in the form of Object Paging diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 3f98684ae..cc614133a 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -921,6 +921,13 @@ namespace MWWorld refnum.load(reader, true, "MVRF"); movedTo.load(reader); + if (refnum.hasContentFile()) + { + auto iter = contentFileMap.find(refnum.mContentFile); + if (iter != contentFileMap.end()) + refnum.mContentFile = iter->second; + } + // Search for the reference. It might no longer exist if its content file was removed. Ptr movedRef = searchViaRefNum(refnum); if (movedRef.isEmpty()) From 88ca4a1db6e10eeeecd0d9f97fbe61dd5b8a9411 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 4 Feb 2021 00:18:25 +0100 Subject: [PATCH 113/117] Count navmesh cache hit rate --- components/detournavigator/navmeshtilescache.cpp | 12 +++++++++++- components/detournavigator/navmeshtilescache.hpp | 2 ++ components/resource/stats.cpp | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp index cff93ac0e..1b90ea89e 100644 --- a/components/detournavigator/navmeshtilescache.cpp +++ b/components/detournavigator/navmeshtilescache.cpp @@ -53,13 +53,16 @@ namespace DetourNavigator } NavMeshTilesCache::NavMeshTilesCache(const std::size_t maxNavMeshDataSize) - : mMaxNavMeshDataSize(maxNavMeshDataSize), mUsedNavMeshDataSize(0), mFreeNavMeshDataSize(0) {} + : mMaxNavMeshDataSize(maxNavMeshDataSize), mUsedNavMeshDataSize(0), mFreeNavMeshDataSize(0), + mHitCount(0), mGetCount(0){} NavMeshTilesCache::Value NavMeshTilesCache::get(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, const RecastMesh& recastMesh, const std::vector& offMeshConnections) { const std::lock_guard lock(mMutex); + ++mGetCount; + const auto agentValues = mValues.find(agentHalfExtents); if (agentValues == mValues.end()) return Value(); @@ -74,6 +77,8 @@ namespace DetourNavigator acquireItemUnsafe(tile->second); + ++mHitCount; + return Value(*this, tile->second); } @@ -123,17 +128,22 @@ namespace DetourNavigator std::size_t navMeshCacheSize = 0; std::size_t usedNavMeshTiles = 0; std::size_t cachedNavMeshTiles = 0; + std::size_t hitCount = 0; + std::size_t getCount = 0; { const std::lock_guard lock(mMutex); navMeshCacheSize = mUsedNavMeshDataSize; usedNavMeshTiles = mBusyItems.size(); cachedNavMeshTiles = mFreeItems.size(); + hitCount = mHitCount; + getCount = mGetCount; } stats.setAttribute(frameNumber, "NavMesh CacheSize", navMeshCacheSize); stats.setAttribute(frameNumber, "NavMesh UsedTiles", usedNavMeshTiles); stats.setAttribute(frameNumber, "NavMesh CachedTiles", cachedNavMeshTiles); + stats.setAttribute(frameNumber, "NavMesh CacheHitRate", static_cast(hitCount) / getCount * 100.0); } void NavMeshTilesCache::removeLeastRecentlyUsed() diff --git a/components/detournavigator/navmeshtilescache.hpp b/components/detournavigator/navmeshtilescache.hpp index 064d9e185..a6dd1ed9a 100644 --- a/components/detournavigator/navmeshtilescache.hpp +++ b/components/detournavigator/navmeshtilescache.hpp @@ -194,6 +194,8 @@ namespace DetourNavigator std::size_t mMaxNavMeshDataSize; std::size_t mUsedNavMeshDataSize; std::size_t mFreeNavMeshDataSize; + std::size_t mHitCount; + std::size_t mGetCount; std::list mBusyItems; std::list mFreeItems; std::map> mValues; diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 3649af089..690814f91 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -394,6 +394,7 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) "NavMesh CacheSize", "NavMesh UsedTiles", "NavMesh CachedTiles", + "NavMesh CacheHitRate", "", "Mechanics Actors", "Mechanics Objects", From 489107c5eed4de2ce0cef3a88d1cedf0cb811311 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 4 Feb 2021 00:44:15 +0100 Subject: [PATCH 114/117] Count navmesh cache key once in item size Key is stored only in NavMeshTilesCache::Item, TileMap uses KeyView with a pointer to a vector. --- .../detournavigator/navmeshtilescache.cpp | 22 +++++++++---------- .../detournavigator/navmeshtilescache.cpp | 2 +- .../detournavigator/navmeshtilescache.hpp | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp index e8e7820d9..5bc7af646 100644 --- a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp +++ b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp @@ -68,7 +68,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; - const std::size_t maxSize = navMeshDataSize + 2 * navMeshKeySize; + const std::size_t maxSize = navMeshDataSize + navMeshKeySize; NavMeshTilesCache cache(maxSize); const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, @@ -81,7 +81,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; - const std::size_t maxSize = 2 * (navMeshDataSize + 2 * navMeshKeySize); + const std::size_t maxSize = 2 * (navMeshDataSize + navMeshKeySize); NavMeshTilesCache cache(maxSize); const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData anotherNavMeshData {anotherData, 1}; @@ -97,7 +97,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; - const std::size_t maxSize = navMeshDataSize + 2 * navMeshKeySize; + const std::size_t maxSize = navMeshDataSize + navMeshKeySize; NavMeshTilesCache cache(maxSize); cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); @@ -142,7 +142,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshWithWaterKeySize; - const std::size_t maxSize = navMeshDataSize + 2 * navMeshKeySize; + const std::size_t maxSize = navMeshDataSize + navMeshKeySize; NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; @@ -163,7 +163,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; - const std::size_t maxSize = navMeshDataSize + 2 * navMeshKeySize; + const std::size_t maxSize = navMeshDataSize + navMeshKeySize; NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; @@ -182,7 +182,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshWithWaterKeySize; - const std::size_t maxSize = 2 * (navMeshDataSize + 2 * navMeshKeySize); + const std::size_t maxSize = 2 * (navMeshDataSize + navMeshKeySize); NavMeshTilesCache cache(maxSize); const std::vector leastRecentlySetWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; @@ -214,7 +214,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshWithWaterKeySize; - const std::size_t maxSize = 2 * (navMeshDataSize + 2 * navMeshKeySize); + const std::size_t maxSize = 2 * (navMeshDataSize + navMeshKeySize); NavMeshTilesCache cache(maxSize); const std::vector leastRecentlyUsedWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; @@ -258,7 +258,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; - const std::size_t maxSize = 2 * (navMeshDataSize + 2 * navMeshKeySize); + const std::size_t maxSize = 2 * (navMeshDataSize + navMeshKeySize); NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; @@ -277,7 +277,7 @@ namespace const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize1 = cRecastMeshKeySize; const std::size_t navMeshKeySize2 = cRecastMeshWithWaterKeySize; - const std::size_t maxSize = 2 * navMeshDataSize + 2 * navMeshKeySize1 + 2 * navMeshKeySize2; + const std::size_t maxSize = 2 * navMeshDataSize + navMeshKeySize1 + navMeshKeySize2; NavMeshTilesCache cache(maxSize); const std::vector anotherWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; @@ -306,7 +306,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; - const std::size_t maxSize = navMeshDataSize + 2 * navMeshKeySize; + const std::size_t maxSize = navMeshDataSize + navMeshKeySize; NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; @@ -330,7 +330,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; - const std::size_t maxSize = navMeshDataSize + 2 * navMeshKeySize; + const std::size_t maxSize = navMeshDataSize + navMeshKeySize; NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp index 1b90ea89e..d9879fcbb 100644 --- a/components/detournavigator/navmeshtilescache.cpp +++ b/components/detournavigator/navmeshtilescache.cpp @@ -97,7 +97,7 @@ namespace DetourNavigator return Value(); auto navMeshKey = makeNavMeshKey(recastMesh, offMeshConnections); - const auto itemSize = navMeshSize + 2 * navMeshKey.size(); + const auto itemSize = navMeshSize + navMeshKey.size(); if (itemSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) return Value(); diff --git a/components/detournavigator/navmeshtilescache.hpp b/components/detournavigator/navmeshtilescache.hpp index a6dd1ed9a..338ead3aa 100644 --- a/components/detournavigator/navmeshtilescache.hpp +++ b/components/detournavigator/navmeshtilescache.hpp @@ -208,7 +208,7 @@ namespace DetourNavigator static std::size_t getSize(const Item& item) { - return static_cast(item.mNavMeshData.mSize) + 2 * item.mNavMeshKey.size(); + return static_cast(item.mNavMeshData.mSize) + item.mNavMeshKey.size(); } }; } From ad1f8c1e8445acdb2f40f7fcee767b587e5df11c Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 4 Feb 2021 00:14:29 +0100 Subject: [PATCH 115/117] Sort water and off mesh connections for recast mesh Inconsisten order of these objects in navmesh cache key leads to cache misses due to key inequality. --- components/bullethelpers/operators.hpp | 15 +++++++++++++++ components/detournavigator/offmeshconnection.hpp | 7 +++++++ .../detournavigator/offmeshconnectionsmanager.hpp | 2 ++ components/detournavigator/recastmesh.hpp | 8 ++++++++ components/detournavigator/recastmeshbuilder.cpp | 1 + 5 files changed, 33 insertions(+) diff --git a/components/bullethelpers/operators.hpp b/components/bullethelpers/operators.hpp index ea88deddf..dd2ec8017 100644 --- a/components/bullethelpers/operators.hpp +++ b/components/bullethelpers/operators.hpp @@ -68,4 +68,19 @@ inline std::ostream& operator <<(std::ostream& stream, BroadphaseNativeTypes val } } +inline bool operator <(const btVector3& lhs, const btVector3& rhs) +{ + return std::tie(lhs.x(), lhs.y(), lhs.z()) < std::tie(rhs.x(), rhs.y(), rhs.z()); +} + +inline bool operator <(const btMatrix3x3& lhs, const btMatrix3x3& rhs) +{ + return std::tie(lhs[0], lhs[1], lhs[2]) < std::tie(rhs[0], rhs[1], rhs[2]); +} + +inline bool operator <(const btTransform& lhs, const btTransform& rhs) +{ + return std::tie(lhs.getBasis(), lhs.getOrigin()) < std::tie(rhs.getBasis(), rhs.getOrigin()); +} + #endif diff --git a/components/detournavigator/offmeshconnection.hpp b/components/detournavigator/offmeshconnection.hpp index ca999dbdb..01bae0273 100644 --- a/components/detournavigator/offmeshconnection.hpp +++ b/components/detournavigator/offmeshconnection.hpp @@ -5,6 +5,8 @@ #include +#include + namespace DetourNavigator { struct OffMeshConnection @@ -13,6 +15,11 @@ namespace DetourNavigator osg::Vec3f mEnd; AreaType mAreaType; }; + + inline bool operator<(const OffMeshConnection& lhs, const OffMeshConnection& rhs) + { + return std::tie(lhs.mStart, lhs.mEnd, lhs.mAreaType) < std::tie(rhs.mStart, rhs.mEnd, rhs.mAreaType); + } } #endif diff --git a/components/detournavigator/offmeshconnectionsmanager.hpp b/components/detournavigator/offmeshconnectionsmanager.hpp index de707f3a8..1ad96e3b9 100644 --- a/components/detournavigator/offmeshconnectionsmanager.hpp +++ b/components/detournavigator/offmeshconnectionsmanager.hpp @@ -85,6 +85,8 @@ namespace DetourNavigator std::for_each(byId.first, byId.second, [&] (const auto& v) { result.push_back(v.second); }); }); + std::sort(result.begin(), result.end()); + return result; } diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index f3259903f..29f37822e 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -5,9 +5,12 @@ #include "chunkytrimesh.hpp" #include "bounds.hpp" +#include + #include #include #include +#include #include @@ -87,6 +90,11 @@ namespace DetourNavigator ChunkyTriMesh mChunkyTriMesh; Bounds mBounds; }; + + inline bool operator<(const RecastMesh::Water& lhs, const RecastMesh::Water& rhs) + { + return std::tie(lhs.mCellSize, lhs.mTransform) < std::tie(rhs.mCellSize, rhs.mTransform); + } } #endif diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index ee014b932..f8456acf0 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -155,6 +155,7 @@ namespace DetourNavigator std::shared_ptr RecastMeshBuilder::create(std::size_t generation, std::size_t revision) { optimizeRecastMesh(mIndices, mVertices); + std::sort(mWater.begin(), mWater.end()); return std::make_shared(generation, revision, mIndices, mVertices, mAreaTypes, mWater, mSettings.get().mTrianglesPerChunk); } From 3a2cea52714b26c69dfd89672dd1ad6a9886acac Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 4 Feb 2021 00:15:54 +0100 Subject: [PATCH 116/117] Use raw recast mesh data and off mesh connections for navmesh key Serialization into a vector of chars produces inconsistent results that leads to reduced cache hit rate. Using a structured object is a more clear solution and allows to remove serialization and nontrivial key compare logic with more straigt forward structured object comparison. --- .../detournavigator/navmeshtilescache.cpp | 123 ++----------- .../detournavigator/navmeshtilescache.hpp | 168 ++++++++++-------- components/detournavigator/recastmesh.hpp | 6 + 3 files changed, 110 insertions(+), 187 deletions(-) diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp index d9879fcbb..84c658653 100644 --- a/components/detournavigator/navmeshtilescache.cpp +++ b/components/detournavigator/navmeshtilescache.cpp @@ -9,52 +9,21 @@ namespace DetourNavigator { namespace { - inline std::vector makeNavMeshKey(const RecastMesh& recastMesh, - const std::vector& offMeshConnections) + inline std::size_t getSize(const RecastMesh& recastMesh, + const std::vector& offMeshConnections) { const std::size_t indicesSize = recastMesh.getIndices().size() * sizeof(int); const std::size_t verticesSize = recastMesh.getVertices().size() * sizeof(float); const std::size_t areaTypesSize = recastMesh.getAreaTypes().size() * sizeof(AreaType); const std::size_t waterSize = recastMesh.getWater().size() * sizeof(RecastMesh::Water); const std::size_t offMeshConnectionsSize = offMeshConnections.size() * sizeof(OffMeshConnection); - - std::vector result(indicesSize + verticesSize + areaTypesSize + waterSize + offMeshConnectionsSize); - unsigned char* dst = result.data(); - - if (indicesSize > 0) - { - std::memcpy(dst, recastMesh.getIndices().data(), indicesSize); - dst += indicesSize; - } - - if (verticesSize > 0) - { - std::memcpy(dst, recastMesh.getVertices().data(), verticesSize); - dst += verticesSize; - } - - if (areaTypesSize > 0) - { - std::memcpy(dst, recastMesh.getAreaTypes().data(), areaTypesSize); - dst += areaTypesSize; - } - - if (waterSize > 0) - { - std::memcpy(dst, recastMesh.getWater().data(), waterSize); - dst += waterSize; - } - - if (offMeshConnectionsSize > 0) - std::memcpy(dst, offMeshConnections.data(), offMeshConnectionsSize); - - return result; + return indicesSize + verticesSize + areaTypesSize + waterSize + offMeshConnectionsSize; } } NavMeshTilesCache::NavMeshTilesCache(const std::size_t maxNavMeshDataSize) : mMaxNavMeshDataSize(maxNavMeshDataSize), mUsedNavMeshDataSize(0), mFreeNavMeshDataSize(0), - mHitCount(0), mGetCount(0){} + mHitCount(0), mGetCount(0) {} NavMeshTilesCache::Value NavMeshTilesCache::get(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, const RecastMesh& recastMesh, const std::vector& offMeshConnections) @@ -71,7 +40,7 @@ namespace DetourNavigator if (tileValues == agentValues->second.end()) return Value(); - const auto tile = tileValues->second.mMap.find(RecastMeshKeyView(recastMesh, offMeshConnections)); + const auto tile = tileValues->second.mMap.find(NavMeshKeyView(recastMesh, offMeshConnections)); if (tile == tileValues->second.mMap.end()) return Value(); @@ -96,8 +65,11 @@ namespace DetourNavigator if (navMeshSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) return Value(); - auto navMeshKey = makeNavMeshKey(recastMesh, offMeshConnections); - const auto itemSize = navMeshSize + navMeshKey.size(); + NavMeshKey navMeshKey { + RecastMeshData {recastMesh.getIndices(), recastMesh.getVertices(), recastMesh.getAreaTypes(), recastMesh.getWater()}, + offMeshConnections + }; + const auto itemSize = navMeshSize + getSize(recastMesh, offMeshConnections); if (itemSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) return Value(); @@ -105,7 +77,7 @@ namespace DetourNavigator while (!mFreeItems.empty() && mUsedNavMeshDataSize + itemSize > mMaxNavMeshDataSize) removeLeastRecentlyUsed(); - const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, std::move(navMeshKey)); + const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, std::move(navMeshKey), itemSize); const auto emplaced = mValues[agentHalfExtents][changedTile].mMap.emplace(iterator->mNavMeshKey, iterator); if (!emplaced.second) @@ -162,8 +134,8 @@ namespace DetourNavigator if (value == tileValues->second.mMap.end()) return; - mUsedNavMeshDataSize -= getSize(item); - mFreeNavMeshDataSize -= getSize(item); + mUsedNavMeshDataSize -= item.mSize; + mFreeNavMeshDataSize -= item.mSize; tileValues->second.mMap.erase(value); mFreeItems.pop_back(); @@ -184,7 +156,7 @@ namespace DetourNavigator return; mBusyItems.splice(mBusyItems.end(), mFreeItems, iterator); - mFreeNavMeshDataSize -= getSize(*iterator); + mFreeNavMeshDataSize -= iterator->mSize; } void NavMeshTilesCache::releaseItem(ItemIterator iterator) @@ -195,71 +167,6 @@ namespace DetourNavigator const std::lock_guard lock(mMutex); mFreeItems.splice(mFreeItems.begin(), mBusyItems, iterator); - mFreeNavMeshDataSize += getSize(*iterator); - } - - namespace - { - struct CompareBytes - { - const unsigned char* mRhsIt; - const unsigned char* const mRhsEnd; - - template - int operator ()(const std::vector& lhs) - { - const auto lhsBegin = reinterpret_cast(lhs.data()); - const auto lhsEnd = reinterpret_cast(lhs.data() + lhs.size()); - const auto lhsSize = static_cast(lhsEnd - lhsBegin); - const auto rhsSize = static_cast(mRhsEnd - mRhsIt); - - if (lhsBegin == nullptr || mRhsIt == nullptr) - { - if (lhsSize < rhsSize) - return -1; - else if (lhsSize > rhsSize) - return 1; - else - return 0; - } - - const auto size = std::min(lhsSize, rhsSize); - - if (const auto result = std::memcmp(lhsBegin, mRhsIt, size)) - return result; - - if (lhsSize > rhsSize) - return 1; - - mRhsIt += size; - - return 0; - } - }; - } - - int NavMeshTilesCache::RecastMeshKeyView::compare(const std::vector& other) const - { - CompareBytes compareBytes {other.data(), other.data() + other.size()}; - - if (const auto result = compareBytes(mRecastMesh.get().getIndices())) - return result; - - if (const auto result = compareBytes(mRecastMesh.get().getVertices())) - return result; - - if (const auto result = compareBytes(mRecastMesh.get().getAreaTypes())) - return result; - - if (const auto result = compareBytes(mRecastMesh.get().getWater())) - return result; - - if (const auto result = compareBytes(mOffMeshConnections.get())) - return result; - - if (compareBytes.mRhsIt < compareBytes.mRhsEnd) - return -1; - - return 0; + mFreeNavMeshDataSize += iterator->mSize; } } diff --git a/components/detournavigator/navmeshtilescache.hpp b/components/detournavigator/navmeshtilescache.hpp index 338ead3aa..25f4dc187 100644 --- a/components/detournavigator/navmeshtilescache.hpp +++ b/components/detournavigator/navmeshtilescache.hpp @@ -27,6 +27,89 @@ namespace DetourNavigator int mSize; }; + struct RecastMeshData + { + std::vector mIndices; + std::vector mVertices; + std::vector mAreaTypes; + std::vector mWater; + }; + + inline bool operator <(const RecastMeshData& lhs, const RecastMeshData& rhs) + { + return std::tie(lhs.mIndices, lhs.mVertices, lhs.mAreaTypes, lhs.mWater) + < std::tie(rhs.mIndices, rhs.mVertices, rhs.mAreaTypes, rhs.mWater); + } + + inline bool operator <(const RecastMeshData& lhs, const RecastMesh& rhs) + { + return std::tie(lhs.mIndices, lhs.mVertices, lhs.mAreaTypes, lhs.mWater) + < std::tie(rhs.getIndices(), rhs.getVertices(), rhs.getAreaTypes(), rhs.getWater()); + } + + inline bool operator <(const RecastMesh& lhs, const RecastMeshData& rhs) + { + return std::tie(lhs.getIndices(), lhs.getVertices(), lhs.getAreaTypes(), lhs.getWater()) + < std::tie(rhs.mIndices, rhs.mVertices, rhs.mAreaTypes, rhs.mWater); + } + + struct NavMeshKey + { + RecastMeshData mRecastMesh; + std::vector mOffMeshConnections; + }; + + inline bool operator <(const NavMeshKey& lhs, const NavMeshKey& rhs) + { + return std::tie(lhs.mRecastMesh, lhs.mOffMeshConnections) + < std::tie(rhs.mRecastMesh, rhs.mOffMeshConnections); + } + + struct NavMeshKeyRef + { + std::reference_wrapper mRef; + + explicit NavMeshKeyRef(const NavMeshKey& ref) : mRef(ref) {} + }; + + inline bool operator <(const NavMeshKeyRef& lhs, const NavMeshKeyRef& rhs) + { + return lhs.mRef.get() < rhs.mRef.get(); + } + + struct NavMeshKeyView + { + std::reference_wrapper mRecastMesh; + std::reference_wrapper> mOffMeshConnections; + + NavMeshKeyView(const RecastMesh& recastMesh, const std::vector& offMeshConnections) + : mRecastMesh(recastMesh), mOffMeshConnections(offMeshConnections) {} + }; + + inline bool operator <(const NavMeshKeyView& lhs, const NavMeshKey& rhs) + { + return std::tie(lhs.mRecastMesh.get(), lhs.mOffMeshConnections.get()) + < std::tie(rhs.mRecastMesh, rhs.mOffMeshConnections); + } + + inline bool operator <(const NavMeshKey& lhs, const NavMeshKeyView& rhs) + { + return std::tie(lhs.mRecastMesh, lhs.mOffMeshConnections) + < std::tie(rhs.mRecastMesh.get(), rhs.mOffMeshConnections.get()); + } + + template + inline bool operator <(const NavMeshKeyRef& lhs, const R& rhs) + { + return lhs.mRef.get() < rhs; + } + + template + inline bool operator <(const L& lhs, const NavMeshKeyRef& rhs) + { + return lhs < rhs.mRef.get(); + } + class NavMeshTilesCache { public: @@ -35,14 +118,16 @@ namespace DetourNavigator std::atomic mUseCount; osg::Vec3f mAgentHalfExtents; TilePosition mChangedTile; - std::vector mNavMeshKey; + NavMeshKey mNavMeshKey; NavMeshData mNavMeshData; + std::size_t mSize; - Item(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, std::vector&& navMeshKey) + Item(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, NavMeshKey&& navMeshKey, std::size_t size) : mUseCount(0) , mAgentHalfExtents(agentHalfExtents) , mChangedTile(changedTile) - , mNavMeshKey(std::move(navMeshKey)) + , mNavMeshKey(navMeshKey) + , mSize(size) {} }; @@ -115,79 +200,9 @@ namespace DetourNavigator void reportStats(unsigned int frameNumber, osg::Stats& stats) const; private: - class KeyView - { - public: - KeyView() = default; - - virtual ~KeyView() = default; - - KeyView(const std::vector& value) - : mValue(&value) {} - - const std::vector& getValue() const - { - assert(mValue); - return *mValue; - } - - virtual int compare(const std::vector& other) const - { - assert(mValue); - - const auto valueSize = mValue->size(); - const auto otherSize = other.size(); - - if (const auto result = std::memcmp(mValue->data(), other.data(), std::min(valueSize, otherSize))) - return result; - - if (valueSize < otherSize) - return -1; - - if (valueSize > otherSize) - return 1; - - return 0; - } - - virtual bool isLess(const KeyView& other) const - { - assert(mValue); - return other.compare(*mValue) > 0; - } - - friend bool operator <(const KeyView& lhs, const KeyView& rhs) - { - return lhs.isLess(rhs); - } - - private: - const std::vector* mValue = nullptr; - }; - - class RecastMeshKeyView : public KeyView - { - public: - RecastMeshKeyView(const RecastMesh& recastMesh, const std::vector& offMeshConnections) - : mRecastMesh(recastMesh), mOffMeshConnections(offMeshConnections) {} - - int compare(const std::vector& other) const override; - - bool isLess(const KeyView& other) const override - { - return compare(other.getValue()) < 0; - } - - virtual ~RecastMeshKeyView() = default; - - private: - std::reference_wrapper mRecastMesh; - std::reference_wrapper> mOffMeshConnections; - }; - struct TileMap { - std::map mMap; + std::map> mMap; }; mutable std::mutex mMutex; @@ -205,11 +220,6 @@ namespace DetourNavigator void acquireItemUnsafe(ItemIterator iterator); void releaseItem(ItemIterator iterator); - - static std::size_t getSize(const Item& item) - { - return static_cast(item.mNavMeshData.mSize) + item.mNavMeshKey.size(); - } }; } diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index 29f37822e..746422ac8 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -95,6 +95,12 @@ namespace DetourNavigator { return std::tie(lhs.mCellSize, lhs.mTransform) < std::tie(rhs.mCellSize, rhs.mTransform); } + + inline bool operator <(const RecastMesh& lhs, const RecastMesh& rhs) + { + return std::tie(lhs.getIndices(), lhs.getVertices(), lhs.getAreaTypes(), lhs.getWater()) + < std::tie(rhs.getIndices(), rhs.getVertices(), rhs.getAreaTypes(), rhs.getWater()); + } } #endif From 68fe6b91145350c12487332cc26483f032f2ec64 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 4 Feb 2021 01:12:52 +0100 Subject: [PATCH 117/117] Use only item size to check whether item fits cache Item size has to be counted anyway and there is no reason to check only navmesh data first. --- .../detournavigator/navmeshtilescache.cpp | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp index 84c658653..b6048da58 100644 --- a/components/detournavigator/navmeshtilescache.cpp +++ b/components/detournavigator/navmeshtilescache.cpp @@ -55,28 +55,21 @@ namespace DetourNavigator const RecastMesh& recastMesh, const std::vector& offMeshConnections, NavMeshData&& value) { - const auto navMeshSize = static_cast(value.mSize); + const auto itemSize = static_cast(value.mSize) + getSize(recastMesh, offMeshConnections); const std::lock_guard lock(mMutex); - if (navMeshSize > mMaxNavMeshDataSize) - return Value(); - - if (navMeshSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) - return Value(); - - NavMeshKey navMeshKey { - RecastMeshData {recastMesh.getIndices(), recastMesh.getVertices(), recastMesh.getAreaTypes(), recastMesh.getWater()}, - offMeshConnections - }; - const auto itemSize = navMeshSize + getSize(recastMesh, offMeshConnections); - if (itemSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) return Value(); while (!mFreeItems.empty() && mUsedNavMeshDataSize + itemSize > mMaxNavMeshDataSize) removeLeastRecentlyUsed(); + NavMeshKey navMeshKey { + RecastMeshData {recastMesh.getIndices(), recastMesh.getVertices(), recastMesh.getAreaTypes(), recastMesh.getWater()}, + offMeshConnections + }; + const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, std::move(navMeshKey), itemSize); const auto emplaced = mValues[agentHalfExtents][changedTile].mMap.emplace(iterator->mNavMeshKey, iterator);