From 36cea2073f598cc9768f88419a9e4d14b68c0682 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sat, 13 Mar 2021 12:10:14 +0000 Subject: [PATCH 01/49] Update MyGUI Includes https://github.com/MyGUI/mygui/commit/f01cba4bb392ae990893442f5bcdf05bfc7a1656 Fixes https://gitlab.com/OpenMW/openmw/-/issues/5897 --- extern/CMakeLists.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index bf18e4136f..7e9f189fea 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -63,10 +63,11 @@ if(NOT OPENMW_USE_SYSTEM_MYGUI) set(BUILD_SHARED_LIBS ON) endif() + # master on 13 Mar 2021 include(FetchContent) FetchContent_Declare(mygui - URL https://github.com/MyGUI/mygui/archive/MyGUI3.4.1.zip - URL_HASH MD5=952d4033854612c99a5d9bf4b8550c26 + URL https://github.com/MyGUI/mygui/archive/59c1388b942721887d18743ada15f1906ff11a1f.zip + URL_HASH MD5=0a64c9cccc8f96dc8c08172175e68e1c SOURCE_DIR fetched/mygui ) FetchContent_MakeAvailableExcludeFromAll(mygui) From e30a59772c6d18f1953b0e4b6afc55be439e7c04 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 8 Jan 2021 22:18:37 +0100 Subject: [PATCH 02/49] Remove DEFAULT_DECODER macros --- apps/openmw/mwsound/ffmpeg_decoder.hpp | 6 ++---- apps/openmw/mwsound/soundmanagerimp.cpp | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index 92d046d85f..f099c831ca 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -69,15 +69,13 @@ namespace MWSound FFmpeg_Decoder& operator=(const FFmpeg_Decoder &rhs); FFmpeg_Decoder(const FFmpeg_Decoder &rhs); - FFmpeg_Decoder(const VFS::Manager* vfs); public: + explicit FFmpeg_Decoder(const VFS::Manager* vfs); + virtual ~FFmpeg_Decoder(); friend class SoundManager; }; -#ifndef DEFAULT_DECODER -#define DEFAULT_DECODER (::MWSound::FFmpeg_Decoder) -#endif } #endif diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 44465e5a76..740167b0ac 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -117,7 +117,7 @@ namespace MWSound // Return a new decoder instance, used as needed by the output implementations DecoderPtr SoundManager::getDecoder() { - return DecoderPtr(new DEFAULT_DECODER (mVFS)); + return std::make_shared(mVFS); } DecoderPtr SoundManager::loadVoice(const std::string &voicefile) From b0311ce9f1ed2c562fac6f8b99127dfbf8a79cf8 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 26 Jan 2021 00:48:45 +0100 Subject: [PATCH 03/49] Remove DEFAULT_OUTPUT macros --- apps/openmw/mwsound/openal_output.hpp | 3 --- apps/openmw/mwsound/soundmanagerimp.cpp | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index d9ca924a78..2a19e6768a 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -98,9 +98,6 @@ namespace MWSound OpenAL_Output(SoundManager &mgr); virtual ~OpenAL_Output(); }; -#ifndef DEFAULT_OUTPUT -#define DEFAULT_OUTPUT(x) ::MWSound::OpenAL_Output((x)) -#endif } #endif diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 740167b0ac..6f029d0f6d 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -54,7 +54,7 @@ namespace MWSound SoundManager::SoundManager(const VFS::Manager* vfs, bool useSound) : mVFS(vfs) - , mOutput(new DEFAULT_OUTPUT(*this)) + , mOutput(new OpenAL_Output(*this)) , mWaterSoundUpdater(makeWaterSoundUpdaterSettings()) , mSoundBuffers(*vfs, *mOutput) , mListenerUnderwater(false) From 9275dd2dcb6499ee23aba7f6ec056552352034aa Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 24 Jan 2021 21:53:23 +0100 Subject: [PATCH 04/49] Avoid virtual dispatch in SoundManager dtor --- apps/openmw/mwsound/soundmanagerimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 6f029d0f6d..fc9735d576 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -109,7 +109,7 @@ namespace MWSound SoundManager::~SoundManager() { - clear(); + SoundManager::clear(); mSoundBuffers.clear(); mOutput.reset(); } From c2580d60e9cc398b4b7a8290e6830a7531c17a57 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 14 Mar 2021 19:32:03 +0100 Subject: [PATCH 05/49] Register copied Spells with SpellList --- apps/openmw/mwmechanics/spells.cpp | 8 ++++++++ apps/openmw/mwmechanics/spells.hpp | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index d292c015d6..0af74e01bd 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -24,6 +24,14 @@ namespace MWMechanics { } + Spells::Spells(const Spells& spells) : mSpellList(spells.mSpellList), mSpells(spells.mSpells), + mSelectedSpell(spells.mSelectedSpell), mUsedPowers(spells.mUsedPowers), + mSpellsChanged(spells.mSpellsChanged), mEffects(spells.mEffects), mSourcedEffects(spells.mSourcedEffects) + { + if(mSpellList) + mSpellList->addListener(this); + } + std::map::const_iterator Spells::begin() const { return mSpells.begin(); diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index 2f4049d2e0..3df89a5372 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -57,6 +57,10 @@ namespace MWMechanics Spells(); + Spells(const Spells&); + + Spells(const Spells&&) = delete; + ~Spells(); static bool hasCorprusEffect(const ESM::Spell *spell); From 952b31ac5c60c576dcbe3a17e44aaa21364dcdee Mon Sep 17 00:00:00 2001 From: Dobrohotov Alexei Date: Mon, 15 Mar 2021 00:43:47 +0300 Subject: [PATCH 06/49] NiZBufferProperty: handle depth test flag (bug #5902) --- CHANGELOG.md | 1 + components/nifosg/nifloader.cpp | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index face13d193..02dc9dc968 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -109,6 +109,7 @@ Bug #5871: The console appears if you type the Russian letter "Ё" in the name of the enchantment Bug #5877: Effects appearing with empty icon Bug #5899: Visible modal windows and dropdowns crashing game on exit + Bug #5902: NiZBufferProperty is unable to disable the depth test Feature #390: 3rd person look "over the shoulder" Feature #832: OpenMW-CS: Handle deleted references Feature #1536: Show more information about level on menu diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 902f15fb35..3e34969f4a 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1725,11 +1725,16 @@ namespace NifOsg case Nif::RC_NiZBufferProperty: { const Nif::NiZBufferProperty* zprop = static_cast(property); - // VER_MW doesn't support a DepthFunction according to NifSkope + osg::StateSet* stateset = node->getOrCreateStateSet(); + // Depth test flag + stateset->setMode(GL_DEPTH_TEST, zprop->flags&1 ? osg::StateAttribute::ON + : osg::StateAttribute::OFF); osg::ref_ptr depth = new osg::Depth; + // Depth write flag depth->setWriteMask((zprop->flags>>1)&1); + // Morrowind ignores depth test function depth = shareAttribute(depth); - node->getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); break; } // OSG groups the material properties that NIFs have separate, so we have to parse them all again when one changed From ba74fbf30ed8709f973c05511adc1b4234af71db Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 15 Mar 2021 22:35:13 +0000 Subject: [PATCH 07/49] Fix MyGUI log Also actually print an error to the regular log when the MyGUI log can't be opened so we notice if we kill it again in under five years. --- apps/openmw/mwgui/windowmanagerimp.cpp | 2 +- components/myguiplatform/myguiloglistener.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 62b3d32d04..8e4ea4e54f 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -189,7 +189,7 @@ namespace MWGui { float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); mGuiPlatform = new osgMyGUI::Platform(viewer, guiRoot, resourceSystem->getImageManager(), uiScale); - mGuiPlatform->initialise(resourcePath, logpath); + mGuiPlatform->initialise(resourcePath, (boost::filesystem::path(logpath) / "MyGUI.log").generic_string()); mGui = new MyGUI::Gui; mGui->initialise(""); diff --git a/components/myguiplatform/myguiloglistener.cpp b/components/myguiplatform/myguiloglistener.cpp index a22ac8fc5d..74b4b30813 100644 --- a/components/myguiplatform/myguiloglistener.cpp +++ b/components/myguiplatform/myguiloglistener.cpp @@ -2,11 +2,15 @@ #include +#include + namespace osgMyGUI { void CustomLogListener::open() { mStream.open(boost::filesystem::path(mFileName), std::ios_base::out); + if (!mStream.is_open()) + Log(Debug::Error) << "Unable to create MyGUI log with path " << mFileName; } void CustomLogListener::close() From f460ab215298d611e7d9438df6b01175f58eb072 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Tue, 16 Mar 2021 01:34:27 +0000 Subject: [PATCH 08/49] MSVC: Fix build with vcpkg's boost boost-zlib is not present (nor needed) in vcpkg version of boost There, it is part of boost-iostreams instead. This was previously reported in: https://gitlab.com/OpenMW/openmw/-/merge_requests/213#note_348625016 --- CMakeLists.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fa11942050..ad35d1b0e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -344,7 +344,9 @@ set(BOOST_COMPONENTS system filesystem program_options iostreams) if(WIN32) set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale) if(MSVC) - set(BOOST_COMPONENTS ${BOOST_COMPONENTS} zlib) + # boost-zlib is not present (nor needed) in vcpkg version of boost. + # there, it is part of boost-iostreams instead. + set(BOOST_OPTIONAL_COMPONENTS zlib) endif(MSVC) endif(WIN32) @@ -354,7 +356,7 @@ endif() set(Boost_NO_BOOST_CMAKE ON) -find_package(Boost 1.6.2 REQUIRED COMPONENTS ${BOOST_COMPONENTS}) +find_package(Boost 1.6.2 REQUIRED COMPONENTS ${BOOST_COMPONENTS} OPTIONAL_COMPONENTS ${BOOST_OPTIONAL_COMPONENTS}) if(OPENMW_USE_SYSTEM_MYGUI) find_package(MyGUI 3.2.2 REQUIRED) endif() From c68e047f19b4f9f5973af5f9b1c3db80b932eead Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 16 Mar 2021 13:21:35 +0400 Subject: [PATCH 09/49] Remove invalid MyGUI properties from layout files --- files/mygui/openmw_settings_window.layout | 1 - files/mygui/openmw_tooltips.layout | 5 ----- 2 files changed, 6 deletions(-) diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index b57d362ed0..babb5c28f9 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -302,7 +302,6 @@ - diff --git a/files/mygui/openmw_tooltips.layout b/files/mygui/openmw_tooltips.layout index bb505d2539..ce927038c2 100644 --- a/files/mygui/openmw_tooltips.layout +++ b/files/mygui/openmw_tooltips.layout @@ -193,7 +193,6 @@ - @@ -248,7 +247,6 @@ - @@ -269,9 +267,6 @@ - - - From 493659d4f93f8490be66918fdbc8154d96902810 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Tue, 16 Mar 2021 02:56:33 +0000 Subject: [PATCH 10/49] MSVC: extern/ tweaks to make it build Not everything is supported but it does build with the following CMakeSettings.json variables and dependencies from vcpkg: "variables": [ { "name": "OPENMW_USE_SYSTEM_BULLET", "value": "False", "type": "BOOL" }, { "name": "OPENMW_USE_SYSTEM_MYGUI", "value": "False", "type": "BOOL" }, { "name": "OPENMW_USE_SYSTEM_OSG", "value": "False", "type": "BOOL" }, { "name": "BULLET_STATIC", "value": "True", "type": "BOOL" }, { "name": "OSG_STATIC", "value": "False", "type": "BOOL" }, { "name": "MYGUI_STATIC", "value": "False", "type": "BOOL" } ], What works: it builds What does not work: Not all DLLs are copied into the output directory with this set up (SDL2, MyGUI, Bullet, OSG, are not copied). --- CMakeLists.txt | 6 ++++++ extern/CMakeLists.txt | 43 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ad35d1b0e0..08bdbce8ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,6 +115,12 @@ option(SDL2_STATIC "Link static build of SDL into the binaries" FALSE) option(QT_STATIC "Link static build of QT into the binaries" FALSE) option(OPENMW_USE_SYSTEM_BULLET "Use system provided bullet physics library" ON) +if(OPENMW_USE_SYSTEM_BULLET) + set(_bullet_static_default OFF) +else() + set(_bullet_static_default ON) +endif() +option(BULLET_STATIC "Link static build of Bullet into the binaries" ${_bullet_static_default}) option(OPENMW_USE_SYSTEM_OSG "Use system provided OpenSceneGraph libraries" ON) if(OPENMW_USE_SYSTEM_OSG) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 613de8fa80..8766e51ccc 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -31,6 +31,15 @@ if(NOT OPENMW_USE_SYSTEM_BULLET) set(USE_DOUBLE_PRECISION ${BULLET_USE_DOUBLES} CACHE BOOL "") set(BULLET2_MULTITHREADING ON CACHE BOOL "") + if(BULLET_STATIC) + set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) + else() + set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE) + if(MSVC) + set(USE_MSVC_RUNTIME_LIBRARY_DLL ON CACHE BOOL "" FORCE) + endif() + endif() + # master on 12 Mar 2021 include(FetchContent) FetchContent_Declare(bullet @@ -61,9 +70,9 @@ if(NOT OPENMW_USE_SYSTEM_MYGUI) set(MYGUI_DONT_USE_OBSOLETE OFF CACHE BOOL "") if(MYGUI_STATIC) - set(BUILD_SHARED_LIBS OFF) + set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) else() - set(BUILD_SHARED_LIBS ON) + set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE) endif() include(FetchContent) @@ -81,8 +90,6 @@ endif() if(NOT OPENMW_USE_SYSTEM_OSG) cmake_minimum_required(VERSION 3.11) # for FetchContent - set(DYNAMIC_OPENTHREADS OFF CACHE BOOL "") - set(DYNAMIC_OPENSCENEGRAPH OFF CACHE BOOL "") set(BUILD_OSG_APPLICATIONS OFF CACHE BOOL "") set(BUILD_OSG_DEPRECATED_SERIALIZERS OFF CACHE BOOL "") set(OSG_FIND_3RD_PARTY_DEPS OFF CACHE BOOL "") @@ -104,9 +111,33 @@ if(NOT OPENMW_USE_SYSTEM_OSG) set(OPENGL_PROFILE "GL2" CACHE STRING "") if(OSG_STATIC) - set(BUILD_SHARED_LIBS OFF) + set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) + set(DYNAMIC_OPENTHREADS OFF CACHE BOOL "" FORCE) + set(DYNAMIC_OPENSCENEGRAPH OFF CACHE BOOL "" FORCE) else() - set(BUILD_SHARED_LIBS ON) + set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE) + set(DYNAMIC_OPENTHREADS ON CACHE BOOL "" FORCE) + set(DYNAMIC_OPENSCENEGRAPH ON CACHE BOOL "" FORCE) + endif() + mark_as_advanced(DYNAMIC_OPENTHREADS DYNAMIC_OPENSCENEGRAPH) + + if(WIN32) + # OSG here inherits C++17 language level because it doesn't specify its own. + # + # OSG's `using namespace std` interferes with Windows header files. + # + # See https://developercommunity.visualstudio.com/t/error-c2872-byte-ambiguous-symbol/93889 + # + # An alternative way to work around this without changing the language level is: + # + # add_compile_definitions(_HAS_STD_BYTE=0) + # + # TODO: Put OSG into its own scope so that this does not leak into Recast below. + set(CMAKE_CXX_STANDARD 11) + + if(MSVC) + set(OSG_MSVC_VERSIONED_DLL OFF CACHE BOOL "") + endif() endif() # branch OpenSceneGraph-3.6 on 23 Jan 2021. From b9c2f6ea1a1a07015bdee3df57d47b02af09a53f Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Tue, 16 Mar 2021 19:55:15 +0000 Subject: [PATCH 11/49] Minor cleanup: Remove `using namespace std` I came across these while trying to figure why MSVC build triggers https://developercommunity.visualstudio.com/t/error-c2872-byte-ambiguous-symbol/93889 In the end, the issue was not in openmw but in OSG, but it's good to clean up here anyway. --- components/bsa/bsa_file.cpp | 7 +++--- components/esm/esmreader.cpp | 6 ++--- components/to_utf8/gen_iconv.cpp | 41 ++++++++++++++++---------------- 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index 3fd74dd838..f6220b7ce7 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -28,12 +28,11 @@ #include #include -using namespace std; using namespace Bsa; /// Error handling -void BSAFile::fail(const string &msg) +void BSAFile::fail(const std::string &msg) { throw std::runtime_error("BSA Error: " + msg + "\nArchive: " + mFilename); } @@ -160,7 +159,7 @@ int BSAFile::getIndex(const char *str) const } /// Open an archive file. -void BSAFile::open(const string &file) +void BSAFile::open(const std::string &file) { mFilename = file; readHeader(); @@ -171,7 +170,7 @@ Files::IStreamPtr BSAFile::getFile(const char *file) assert(file); int i = getIndex(file); if(i == -1) - fail("File not found: " + string(file)); + fail("File not found: " + std::string(file)); const FileStruct &fs = mFiles[i]; diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 1b6eca7346..4e7dce876b 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -357,16 +357,14 @@ std::string ESMReader::getString(int size) void ESMReader::fail(const std::string &msg) { - using namespace std; - - stringstream ss; + std::stringstream ss; ss << "ESM Error: " << msg; ss << "\n File: " << mCtx.filename; ss << "\n Record: " << mCtx.recName.toString(); ss << "\n Subrecord: " << mCtx.subName.toString(); if (mEsm.get()) - ss << "\n Offset: 0x" << hex << mEsm->tellg(); + ss << "\n Offset: 0x" << std::hex << mEsm->tellg(); throw std::runtime_error(ss.str()); } diff --git a/components/to_utf8/gen_iconv.cpp b/components/to_utf8/gen_iconv.cpp index 8198b305dd..f2d9a42f18 100644 --- a/components/to_utf8/gen_iconv.cpp +++ b/components/to_utf8/gen_iconv.cpp @@ -1,20 +1,19 @@ // This program generates the file tables_gen.hpp #include -using namespace std; #include #include -void tab() { cout << " "; } +void tab() { std::cout << " "; } // write one number with a space in front of it and a comma after it void num(char i, bool last) { // Convert i to its integer value, i.e. -128 to 127. Printing it directly // would result in non-printable characters in the source code, which is bad. - cout << " " << static_cast(i); - if(!last) cout << ","; + std::cout << " " << static_cast(i); + if(!last) std::cout << ","; } // Write one table entry (UTF8 value), 1-5 bytes @@ -27,9 +26,9 @@ void writeChar(char *value, int length, bool last, const std::string &comment="" num(value[i], last && i==4); if(comment != "") - cout << " // " << comment; + std::cout << " // " << comment; - cout << endl; + std::cout << std::endl; } // What to write on missing characters @@ -46,7 +45,7 @@ void writeMissing(bool last) int write_table(const std::string &charset, const std::string &tableName) { // Write table header - cout << "static signed char " << tableName << "[] =\n{\n"; + std::cout << "static signed char " << tableName << "[] =\n{\n"; // Open conversion system iconv_t cd = iconv_open ("UTF-8", charset.c_str()); @@ -74,7 +73,7 @@ int write_table(const std::string &charset, const std::string &tableName) iconv_close (cd); // Finish table - cout << "};\n"; + std::cout << "};\n"; return 0; } @@ -82,37 +81,37 @@ int write_table(const std::string &charset, const std::string &tableName) int main() { // Write header guard - cout << "#ifndef COMPONENTS_TOUTF8_TABLE_GEN_H\n#define COMPONENTS_TOUTF8_TABLE_GEN_H\n\n"; + std::cout << "#ifndef COMPONENTS_TOUTF8_TABLE_GEN_H\n#define COMPONENTS_TOUTF8_TABLE_GEN_H\n\n"; // Write namespace - cout << "namespace ToUTF8\n{\n\n"; + std::cout << "namespace ToUTF8\n{\n\n"; // Central European and Eastern European languages that use Latin script, such as // Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian. - cout << "\n/// Central European and Eastern European languages that use Latin script," - "\n/// such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian," - "\n/// Serbian (Latin script), Romanian and Albanian." - "\n"; + std::cout << "\n/// Central European and Eastern European languages that use Latin script," + "\n/// such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian," + "\n/// Serbian (Latin script), Romanian and Albanian." + "\n"; write_table("WINDOWS-1250", "windows_1250"); // Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages - cout << "\n/// Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic" - "\n/// and other languages" - "\n"; + std::cout << "\n/// Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic" + "\n/// and other languages" + "\n"; write_table("WINDOWS-1251", "windows_1251"); // English - cout << "\n/// Latin alphabet used by English and some other Western languages" - "\n"; + std::cout << "\n/// Latin alphabet used by English and some other Western languages" + "\n"; write_table("WINDOWS-1252", "windows_1252"); write_table("CP437", "cp437"); // Close namespace - cout << "\n}\n\n"; + std::cout << "\n}\n\n"; // Close header guard - cout << "#endif\n\n"; + std::cout << "#endif\n\n"; return 0; } From 162b25c1808b61103be37f406e77e637e90ab15d Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 17 Mar 2021 01:46:04 +0000 Subject: [PATCH 12/49] Fix sunglare on Mesa The sunglare works by comparing an occlusion query with depth testing on against one with depth testing off to determine if there's anything closer to the camera than the maximum depth buffer value. For the depth- tested query, the depth range is set from 1 to 1 so it's always drawn at the maximum distance. Originally, we had the depth function set to LESS, meaning that the query would always fail as 1 is not less than 1, but also glPolygonOffset was used to move the query by "the smallest value that is guaranteed to produce a resolvable offset for a given implementation" closer to the camera. While other driver and hardware combinations do that, Mesa seems to be clamping to the depth range, so still failing. Instead, it's simpler to just get rid of the polygon offset and change the depth test to LEQUAL as 1 *is* less than or equal to 1, but not than any other possible depth buffer value. --- apps/openmw/mwrender/sky.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index f826416769..5a6ec06e56 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -497,8 +497,6 @@ public: // Disable writing to the color buffer. We are using this geometry for visibility tests only. osg::ref_ptr colormask (new osg::ColorMask(0, 0, 0, 0)); stateset->setAttributeAndModes(colormask, osg::StateAttribute::ON); - osg::ref_ptr po (new osg::PolygonOffset( -1., -1. )); - stateset->setAttributeAndModes(po, osg::StateAttribute::ON); mTransform->addChild(queryNode); @@ -595,7 +593,7 @@ private: if (queryVisible) { osg::ref_ptr depth (new osg::Depth); - depth->setFunction(osg::Depth::LESS); + depth->setFunction(osg::Depth::LEQUAL); // This is a trick to make fragments written by the query always use the maximum depth value, // without having to retrieve the current far clipping distance. // We want the sun glare to be "infinitely" far away. From 3ad1040271094aecb07e8447a3f107fa8bc0db72 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 17 Mar 2021 02:00:21 +0000 Subject: [PATCH 13/49] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02dc9dc968..0891ecba99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -110,6 +110,7 @@ Bug #5877: Effects appearing with empty icon Bug #5899: Visible modal windows and dropdowns crashing game on exit Bug #5902: NiZBufferProperty is unable to disable the depth test + Bug #5906: Sunglare doesn't work with Mesa drivers and AMD GPUs Feature #390: 3rd person look "over the shoulder" Feature #832: OpenMW-CS: Handle deleted references Feature #1536: Show more information about level on menu From 1c296a1a78c4147835273aa6a4f7c08df8ef61cb Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 17 Mar 2021 18:11:36 +0000 Subject: [PATCH 14/49] Deploy Qt style DLL --- CI/before_script.msvc.sh | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 0c26801cc4..4eda627ac4 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -325,6 +325,16 @@ add_qt_platform_dlls() { QT_PLATFORMS[$CONFIG]="${QT_PLATFORMS[$CONFIG]} $@" } +declare -A QT_STYLES +QT_STYLES["Release"]="" +QT_STYLES["Debug"]="" +QT_STYLES["RelWithDebInfo"]="" +add_qt_style_dlls() { + local CONFIG=$1 + shift + QT_STYLES[$CONFIG]="${QT_STYLES[$CONFIG]} $@" +} + if [ -z $PLATFORM ]; then PLATFORM="$(uname -m)" fi @@ -868,6 +878,7 @@ fi fi add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll add_qt_platform_dlls $CONFIGURATION "$(pwd)/plugins/platforms/qwindows${DLLSUFFIX}.dll" + add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll" done echo Done. else @@ -883,6 +894,7 @@ fi DIR=$(windowsPathAsUnix "${QT_SDK}") add_runtime_dlls $CONFIGURATION "${DIR}/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll add_qt_platform_dlls $CONFIGURATION "${DIR}/plugins/platforms/qwindows${DLLSUFFIX}.dll" + add_qt_style_dlls $CONFIGURATION "${DIR}/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll" done echo Done. fi @@ -1060,6 +1072,13 @@ fi cp "$DLL" "${DLL_PREFIX}platforms" done echo + echo "- Qt Style DLLs..." + mkdir -p ${DLL_PREFIX}styles + for DLL in ${QT_STYLES[$CONFIGURATION]}; do + echo " $(basename $DLL)" + cp "$DLL" "${DLL_PREFIX}styles" + done + echo done #fi From b38a81760069f9218411fb7657ce981a24356716 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 17 Mar 2021 23:29:48 +0000 Subject: [PATCH 15/49] Ensure vswhere finds us a single suitable MSVC installation Also document the numerous arguments to achieve this. --- CI/before_script.msvc.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 0c26801cc4..71a6b79c4e 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -1067,7 +1067,13 @@ if [ -n "$ACTIVATE_MSVC" ]; then echo -n "- Activating MSVC in the current shell... " command -v vswhere >/dev/null 2>&1 || { echo "Error: vswhere is not on the path."; wrappedExit 1; } - MSVC_INSTALLATION_PATH=$(vswhere -products '*' -version "[$MSVC_REAL_VER,$(awk "BEGIN { print $MSVC_REAL_VER + 1; exit }"))" -property installationPath) + # There are so many arguments now that I'm going to document them: + # * products: allow Visual Studio or standalone build tools + # * version: obvious. Awk helps make a version range by adding one. + # * property installationPath: only give the installation path. + # * latest: return only one result if several candidates exist. Prefer the last installed/updated + # * requires: make sure it's got the MSVC compiler instead of, for example, just the .NET compiler. The .x86.x64 suffix means it's for either, not that it's the x64 on x86 cross compiler as you always get both + MSVC_INSTALLATION_PATH=$(vswhere -products '*' -version "[$MSVC_REAL_VER,$(awk "BEGIN { print $MSVC_REAL_VER + 1; exit }"))" -property installationPath -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64) if [ -z "$MSVC_INSTALLATION_PATH" ]; then echo "vswhere was unable to find MSVC $MSVC_DISPLAY_YEAR" wrappedExit 1 From a22f6b24d539c0ae773dbc9f1966eb122d79194b Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 18 Mar 2021 11:47:06 +0400 Subject: [PATCH 16/49] Init animation key struct before usage --- components/nif/nifkey.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nif/nifkey.hpp b/components/nif/nifkey.hpp index de2fa31c89..91869ff849 100644 --- a/components/nif/nifkey.hpp +++ b/components/nif/nifkey.hpp @@ -77,7 +77,7 @@ struct KeyMapT { mInterpolationType = nif->getUInt(); - KeyT key; + KeyType key = {}; NIFStream &nifReference = *nif; if (mInterpolationType == InterpolationType_Linear From 1471ef003a6110c21fb1f76e057aea11c4664821 Mon Sep 17 00:00:00 2001 From: wareya Date: Thu, 18 Mar 2021 13:53:00 -0400 Subject: [PATCH 17/49] fix async physics interpolation --- apps/openmw/mwphysics/mtphysics.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 2b0db5b82c..1fa6251f83 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -186,9 +186,11 @@ namespace MWPhysics mPostStepBarrier = std::make_unique(mNumThreads, [&]() { if (mRemainingSteps) + { --mRemainingSteps; + updateActorsPositions(); + } mNextJob.store(0, std::memory_order_release); - updateActorsPositions(); }); mPostSimBarrier = std::make_unique(mNumThreads, [&]() From 54daa234bd9124cd187eae6576b044eb849f377d Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 19 Mar 2021 11:56:14 +0400 Subject: [PATCH 18/49] Reset watched stats upon reload or new game start --- apps/openmw/mwgui/statswatcher.hpp | 2 ++ apps/openmw/mwgui/windowmanagerimp.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/apps/openmw/mwgui/statswatcher.hpp b/apps/openmw/mwgui/statswatcher.hpp index 41ab4fd252..353779d877 100644 --- a/apps/openmw/mwgui/statswatcher.hpp +++ b/apps/openmw/mwgui/statswatcher.hpp @@ -63,6 +63,8 @@ namespace MWGui void watchActor(const MWWorld::Ptr& ptr); MWWorld::Ptr getWatchedActor() const { return mWatched; } + + void forceUpdate() { mWatchedStatsEmpty = true; } }; } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 8e4ea4e54f..54c09e00f1 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -495,6 +495,8 @@ namespace MWGui } else allow(GW_ALL); + + mStatsWatcher->forceUpdate(); } WindowManager::~WindowManager() From 845e3944d6d61488f024d73923dae975a3d12387 Mon Sep 17 00:00:00 2001 From: wareya Date: Fri, 19 Mar 2021 17:54:08 -0400 Subject: [PATCH 19/49] make refraction more visible even at a distance --- files/shaders/water_fragment.glsl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index d9b9463ad9..5f6d8c6ac3 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -236,7 +236,11 @@ void main(void) if (cameraPos.z < 0.0) refraction = clamp(refraction * 1.5, 0.0, 1.0); else - refraction = mix(refraction, waterColor, clamp(depthSampleDistorted/VISIBILITY, 0.0, 1.0)); + { + vec3 refractionA = mix(refraction, waterColor, clamp(depthSampleDistorted/VISIBILITY, 0.0, 1.0)); + vec3 refractionB = mix(refraction, waterColor, clamp(realWaterDepth/VISIBILITY, 0.0, 1.0)); + refraction = mix(refractionA, refractionB, 0.8); + } // sunlight scattering // normal for sunlight scattering From bf336e4cb45575b67903e611675d0b356d79d8cd Mon Sep 17 00:00:00 2001 From: wareya Date: Fri, 19 Mar 2021 18:51:52 -0400 Subject: [PATCH 20/49] make sun scattering color stop being an ugly radioactive green --- files/shaders/water_fragment.glsl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index 5f6d8c6ac3..b7c7487518 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -29,9 +29,6 @@ const float BUMP_RAIN = 2.5; const float REFL_BUMP = 0.10; // reflection distortion amount const float REFR_BUMP = 0.07; // refraction distortion amount -const float SCATTER_AMOUNT = 0.3; // amount of sunlight scattering -const vec3 SCATTER_COLOUR = vec3(0.0,1.0,0.95); // colour of sunlight scattering - const vec3 SUN_EXT = vec3(0.45, 0.55, 0.68); //sunlight extinction const float SPEC_HARDNESS = 256.0; // specular highlights hardness @@ -43,6 +40,9 @@ const float WIND_SPEED = 0.2f; const vec3 WATER_COLOR = vec3(0.090195, 0.115685, 0.12745); +const float SCATTER_AMOUNT = 0.5; // amount of sunlight scattering +const vec3 SCATTER_COLOUR = WATER_COLOR * 8.0; // colour of sunlight scattering + // ---------------- rain ripples related stuff --------------------- const float RAIN_RIPPLE_GAPS = 5.0; From 1a4e9df707a4efe2cf092a252ccb66781906e466 Mon Sep 17 00:00:00 2001 From: wareya Date: Fri, 19 Mar 2021 20:12:40 -0400 Subject: [PATCH 21/49] add bit to suppress coastline artifacts at more of a distance --- files/shaders/water_fragment.glsl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index b7c7487518..db208ea424 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -34,6 +34,7 @@ const vec3 SUN_EXT = vec3(0.45, 0.55, 0.68); //sunlight extinction const float SPEC_HARDNESS = 256.0; // specular highlights hardness const float BUMP_SUPPRESS_DEPTH = 300.0; // at what water depth bumpmap will be suppressed for reflections and refractions (prevents artifacts at shores) +const float BUMP_SUPPRESS_DEPTH_SS = 1000.0; // modifier using screenspace depth (helps prevent same artifacts but at higher distances) const vec2 WIND_DIR = vec2(0.5f, -0.8f); const float WIND_SPEED = 0.2f; @@ -218,7 +219,10 @@ void main(void) float depthSampleDistorted = linearizeDepth(texture2D(refractionDepthMap,screenCoords-screenCoordsOffset).x) * radialise; float surfaceDepth = linearizeDepth(gl_FragCoord.z) * radialise; float realWaterDepth = depthSample - surfaceDepth; // undistorted water depth in view direction, independent of frustum - screenCoordsOffset *= clamp(realWaterDepth / BUMP_SUPPRESS_DEPTH,0,1); + screenCoordsOffset *= clamp( + realWaterDepth / (BUMP_SUPPRESS_DEPTH + * max(1, depthSample / BUMP_SUPPRESS_DEPTH_SS)) // suppress more at distance + ,0 ,1); #endif // reflection vec3 reflection = texture2D(reflectionMap, screenCoords + screenCoordsOffset).rgb; From 675c0ab72fd365c3699a68081636db7d64dd175e Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 19 Mar 2021 23:23:26 +0100 Subject: [PATCH 22/49] Apply uniform random deviation to AI reaction timer This allows to distribute AI reaction calls over time. Before this change actors appearing at the same frame will react in the same frame over and over because AI reaction period is constant. It creates a non-uniform CPU usage over frames. If a single frame has too many AI reactions it may cause stuttering when there are too many actors on a scene for current system. Another concern is a synchronization of actions between creatures and NPC. They start to go or hit at the same frame that is unnatural. --- apps/openmw/mwmechanics/aicombat.cpp | 15 ++-------- apps/openmw/mwmechanics/aicombat.hpp | 4 +-- apps/openmw/mwmechanics/aipackage.cpp | 9 ++---- apps/openmw/mwmechanics/aipackage.hpp | 5 ++-- apps/openmw/mwmechanics/aitimer.hpp | 26 +++++++++++++++++ apps/openmw/mwmechanics/aiwander.cpp | 11 ++----- apps/openmw/mwmechanics/aiwander.hpp | 4 +-- components/misc/rng.cpp | 5 ++++ components/misc/rng.hpp | 2 ++ components/misc/timer.hpp | 42 +++++++++++++++++++++++++++ 10 files changed, 90 insertions(+), 33 deletions(-) create mode 100644 apps/openmw/mwmechanics/aitimer.hpp create mode 100644 components/misc/timer.hpp diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 58a9086720..51fcb92c10 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -146,19 +146,10 @@ namespace MWMechanics } storage.mActionCooldown -= duration; - float& timerReact = storage.mTimerReact; - if (timerReact < AI_REACTION_TIME) - { - timerReact += duration; - } - else - { - timerReact = 0; - if (attack(actor, target, storage, characterController)) - return true; - } + if (storage.mReaction.update(duration) == Misc::TimerStatus::Waiting) + return false; - return false; + return attack(actor, target, storage, characterController); } bool AiCombat::attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController) diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 3a77aa8e8e..0f42c6e2d1 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -10,6 +10,7 @@ #include "pathfinding.hpp" #include "movement.hpp" #include "obstacle.hpp" +#include "aitimer.hpp" namespace ESM { @@ -27,7 +28,7 @@ namespace MWMechanics struct AiCombatStorage : AiTemporaryBase { float mAttackCooldown; - float mTimerReact; + AiReactionTimer mReaction; float mTimerCombatMove; bool mReadyToAttack; bool mAttack; @@ -60,7 +61,6 @@ namespace MWMechanics AiCombatStorage(): mAttackCooldown(0.0f), - mTimerReact(AI_REACTION_TIME), mTimerCombatMove(0.0f), mReadyToAttack(false), mAttack(false), diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 99132b711a..00e1c9c3a8 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -28,7 +28,6 @@ MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) : mTypeId(typeId), mOptions(options), - mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild mTargetActorRefId(""), mTargetActorId(-1), mRotateOnTheRunChecks(0), @@ -64,7 +63,7 @@ MWWorld::Ptr MWMechanics::AiPackage::getTarget() const void MWMechanics::AiPackage::reset() { // reset all members - mTimer = AI_REACTION_TIME + 1.0f; + mReaction.reset(); mIsShortcutting = false; mShortcutProhibited = false; mShortcutFailPos = osg::Vec3f(); @@ -75,7 +74,7 @@ void MWMechanics::AiPackage::reset() bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, float destTolerance) { - mTimer += duration; //Update timer + const Misc::TimerStatus timerStatus = mReaction.update(duration); const osg::Vec3f position = actor.getRefData().getPosition().asVec3(); //position of the actor MWBase::World* world = MWBase::Environment::get().getWorld(); @@ -98,7 +97,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& const bool isDestReached = (distToTarget <= destTolerance); const bool actorCanMoveByZ = canActorMoveByZAxis(actor); - if (!isDestReached && mTimer > AI_REACTION_TIME) + if (!isDestReached && timerStatus == Misc::TimerStatus::Elapsed) { if (actor.getClass().isBipedal(actor)) openDoors(actor); @@ -142,8 +141,6 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go } } - - mTimer = 0; } const float actorTolerance = 2 * actor.getClass().getMaxSpeed(actor) * duration diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 4201de5c84..81b09c8b9c 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -10,6 +10,7 @@ #include "obstacle.hpp" #include "aistate.hpp" #include "aipackagetypeid.hpp" +#include "aitimer.hpp" namespace MWWorld { @@ -28,8 +29,6 @@ namespace ESM namespace MWMechanics { - const float AI_REACTION_TIME = 0.25f; - class CharacterController; class PathgridGraph; @@ -158,7 +157,7 @@ namespace MWMechanics PathFinder mPathFinder; ObstacleCheck mObstacleCheck; - float mTimer; + AiReactionTimer mReaction; std::string mTargetActorRefId; mutable int mTargetActorId; diff --git a/apps/openmw/mwmechanics/aitimer.hpp b/apps/openmw/mwmechanics/aitimer.hpp new file mode 100644 index 0000000000..804cda1bd2 --- /dev/null +++ b/apps/openmw/mwmechanics/aitimer.hpp @@ -0,0 +1,26 @@ +#ifndef OPENMW_MECHANICS_AITIMER_H +#define OPENMW_MECHANICS_AITIMER_H + +#include +#include + +namespace MWMechanics +{ + constexpr float AI_REACTION_TIME = 0.25f; + + class AiReactionTimer + { + public: + static constexpr float sDeviation = 0.1f; + + Misc::TimerStatus update(float duration) { return mImpl.update(duration); } + + void reset() { mImpl.reset(Misc::Rng::deviate(0, sDeviation)); } + + private: + Misc::DeviatingPeriodicTimer mImpl {AI_REACTION_TIME, sDeviation, + Misc::Rng::deviate(0, sDeviation)}; + }; +} + +#endif diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 375209a250..72b8757bff 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -223,15 +223,10 @@ namespace MWMechanics doPerFrameActionsForState(actor, duration, storage); - float& lastReaction = storage.mReaction; - lastReaction += duration; - if (AI_REACTION_TIME <= lastReaction) - { - lastReaction = 0; - return reactionTimeActions(actor, storage, pos); - } - else + if (storage.mReaction.update(duration) == Misc::TimerStatus::Waiting) return false; + + return reactionTimeActions(actor, storage, pos); } bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos) diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 8e718061e2..52a926d143 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -10,6 +10,7 @@ #include "pathfinding.hpp" #include "obstacle.hpp" #include "aistate.hpp" +#include "aitimer.hpp" namespace ESM { @@ -25,7 +26,7 @@ namespace MWMechanics /// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive. struct AiWanderStorage : AiTemporaryBase { - float mReaction; // update some actions infrequently + AiReactionTimer mReaction; // AiWander states enum WanderState @@ -57,7 +58,6 @@ namespace MWMechanics int mStuckCount; AiWanderStorage(): - mReaction(0), mState(Wander_ChooseAction), mIsWanderingManually(false), mCanWanderAlongPathGrid(true), diff --git a/components/misc/rng.cpp b/components/misc/rng.cpp index 23d8204482..4805f0d91c 100644 --- a/components/misc/rng.cpp +++ b/components/misc/rng.cpp @@ -47,4 +47,9 @@ namespace Misc { return static_cast(std::chrono::high_resolution_clock::now().time_since_epoch().count()); } + + float Rng::deviate(float mean, float deviation, Seed& seed) + { + return std::uniform_real_distribution(mean - deviation, mean + deviation)(seed.mGenerator); + } } diff --git a/components/misc/rng.hpp b/components/misc/rng.hpp index 8efca438d1..998ac0d535 100644 --- a/components/misc/rng.hpp +++ b/components/misc/rng.hpp @@ -42,6 +42,8 @@ public: /// returns default seed for RNG static unsigned int generateDefaultSeed(); + + static float deviate(float mean, float deviation, Seed& seed = getSeed()); }; } diff --git a/components/misc/timer.hpp b/components/misc/timer.hpp new file mode 100644 index 0000000000..81a2ca073c --- /dev/null +++ b/components/misc/timer.hpp @@ -0,0 +1,42 @@ +#ifndef OPENMW_COMPONENTS_MISC_TIMER_H +#define OPENMW_COMPONENTS_MISC_TIMER_H + +#include "rng.hpp" + +namespace Misc +{ + enum class TimerStatus + { + Waiting, + Elapsed, + }; + + class DeviatingPeriodicTimer + { + public: + explicit DeviatingPeriodicTimer(float period, float deviation, float timeLeft) + : mPeriod(period), mDeviation(deviation), mTimeLeft(timeLeft) + {} + + TimerStatus update(float duration) + { + if (mTimeLeft > 0) + { + mTimeLeft -= duration; + return TimerStatus::Waiting; + } + + mTimeLeft = Rng::deviate(mPeriod, mDeviation); + return TimerStatus::Elapsed; + } + + void reset(float timeLeft) { mTimeLeft = timeLeft; } + + private: + const float mPeriod; + const float mDeviation; + float mTimeLeft; + }; +} + +#endif From 62c0ecbbd0556b16db5a01798f41b4e94921565e Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 20 Mar 2021 01:50:51 +0100 Subject: [PATCH 23/49] Separate engage combat timer for each actor Use DeviatingPeriodicTimer to distribute calls over time. This reduces stuttering and make AI more natural. --- apps/openmw/mwmechanics/actor.hpp | 8 ++++++++ apps/openmw/mwmechanics/actors.cpp | 8 +++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/actor.hpp b/apps/openmw/mwmechanics/actor.hpp index 287ca420f4..be4f425378 100644 --- a/apps/openmw/mwmechanics/actor.hpp +++ b/apps/openmw/mwmechanics/actor.hpp @@ -5,6 +5,8 @@ #include "../mwmechanics/actorutil.hpp" +#include + namespace MWRender { class Animation; @@ -41,12 +43,18 @@ namespace MWMechanics bool isTurningToPlayer() const; void setTurningToPlayer(bool turning); + Misc::TimerStatus updateEngageCombatTimer(float duration) + { + return mEngageCombat.update(duration); + } + private: std::unique_ptr mCharacterController; int mGreetingTimer{0}; float mTargetAngleRadians{0.f}; GreetingState mGreetingState{Greet_None}; bool mIsTurningToPlayer{false}; + Misc::DeviatingPeriodicTimer mEngageCombat{1.0f, 0.25f, Misc::Rng::deviate(0, 0.25f)}; }; } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index c0a1371585..4379521a4c 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1901,14 +1901,11 @@ namespace MWMechanics { if(!paused) { - static float timerUpdateAITargets = 0; static float timerUpdateHeadTrack = 0; static float timerUpdateEquippedLight = 0; static float timerUpdateHello = 0; const float updateEquippedLightInterval = 1.0f; - // target lists get updated once every 1.0 sec - if (timerUpdateAITargets >= 1.0f) timerUpdateAITargets = 0; if (timerUpdateHeadTrack >= 0.3f) timerUpdateHeadTrack = 0; if (timerUpdateHello >= 0.25f) timerUpdateHello = 0; if (mTimerDisposeSummonsCorpses >= 0.2f) mTimerDisposeSummonsCorpses = 0; @@ -1961,6 +1958,8 @@ namespace MWMechanics iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration); + const Misc::TimerStatus engageCombatTimerStatus = iter->second->updateEngageCombatTimer(duration); + // For dead actors we need to update looping spell particles if (iter->first.getClass().getCreatureStats(iter->first).isDead()) { @@ -1989,7 +1988,7 @@ namespace MWMechanics } if (aiActive && inProcessingRange) { - if (timerUpdateAITargets == 0) + if (engageCombatTimerStatus == Misc::TimerStatus::Elapsed) { if (!isPlayer) adjustCommandedActor(iter->first); @@ -2076,7 +2075,6 @@ namespace MWMechanics if (avoidCollisions) predictAndAvoidCollisions(); - timerUpdateAITargets += duration; timerUpdateHeadTrack += duration; timerUpdateEquippedLight += duration; timerUpdateHello += duration; From 40265bf1181167d927f3870b9d5127e310ce49f7 Mon Sep 17 00:00:00 2001 From: wareya Date: Sat, 20 Mar 2021 21:14:56 -0400 Subject: [PATCH 24/49] make unstucking slightly smarter (can turn itself off, also acts like flat ground) --- apps/openmw/mwphysics/actor.hpp | 21 +++++++++++++++++++ apps/openmw/mwphysics/movementsolver.cpp | 26 +++++++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 031125f40d..472a79bff5 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -159,6 +159,24 @@ namespace MWPhysics MWWorld::Ptr getStandingOnPtr() const; void setStandingOnPtr(const MWWorld::Ptr& ptr); + unsigned int getStuckFrames() const + { + return mStuckFrames; + } + void setStuckFrames(unsigned int frames) + { + mStuckFrames = frames; + } + + const osg::Vec3f &getLastStuckPosition() const + { + return mLastStuckPosition; + } + void setLastStuckPosition(osg::Vec3f position) + { + mLastStuckPosition = position; + } + private: MWWorld::Ptr mStandingOnPtr; /// Removes then re-adds the collision object to the dynamics world @@ -192,6 +210,9 @@ namespace MWPhysics btTransform mLocalTransform; mutable std::mutex mPositionMutex; + unsigned int mStuckFrames; + osg::Vec3f mLastStuckPosition; + osg::Vec3f mForce; std::atomic mOnGround; std::atomic mOnSlope; diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 5f0322a1f2..a393ec5784 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -204,7 +204,7 @@ namespace MWPhysics osg::Vec3f lastSlideNormalFallback(0,0,1); bool forceGroundTest = false; - for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f; ++iterations) + for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.0001f; ++iterations) { osg::Vec3f nextpos = newPosition + velocity * remainingTime; @@ -394,6 +394,12 @@ namespace MWPhysics isOnGround = false; } } + // forcibly treat stuck actors as if they're on flat ground because buggy collisions when inside of things can/will break ground detection + if(physicActor->getStuckFrames() > 0) + { + isOnGround = true; + isOnSlope = false; + } } if((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || actor.mFlying) @@ -437,6 +443,17 @@ namespace MWPhysics auto* collisionObject = physicActor->getCollisionObject(); auto tempPosition = actor.mPosition; + if(physicActor->getStuckFrames() >= 10) + { + if((physicActor->getLastStuckPosition() - actor.mPosition).length2() < 100) + return; + else + { + physicActor->setStuckFrames(0); + physicActor->setLastStuckPosition({0, 0, 0}); + } + } + // use vanilla-accurate collision hull position hack (do same hitbox offset hack as movement solver) // if vanilla compatibility didn't matter, the "correct" collision hull position would be physicActor->getScaledMeshTranslation() const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, physicActor->getHalfExtents().z()); @@ -470,6 +487,8 @@ namespace MWPhysics auto contactCallback = gatherContacts({0.0, 0.0, 0.0}); if(contactCallback.mDistance < -sAllowedPenetration) { + physicActor->setStuckFrames(physicActor->getStuckFrames() + 1); + physicActor->setLastStuckPosition(actor.mPosition); // we are; try moving it out of the world auto positionDelta = contactCallback.mContactSum; // limit rejection delta to the largest known individual rejections @@ -502,6 +521,11 @@ namespace MWPhysics } } } + else + { + physicActor->setStuckFrames(0); + physicActor->setLastStuckPosition({0, 0, 0}); + } collisionObject->setWorldTransform(oldTransform); actor.mPosition = tempPosition; From e722c99e6284d5f3611b746e5e6cc562c28b8fa4 Mon Sep 17 00:00:00 2001 From: wareya Date: Sun, 21 Mar 2021 08:57:15 -0400 Subject: [PATCH 25/49] forgot to initialize these variables --- apps/openmw/mwphysics/actor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 06abe72403..905034cde5 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -22,6 +22,7 @@ namespace MWPhysics Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler) : mStandingOnPtr(nullptr), mCanWaterWalk(false), mWalkingOnWater(false) , mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBox.center), mHalfExtents(shape->mCollisionBox.extents) + , mStuckFrames(0), mLastStuckPosition{0, 0, 0} , mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false) , mInternalCollisionMode(true) , mExternalCollisionMode(true) From 7bbbe40abe042c1931823d68f0d769f40a5f8695 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 21 Mar 2021 13:56:56 +0100 Subject: [PATCH 26/49] "static const" -> "static constexpr" in headers --- apps/opencs/model/prefs/shortcutsetting.hpp | 2 +- apps/openmw/mwgui/sortfilteritemmodel.hpp | 26 ++++++------- apps/openmw/mwgui/widgets.hpp | 2 +- apps/openmw/mwmechanics/creaturestats.hpp | 2 +- apps/openmw/mwmechanics/obstacle.hpp | 2 +- apps/openmw/mwphysics/constants.hpp | 20 +++++----- apps/openmw/mwrender/animation.hpp | 2 +- apps/openmw/mwscript/ref.hpp | 4 +- apps/openmw/mwworld/containerstore.hpp | 32 ++++++++-------- apps/openmw/mwworld/inventorystore.hpp | 42 ++++++++++----------- 10 files changed, 67 insertions(+), 67 deletions(-) diff --git a/apps/opencs/model/prefs/shortcutsetting.hpp b/apps/opencs/model/prefs/shortcutsetting.hpp index a0c588b42e..52298232e7 100644 --- a/apps/opencs/model/prefs/shortcutsetting.hpp +++ b/apps/opencs/model/prefs/shortcutsetting.hpp @@ -34,7 +34,7 @@ namespace CSMPrefs void storeValue(const QKeySequence& sequence); void resetState(); - static const int MaxKeys = 4; + static constexpr int MaxKeys = 4; QPushButton* mButton; diff --git a/apps/openmw/mwgui/sortfilteritemmodel.hpp b/apps/openmw/mwgui/sortfilteritemmodel.hpp index fa70a0edd7..64a01f71bd 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.hpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.hpp @@ -35,20 +35,20 @@ namespace MWGui bool onDropItem(const MWWorld::Ptr &item, int count) override; bool onTakeItem(const MWWorld::Ptr &item, int count) override; - static const int Category_Weapon = (1<<1); - static const int Category_Apparel = (1<<2); - static const int Category_Misc = (1<<3); - static const int Category_Magic = (1<<4); - static const int Category_All = 255; + static constexpr int Category_Weapon = (1<<1); + static constexpr int Category_Apparel = (1<<2); + static constexpr int Category_Misc = (1<<3); + static constexpr int Category_Magic = (1<<4); + static constexpr int Category_All = 255; - static const int Filter_OnlyIngredients = (1<<0); - static const int Filter_OnlyEnchanted = (1<<1); - static const int Filter_OnlyEnchantable = (1<<2); - static const int Filter_OnlyChargedSoulstones = (1<<3); - static const int Filter_OnlyUsableItems = (1<<4); // Only items with a Use action - static const int Filter_OnlyRepairable = (1<<5); - static const int Filter_OnlyRechargable = (1<<6); - static const int Filter_OnlyRepairTools = (1<<7); + static constexpr int Filter_OnlyIngredients = (1<<0); + static constexpr int Filter_OnlyEnchanted = (1<<1); + static constexpr int Filter_OnlyEnchantable = (1<<2); + static constexpr int Filter_OnlyChargedSoulstones = (1<<3); + static constexpr int Filter_OnlyUsableItems = (1<<4); // Only items with a Use action + static constexpr int Filter_OnlyRepairable = (1<<5); + static constexpr int Filter_OnlyRechargable = (1<<6); + static constexpr int Filter_OnlyRepairTools = (1<<7); private: diff --git a/apps/openmw/mwgui/widgets.hpp b/apps/openmw/mwgui/widgets.hpp index 731a41a354..3c55287159 100644 --- a/apps/openmw/mwgui/widgets.hpp +++ b/apps/openmw/mwgui/widgets.hpp @@ -268,7 +268,7 @@ namespace MWGui void initialiseOverride() override; private: - static const int sIconOffset = 24; + static constexpr int sIconOffset = 24; void updateWidgets(); diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index b2c0aec98e..e09f5197e1 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -24,7 +24,7 @@ namespace MWMechanics { struct CorprusStats { - static const int sWorseningPeriod = 24; + static constexpr int sWorseningPeriod = 24; int mWorsenings[ESM::Attribute::Length]; MWWorld::TimeStamp mNextWorsening; diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index 6c2197d811..b574bab67f 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -12,7 +12,7 @@ namespace MWMechanics { struct Movement; - static const int NUM_EVADE_DIRECTIONS = 4; + static constexpr int NUM_EVADE_DIRECTIONS = 4; /// tests actor's proximity to a closed door by default bool proximityToDoor(const MWWorld::Ptr& actor, float minDist); diff --git a/apps/openmw/mwphysics/constants.hpp b/apps/openmw/mwphysics/constants.hpp index c6f2f3b530..d552ed49e6 100644 --- a/apps/openmw/mwphysics/constants.hpp +++ b/apps/openmw/mwphysics/constants.hpp @@ -3,24 +3,24 @@ namespace MWPhysics { - static const float sStepSizeUp = 34.0f; - static const float sStepSizeDown = 62.0f; + static constexpr float sStepSizeUp = 34.0f; + static constexpr float sStepSizeDown = 62.0f; - static const float sMinStep = 10.0f; // hack to skip over tiny unwalkable slopes - static const float sMinStep2 = 20.0f; // hack to skip over shorter but longer/wider/further unwalkable slopes + static constexpr float sMinStep = 10.0f; // hack to skip over tiny unwalkable slopes + static constexpr float sMinStep2 = 20.0f; // hack to skip over shorter but longer/wider/further unwalkable slopes // whether to do the above stairstepping logic hacks to work around bad morrowind assets - disabling causes problems but improves performance - static const bool sDoExtraStairHacks = true; + static constexpr bool sDoExtraStairHacks = true; - static const float sGroundOffset = 1.0f; - static const float sMaxSlope = 49.0f; + static constexpr float sGroundOffset = 1.0f; + static constexpr float sMaxSlope = 49.0f; // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared. - static const int sMaxIterations = 8; + static constexpr int sMaxIterations = 8; // Allows for more precise movement solving without getting stuck or snagging too easily. - static const float sCollisionMargin = 0.1; + static constexpr float sCollisionMargin = 0.1; // Allow for a small amount of penetration to prevent numerical precision issues from causing the "unstuck"ing code to run unnecessarily // Currently set to 0 because having the "unstuck"ing code run whenever possible prevents some glitchy snagging issues - static const float sAllowedPenetration = 0.0; + static constexpr float sAllowedPenetration = 0.0; } #endif diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index ebfe8a2e59..04c5825c94 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -102,7 +102,7 @@ public: BlendMask_All = BlendMask_LowerBody | BlendMask_UpperBody }; /* This is the number of *discrete* blend masks. */ - static const size_t sNumBlendMasks = 4; + static constexpr size_t sNumBlendMasks = 4; /// Holds an animation priority value for each BoneGroup. struct AnimPriority diff --git a/apps/openmw/mwscript/ref.hpp b/apps/openmw/mwscript/ref.hpp index e572f51471..c52b419c1d 100644 --- a/apps/openmw/mwscript/ref.hpp +++ b/apps/openmw/mwscript/ref.hpp @@ -14,7 +14,7 @@ namespace MWScript { struct ExplicitRef { - static const bool implicit = false; + static constexpr bool implicit = false; MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required = true, bool activeOnly = false) const; @@ -22,7 +22,7 @@ namespace MWScript struct ImplicitRef { - static const bool implicit = true; + static constexpr bool implicit = true; MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required = true, bool activeOnly = false) const; diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index e0843efbac..882f5efc44 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -73,22 +73,22 @@ namespace MWWorld { public: - static const int Type_Potion = 0x0001; - static const int Type_Apparatus = 0x0002; - static const int Type_Armor = 0x0004; - static const int Type_Book = 0x0008; - static const int Type_Clothing = 0x0010; - static const int Type_Ingredient = 0x0020; - static const int Type_Light = 0x0040; - static const int Type_Lockpick = 0x0080; - static const int Type_Miscellaneous = 0x0100; - static const int Type_Probe = 0x0200; - static const int Type_Repair = 0x0400; - static const int Type_Weapon = 0x0800; + static constexpr int Type_Potion = 0x0001; + static constexpr int Type_Apparatus = 0x0002; + static constexpr int Type_Armor = 0x0004; + static constexpr int Type_Book = 0x0008; + static constexpr int Type_Clothing = 0x0010; + static constexpr int Type_Ingredient = 0x0020; + static constexpr int Type_Light = 0x0040; + static constexpr int Type_Lockpick = 0x0080; + static constexpr int Type_Miscellaneous = 0x0100; + static constexpr int Type_Probe = 0x0200; + static constexpr int Type_Repair = 0x0400; + static constexpr int Type_Weapon = 0x0800; - static const int Type_Last = Type_Weapon; + static constexpr int Type_Last = Type_Weapon; - static const int Type_All = 0xffff; + static constexpr int Type_All = 0xffff; static const std::string sGoldId; @@ -265,13 +265,13 @@ namespace MWWorld template struct IsConvertible { - static const bool value = true; + static constexpr bool value = true; }; template struct IsConvertible { - static const bool value = false; + static constexpr bool value = false; }; template diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index bfe0a99922..6809e63b29 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -42,29 +42,29 @@ namespace MWWorld { public: - static const int Slot_Helmet = 0; - static const int Slot_Cuirass = 1; - static const int Slot_Greaves = 2; - static const int Slot_LeftPauldron = 3; - static const int Slot_RightPauldron = 4; - static const int Slot_LeftGauntlet = 5; - static const int Slot_RightGauntlet = 6; - static const int Slot_Boots = 7; - static const int Slot_Shirt = 8; - static const int Slot_Pants = 9; - static const int Slot_Skirt = 10; - static const int Slot_Robe = 11; - static const int Slot_LeftRing = 12; - static const int Slot_RightRing = 13; - static const int Slot_Amulet = 14; - static const int Slot_Belt = 15; - static const int Slot_CarriedRight = 16; - static const int Slot_CarriedLeft = 17; - static const int Slot_Ammunition = 18; + static constexpr int Slot_Helmet = 0; + static constexpr int Slot_Cuirass = 1; + static constexpr int Slot_Greaves = 2; + static constexpr int Slot_LeftPauldron = 3; + static constexpr int Slot_RightPauldron = 4; + static constexpr int Slot_LeftGauntlet = 5; + static constexpr int Slot_RightGauntlet = 6; + static constexpr int Slot_Boots = 7; + static constexpr int Slot_Shirt = 8; + static constexpr int Slot_Pants = 9; + static constexpr int Slot_Skirt = 10; + static constexpr int Slot_Robe = 11; + static constexpr int Slot_LeftRing = 12; + static constexpr int Slot_RightRing = 13; + static constexpr int Slot_Amulet = 14; + static constexpr int Slot_Belt = 15; + static constexpr int Slot_CarriedRight = 16; + static constexpr int Slot_CarriedLeft = 17; + static constexpr int Slot_Ammunition = 18; - static const int Slots = 19; + static constexpr int Slots = 19; - static const int Slot_NoSlot = -1; + static constexpr int Slot_NoSlot = -1; private: From 209e7718a8e246a07e3afb8ffe40859f21df4594 Mon Sep 17 00:00:00 2001 From: "Hristos N. Triantafillou" Date: Sun, 21 Mar 2021 15:37:48 -0500 Subject: [PATCH 27/49] Clarify the requirements of a data folder The current text could be interpreted to mean that a data folder _must_ have a plugin, but this isn't the case. This added text clarifies that a plugin or resources are needed. --- docs/source/reference/modding/mod-install.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/reference/modding/mod-install.rst b/docs/source/reference/modding/mod-install.rst index 2c883aa599..c15d1b268a 100644 --- a/docs/source/reference/modding/mod-install.rst +++ b/docs/source/reference/modding/mod-install.rst @@ -11,6 +11,7 @@ Install #. Locate the plugin files, ``.esp`` or ``.omwaddon``, or possibly ``.esm``. The folder containing the plugin files we will call your *data folder* #. Check that all resource folders (``Meshes``, ``Textures``, etc.) containing additional resource files (the actual meshes, textures, etc.) are in the *data folder*. + #. Note that not all mods have a plugin, and not all mods have resources, but they must at minimum have one or the other. .. note:: There may be multiple levels of folders, but the location of the plugins must be the same as the resource folders. From 63f01d8c5f3dcc86826acd36ca244a17e845201c Mon Sep 17 00:00:00 2001 From: wareya Date: Sun, 21 Mar 2021 20:45:46 +0000 Subject: [PATCH 28/49] Prevent physics death spiral by falling back to true delta time when needed --- CHANGELOG.md | 2 + apps/openmw/mwphysics/mtphysics.cpp | 77 ++++++++++++++++++++++++- apps/openmw/mwphysics/mtphysics.hpp | 13 ++++- apps/openmw/mwphysics/physicssystem.cpp | 13 ++--- apps/openmw/mwphysics/physicssystem.hpp | 2 +- components/misc/budgetmeasurement.hpp | 42 ++++++++++++++ 6 files changed, 134 insertions(+), 15 deletions(-) create mode 100644 components/misc/budgetmeasurement.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 0891ecba99..4ac01c00ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ Bug #5424: Creatures do not headtrack player Bug #5425: Poison effect only appears for one frame Bug #5427: GetDistance unknown ID error is misleading + Bug #5431: Physics performance degradation after a specific number of actors on a scene Bug #5435: Enemies can't hurt the player when collision is off Bug #5441: Enemies can't push a player character when in critical strike stance Bug #5451: Magic projectiles don't disappear with the caster @@ -143,6 +144,7 @@ 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 + Feature #5910: Fall back to delta time when physics can't keep up Task #5480: Drop Qt4 support Task #5520: Improve cell name autocompleter implementation Task #5844: Update 'toggle sneak' documentation diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 1fa6251f83..11eb7f909c 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -101,7 +101,7 @@ namespace osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt) { - const float interpolationFactor = timeAccum / physicsDt; + const float interpolationFactor = std::clamp(timeAccum / physicsDt, 0.0f, 1.0f); return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor); } @@ -138,7 +138,8 @@ namespace namespace MWPhysics { PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, std::shared_ptr collisionWorld) - : mPhysicsDt(physicsDt) + : mDefaultPhysicsDt(physicsDt) + , mPhysicsDt(physicsDt) , mTimeAccum(0.f) , mCollisionWorld(std::move(collisionWorld)) , mNumJobs(0) @@ -152,6 +153,11 @@ namespace MWPhysics , mNextLOS(0) , mFrameNumber(0) , mTimer(osg::Timer::instance()) + , mPrevStepCount(1) + , mBudget(physicsDt) + , mAsyncBudget(0.0f) + , mBudgetCursor(0) + , mAsyncStartTime(0) , mTimeBegin(0) , mTimeEnd(0) , mFrameStart(0) @@ -220,13 +226,61 @@ namespace MWPhysics thread.join(); } - const std::vector& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) + std::tuple PhysicsTaskScheduler::calculateStepConfig(float timeAccum) const + { + int maxAllowedSteps = 2; + int numSteps = timeAccum / mDefaultPhysicsDt; + + // adjust maximum step count based on whether we're likely physics bottlenecked or not + // if maxAllowedSteps ends up higher than numSteps, we will not invoke delta time + // if it ends up lower than numSteps, but greater than 1, we will run a number of true delta time physics steps that we expect to be within budget + // if it ends up lower than numSteps and also 1, we will run a single delta time physics step + // if we did not do this, and had a fixed step count limit, + // we would have an unnecessarily low render framerate if we were only physics bottlenecked, + // and we would be unnecessarily invoking true delta time if we were only render bottlenecked + + // get physics timing stats + float budgetMeasurement = std::max(mBudget.get(), mAsyncBudget.get()); + // time spent per step in terms of the intended physics framerate + budgetMeasurement /= mDefaultPhysicsDt; + // ensure sane minimum value + budgetMeasurement = std::max(0.00001f, budgetMeasurement); + // we're spending almost or more than realtime per physics frame; limit to a single step + if (budgetMeasurement > 0.95) + maxAllowedSteps = 1; + // physics is fairly cheap; limit based on expense + if (budgetMeasurement < 0.5) + maxAllowedSteps = std::ceil(1.0/budgetMeasurement); + // limit to a reasonable amount + maxAllowedSteps = std::min(10, maxAllowedSteps); + + // fall back to delta time for this frame if fixed timestep physics would fall behind + float actualDelta = mDefaultPhysicsDt; + if (numSteps > maxAllowedSteps) + { + numSteps = maxAllowedSteps; + // ensure that we do not simulate a frame ahead when doing delta time; this reduces stutter and latency + // this causes interpolation to 100% use the most recent physics result when true delta time is happening + // and we deliberately simulate up to exactly the timestamp that we want to render + actualDelta = timeAccum/float(numSteps+1); + // actually: if this results in a per-step delta less than the target physics steptime, clamp it + // this might reintroduce some stutter, but only comes into play in obscure cases + // (because numSteps is originally based on mDefaultPhysicsDt, this won't cause us to overrun) + actualDelta = std::max(actualDelta, mDefaultPhysicsDt); + } + + return std::make_tuple(numSteps, actualDelta); + } + + const std::vector& PhysicsTaskScheduler::moveActors(float & timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { // This function run in the main thread. // While the mSimulationMutex is held, background physics threads can't run. std::unique_lock lock(mSimulationMutex); + double timeStart = mTimer->tick(); + mMovedActors.clear(); // start by finishing previous background computation @@ -251,14 +305,21 @@ namespace MWPhysics mMovedActors.emplace_back(data.mActorRaw->getPtr()); } } + if(mAdvanceSimulation) + mAsyncBudget.update(mTimer->delta_s(mAsyncStartTime, mTimeEnd), mPrevStepCount, mBudgetCursor); updateStats(frameStart, frameNumber, stats); } + auto [numSteps, newDelta] = calculateStepConfig(timeAccum); + timeAccum -= numSteps*newDelta; + // init for (auto& data : actorsData) data.updatePosition(); + mPrevStepCount = numSteps; mRemainingSteps = numSteps; mTimeAccum = timeAccum; + mPhysicsDt = newDelta; mActorsFrameData = std::move(actorsData); mAdvanceSimulation = (mRemainingSteps != 0); mNewFrame = true; @@ -269,20 +330,30 @@ namespace MWPhysics if (mAdvanceSimulation) mWorldFrameData = std::make_unique(); + if (mAdvanceSimulation) + mBudgetCursor += 1; + if (mNumThreads == 0) { syncComputation(); + if(mAdvanceSimulation) + mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), numSteps, mBudgetCursor); return mMovedActors; } + mAsyncStartTime = mTimer->tick(); lock.unlock(); mHasJob.notify_all(); + if (mAdvanceSimulation) + mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), 1, mBudgetCursor); return mMovedActors; } const std::vector& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors) { std::unique_lock lock(mSimulationMutex); + mBudget.reset(mDefaultPhysicsDt); + mAsyncBudget.reset(0.0f); mMovedActors.clear(); mActorsFrameData.clear(); for (const auto& [_, actor] : actors) diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index b35ebd5ee9..137755c21e 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -13,6 +13,7 @@ #include "physicssystem.hpp" #include "ptrholder.hpp" +#include "components/misc/budgetmeasurement.hpp" namespace Misc { @@ -32,7 +33,7 @@ namespace MWPhysics /// @param timeAccum accumulated time from previous run to interpolate movements /// @param actorsData per actor data needed to compute new positions /// @return new position of each actor - const std::vector& moveActors(int numSteps, float timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); + const std::vector& moveActors(float & timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); const std::vector& resetSimulation(const ActorMap& actors); @@ -58,11 +59,13 @@ namespace MWPhysics void updateAabbs(); void updatePtrAabb(const std::weak_ptr& ptr); void updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); + std::tuple calculateStepConfig(float timeAccum) const; std::unique_ptr mWorldFrameData; std::vector mActorsFrameData; std::vector mMovedActors; - const float mPhysicsDt; + float mDefaultPhysicsDt; + float mPhysicsDt; float mTimeAccum; std::shared_ptr mCollisionWorld; std::vector mLOSCache; @@ -94,6 +97,12 @@ namespace MWPhysics unsigned int mFrameNumber; const osg::Timer* mTimer; + + int mPrevStepCount; + Misc::BudgetMeasurement mBudget; + Misc::BudgetMeasurement mAsyncBudget; + unsigned int mBudgetCursor; + osg::Timer_t mAsyncStartTime; osg::Timer_t mTimeBegin; osg::Timer_t mTimeEnd; osg::Timer_t mFrameStart; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index dc9ab629a1..65ed13f802 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -744,19 +744,14 @@ namespace MWPhysics { mTimeAccum += dt; - const int maxAllowedSteps = 20; - int numSteps = mTimeAccum / mPhysicsDt; - numSteps = std::min(numSteps, maxAllowedSteps); - - mTimeAccum -= numSteps * mPhysicsDt; - if (skipSimulation) return mTaskScheduler->resetSimulation(mActors); - return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(numSteps), frameStart, frameNumber, stats); + // modifies mTimeAccum + return mTaskScheduler->moveActors(mTimeAccum, prepareFrameData(mTimeAccum >= mPhysicsDt), frameStart, frameNumber, stats); } - std::vector PhysicsSystem::prepareFrameData(int numSteps) + std::vector PhysicsSystem::prepareFrameData(bool willSimulate) { std::vector actorsFrameData; actorsFrameData.reserve(mMovementQueue.size()); @@ -796,7 +791,7 @@ namespace MWPhysics // Ue current value only if we don't advance the simulation. Otherwise we might get a stale value. MWWorld::Ptr standingOn; - if (numSteps == 0) + if (!willSimulate) standingOn = physicActor->getStandingOnPtr(); actorsFrameData.emplace_back(std::move(physicActor), standingOn, moveToWaterSurface, movement, slowFall, waterlevel); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 80b2d98bcf..3548239867 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -252,7 +252,7 @@ namespace MWPhysics void updateWater(); - std::vector prepareFrameData(int numSteps); + std::vector prepareFrameData(bool willSimulate); osg::ref_ptr mUnrefQueue; diff --git a/components/misc/budgetmeasurement.hpp b/components/misc/budgetmeasurement.hpp new file mode 100644 index 0000000000..3d56477af1 --- /dev/null +++ b/components/misc/budgetmeasurement.hpp @@ -0,0 +1,42 @@ +#ifndef OPENMW_COMPONENTS_MISC_BUDGETMEASUREMENT_H +#define OPENMW_COMPONENTS_MISC_BUDGETMEASUREMENT_H + + +namespace Misc +{ + +class BudgetMeasurement +{ + std::array mBudgetHistory; + std::array mBudgetStepCount; + +public: + BudgetMeasurement(const float default_expense) + { + mBudgetHistory = {default_expense, default_expense, default_expense, default_expense}; + mBudgetStepCount = {1, 1, 1, 1}; + } + + void reset(const float default_expense) + { + mBudgetHistory = {default_expense, default_expense, default_expense, default_expense}; + mBudgetStepCount = {1, 1, 1, 1}; + } + + void update(double delta, unsigned int stepCount, size_t cursor) + { + mBudgetHistory[cursor%4] = delta; + mBudgetStepCount[cursor%4] = stepCount; + } + + double get() const + { + float sum = (mBudgetHistory[0] + mBudgetHistory[1] + mBudgetHistory[2] + mBudgetHistory[3]); + unsigned int stepCountSum = (mBudgetStepCount[0] + mBudgetStepCount[1] + mBudgetStepCount[2] + mBudgetStepCount[3]); + return sum/float(stepCountSum); + } +}; + +} + +#endif From 4862e8c8f492548f9e8b3c4e0d1aa962179b0f2e Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 21 Mar 2021 21:30:17 +0000 Subject: [PATCH 29/49] Bump aqt version --- CI/before_script.msvc.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index dba87128a3..b2b6483ec7 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -848,9 +848,11 @@ fi wrappedExit 1 fi - if ! [ -e "aqt-venv/${VENV_BIN_DIR}/aqt" ]; then + # check version + pip list | grep 'aqtinstall\s*1.1.3' + if [ $? -ne 0 ]; then echo " Installing aqt wheel into virtualenv..." - run_cmd "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall==0.9.2 + run_cmd "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall==1.1.3 fi popd > /dev/null From 2fdbe9b3f6a5b47ae692148dd0a8651c2836d5f6 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Thu, 17 Dec 2020 00:46:09 +0300 Subject: [PATCH 30/49] Handle BSShader[PP/No]LightingProperty --- components/nif/niffile.cpp | 2 + components/nif/property.cpp | 32 ++++- components/nif/property.hpp | 42 ++++++- components/nif/record.hpp | 4 +- components/nifosg/nifloader.cpp | 139 ++++++++++++++++++++++ components/resource/scenemanager.cpp | 2 +- components/shader/shadervisitor.cpp | 17 ++- components/shader/shadervisitor.hpp | 5 +- files/shaders/CMakeLists.txt | 4 + files/shaders/nv_default_fragment.glsl | 106 +++++++++++++++++ files/shaders/nv_default_vertex.glsl | 58 +++++++++ files/shaders/nv_nolighting_fragment.glsl | 55 +++++++++ files/shaders/nv_nolighting_vertex.glsl | 49 ++++++++ files/shaders/objects_fragment.glsl | 5 +- files/shaders/objects_vertex.glsl | 4 +- 15 files changed, 509 insertions(+), 15 deletions(-) create mode 100644 files/shaders/nv_default_fragment.glsl create mode 100644 files/shaders/nv_default_vertex.glsl create mode 100644 files/shaders/nv_nolighting_fragment.glsl create mode 100644 files/shaders/nv_nolighting_vertex.glsl diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 665533c91b..3e226b35e1 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -133,6 +133,8 @@ static std::map makeFactory() factory["BSShaderTextureSet"] = {&construct , RC_BSShaderTextureSet }; factory["BSLODTriShape"] = {&construct , RC_BSLODTriShape }; factory["BSShaderProperty"] = {&construct , RC_BSShaderProperty }; + factory["BSShaderPPLightingProperty"] = {&construct , RC_BSShaderPPLightingProperty }; + factory["BSShaderNoLightingProperty"] = {&construct , RC_BSShaderNoLightingProperty }; return factory; } diff --git a/components/nif/property.cpp b/components/nif/property.cpp index d5357e1230..1d2dd885d4 100644 --- a/components/nif/property.cpp +++ b/components/nif/property.cpp @@ -118,6 +118,34 @@ void BSShaderLightingProperty::read(NIFStream *nif) clamp = nif->getUInt(); } +void BSShaderPPLightingProperty::read(NIFStream *nif) +{ + BSShaderLightingProperty::read(nif); + textureSet.read(nif); + if (nif->getBethVersion() <= 14) + return; + refraction.strength = nif->getFloat(); + refraction.period = nif->getInt(); + if (nif->getBethVersion() <= 24) + return; + parallax.passes = nif->getFloat(); + parallax.scale = nif->getFloat(); +} + +void BSShaderPPLightingProperty::post(NIFFile *nif) +{ + BSShaderLightingProperty::post(nif); + textureSet.post(nif); +} + +void BSShaderNoLightingProperty::read(NIFStream *nif) +{ + BSShaderLightingProperty::read(nif); + filename = nif->getSizedString(); + if (nif->getBethVersion() >= 27) + falloffParams = nif->getVector4(); +} + void NiFogProperty::read(NIFStream *nif) { Property::read(nif); @@ -137,8 +165,8 @@ void S_MaterialProperty::read(NIFStream *nif) emissive = nif->getVector3(); glossiness = nif->getFloat(); alpha = nif->getFloat(); - if (nif->getBethVersion() > 21) - emissive *= nif->getFloat(); + if (nif->getBethVersion() >= 22) + emissiveMult = nif->getFloat(); } void S_VertexColorProperty::read(NIFStream *nif) diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 008e84515c..9c76f6d6ec 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -118,6 +118,18 @@ struct NiShadeProperty : public Property struct BSShaderProperty : public NiShadeProperty { + enum BSShaderType + { + SHADER_TALL_GRASS = 0, + SHADER_DEFAULT = 1, + SHADER_SKY = 10, + SHADER_SKIN = 14, + SHADER_WATER = 17, + SHADER_LIGHTING30 = 29, + SHADER_TILE = 32, + SHADER_NOLIGHTING = 33 + }; + unsigned int type{0u}, flags1{0u}, flags2{0u}; float envMapIntensity{0.f}; void read(NIFStream *nif) override; @@ -129,6 +141,34 @@ struct BSShaderLightingProperty : public BSShaderProperty void read(NIFStream *nif) override; }; +struct BSShaderPPLightingProperty : public BSShaderLightingProperty +{ + BSShaderTextureSetPtr textureSet; + struct RefractionSettings + { + float strength{0.f}; + int period{0}; + }; + struct ParallaxSettings + { + float passes{0.f}; + float scale{0.f}; + }; + RefractionSettings refraction; + ParallaxSettings parallax; + + void read(NIFStream *nif) override; + void post(NIFFile *nif) override; +}; + +struct BSShaderNoLightingProperty : public BSShaderLightingProperty +{ + std::string filename; + osg::Vec4f falloffParams; + + void read(NIFStream *nif) override; +}; + struct NiDitherProperty : public Property { unsigned short flags; @@ -193,7 +233,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{0.f}, alpha{0.f}; + float glossiness{0.f}, alpha{0.f}, emissiveMult{1.f}; void read(NIFStream *nif); }; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index efacd82462..ed97acabc6 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -122,7 +122,9 @@ enum RecordType RC_NiColorInterpolator, RC_BSShaderTextureSet, RC_BSLODTriShape, - RC_BSShaderProperty + RC_BSShaderProperty, + RC_BSShaderPPLightingProperty, + RC_BSShaderNoLightingProperty }; /// Base class for all records diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 3e34969f4a..ae1726f065 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1672,6 +1672,85 @@ namespace NifOsg handleTextureControllers(texprop, composite, imageManager, stateset, animflags); } + void handleTextureSet(const Nif::BSShaderTextureSet* textureSet, unsigned int clamp, const std::string& nodeName, osg::StateSet* stateset, Resource::ImageManager* imageManager, std::vector& boundTextures) + { + if (!boundTextures.empty()) + { + for (unsigned int i = 0; i < boundTextures.size(); ++i) + stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); + boundTextures.clear(); + } + + const unsigned int uvSet = 0; + + for (size_t i = 0; i < textureSet->textures.size(); ++i) + { + if (textureSet->textures[i].empty()) + continue; + switch(i) + { + case Nif::BSShaderTextureSet::TextureType_Base: + case Nif::BSShaderTextureSet::TextureType_Normal: + case Nif::BSShaderTextureSet::TextureType_Glow: + break; + default: + { + Log(Debug::Info) << "Unhandled texture stage " << i << " on shape \"" << nodeName << "\" in " << mFilename; + continue; + } + } + std::string filename = Misc::ResourceHelpers::correctTexturePath(textureSet->textures[i], imageManager->getVFS()); + osg::ref_ptr image = imageManager->getImage(filename); + osg::ref_ptr texture2d = new osg::Texture2D(image); + if (image) + texture2d->setTextureSize(image->s(), image->t()); + bool wrapT = clamp & 0x1; + bool wrapS = (clamp >> 1) & 0x1; + texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); + texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); + unsigned int texUnit = boundTextures.size(); + stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); + // BSShaderTextureSet presence means there's no need for FFP support for the affected node + switch (i) + { + case Nif::BSShaderTextureSet::TextureType_Base: + texture2d->setName("diffuseMap"); + break; + case Nif::BSShaderTextureSet::TextureType_Normal: + texture2d->setName("normalMap"); + break; + case Nif::BSShaderTextureSet::TextureType_Glow: + texture2d->setName("emissiveMap"); + break; + } + boundTextures.emplace_back(uvSet); + } + } + + const std::string& getNVShaderPrefix(unsigned int type) const + { + static const std::map mapping = + { + {Nif::BSShaderProperty::SHADER_TALL_GRASS, std::string()}, + {Nif::BSShaderProperty::SHADER_DEFAULT, "nv_default"}, + {Nif::BSShaderProperty::SHADER_SKY, std::string()}, + {Nif::BSShaderProperty::SHADER_SKIN, std::string()}, + {Nif::BSShaderProperty::SHADER_WATER, std::string()}, + {Nif::BSShaderProperty::SHADER_LIGHTING30, std::string()}, + {Nif::BSShaderProperty::SHADER_TILE, std::string()}, + {Nif::BSShaderProperty::SHADER_NOLIGHTING, "nv_nolighting"}, + }; + auto prefix = mapping.find(type); + if (prefix == mapping.end()) + Log(Debug::Warning) << "Unknown shader type " << type << " in " << mFilename; + else if (prefix->second.empty()) + Log(Debug::Warning) << "Unhandled shader type " << type << " in " << mFilename; + else + return prefix->second; + + return mapping.at(Nif::BSShaderProperty::SHADER_DEFAULT); + } + void handleProperty(const Nif::Property *property, osg::Node *node, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) { @@ -1757,6 +1836,63 @@ namespace NifOsg handleTextureProperty(texprop, node->getName(), stateset, composite, imageManager, boundTextures, animflags); break; } + case Nif::RC_BSShaderPPLightingProperty: + { + auto texprop = static_cast(property); + bool shaderRequired = true; + node->setUserValue("shaderPrefix", getNVShaderPrefix(texprop->type)); + node->setUserValue("shaderRequired", shaderRequired); + osg::StateSet* stateset = node->getOrCreateStateSet(); + if (!texprop->textureSet.empty()) + { + auto textureSet = texprop->textureSet.getPtr(); + handleTextureSet(textureSet, texprop->clamp, node->getName(), stateset, imageManager, boundTextures); + } + handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + break; + } + case Nif::RC_BSShaderNoLightingProperty: + { + auto texprop = static_cast(property); + bool shaderRequired = true; + node->setUserValue("shaderPrefix", getNVShaderPrefix(texprop->type)); + node->setUserValue("shaderRequired", shaderRequired); + osg::StateSet* stateset = node->getOrCreateStateSet(); + if (!texprop->filename.empty()) + { + if (!boundTextures.empty()) + { + for (unsigned int i = 0; i < boundTextures.size(); ++i) + stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); + boundTextures.clear(); + } + std::string filename = Misc::ResourceHelpers::correctTexturePath(texprop->filename, imageManager->getVFS()); + osg::ref_ptr image = imageManager->getImage(filename); + osg::ref_ptr texture2d = new osg::Texture2D(image); + texture2d->setName("diffuseMap"); + if (image) + texture2d->setTextureSize(image->s(), image->t()); + bool wrapT = texprop->clamp & 0x1; + bool wrapS = (texprop->clamp >> 1) & 0x1; + texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); + texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); + const unsigned int texUnit = 0; + const unsigned int uvSet = 0; + stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); + boundTextures.push_back(uvSet); + } + if (mBethVersion >= 27) + { + stateset->addUniform(new osg::Uniform("useFalloff", true)); + stateset->addUniform(new osg::Uniform("falloffParams", texprop->falloffParams)); + } + else + { + stateset->addUniform(new osg::Uniform("useFalloff", false)); + } + handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + break; + } // unused by mw case Nif::RC_NiShadeProperty: case Nif::RC_NiDitherProperty: @@ -1809,6 +1945,7 @@ namespace NifOsg bool hasMatCtrl = false; int lightmode = 1; + float emissiveMult = 1.f; for (const Nif::Property* property : properties) { @@ -1828,6 +1965,7 @@ namespace NifOsg mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.diffuse, matprop->data.alpha)); mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.ambient, 1.f)); mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.emissive, 1.f)); + emissiveMult = matprop->data.emissiveMult; mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.specular, 1.f)); mat->setShininess(osg::Material::FRONT_AND_BACK, matprop->data.glossiness); @@ -1948,6 +2086,7 @@ namespace NifOsg mat = shareAttribute(mat); stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); + stateset->addUniform(new osg::Uniform("emissiveMult", emissiveMult)); } }; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index e46ce20168..66d48f9715 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -774,7 +774,7 @@ namespace Resource 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"); + Shader::ShaderVisitor* shaderVisitor = new Shader::ShaderVisitor(*mShaderManager.get(), *mImageManager, shaderPrefix); shaderVisitor->setForceShaders(mForceShaders); shaderVisitor->setAutoUseNormalMaps(mAutoUseNormalMaps); shaderVisitor->setNormalMapPattern(mNormalMapPattern); diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index eae9ad2dbb..1af9f94c57 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -42,7 +42,7 @@ namespace Shader } - ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string &defaultVsTemplate, const std::string &defaultFsTemplate) + ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string &defaultShaderPrefix) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mForceShaders(false) , mAllowedToModifyStateSets(true) @@ -52,8 +52,7 @@ namespace Shader , mTranslucentFramebuffer(false) , mShaderManager(shaderManager) , mImageManager(imageManager) - , mDefaultVsTemplate(defaultVsTemplate) - , mDefaultFsTemplate(defaultFsTemplate) + , mDefaultShaderPrefix(defaultShaderPrefix) { mRequirements.emplace_back(); } @@ -129,6 +128,10 @@ namespace Shader if (mAllowedToModifyStateSets) writableStateSet = node.getStateSet(); const osg::StateSet::TextureAttributeList& texAttributes = stateset->getTextureAttributeList(); + bool shaderRequired = false; + if (node.getUserValue("shaderRequired", shaderRequired) && shaderRequired) + mRequirements.back().mShaderRequired = true; + if (!texAttributes.empty()) { const osg::Texture* diffuseMap = nullptr; @@ -440,8 +443,12 @@ namespace Shader 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)); + std::string shaderPrefix; + if (!node.getUserValue("shaderPrefix", shaderPrefix)) + shaderPrefix = mDefaultShaderPrefix; + + osg::ref_ptr vertexShader (mShaderManager.getShader(shaderPrefix + "_vertex.glsl", defineMap, osg::Shader::VERTEX)); + osg::ref_ptr fragmentShader (mShaderManager.getShader(shaderPrefix + "_fragment.glsl", defineMap, osg::Shader::FRAGMENT)); if (vertexShader && fragmentShader) { diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index f7c6f83127..30ff41a33c 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -17,7 +17,7 @@ namespace Shader class ShaderVisitor : public osg::NodeVisitor { public: - ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string& defaultVsTemplate, const std::string& defaultFsTemplate); + ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string& defaultShaderPrefix); /// By default, only bump mapped objects will have a shader added to them. /// Setting force = true will cause all objects to render using shaders, regardless of having a bump map. @@ -104,8 +104,7 @@ namespace Shader }; std::vector mRequirements; - std::string mDefaultVsTemplate; - std::string mDefaultFsTemplate; + std::string mDefaultShaderPrefix; void createProgram(const ShaderRequirements& reqs); void ensureFFP(osg::Node& node); diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 7250aa3726..e06dfe56a2 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -26,6 +26,10 @@ set(SHADER_FILES shadowcasting_vertex.glsl shadowcasting_fragment.glsl vertexcolors.glsl + nv_default_vertex.glsl + nv_default_fragment.glsl + nv_nolighting_vertex.glsl + nv_nolighting_fragment.glsl ) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") diff --git a/files/shaders/nv_default_fragment.glsl b/files/shaders/nv_default_fragment.glsl new file mode 100644 index 0000000000..03fa378a6d --- /dev/null +++ b/files/shaders/nv_default_fragment.glsl @@ -0,0 +1,106 @@ +#version 120 + +#if @useGPUShader4 + #extension GL_EXT_gpu_shader4: require +#endif + +#if @diffuseMap +uniform sampler2D diffuseMap; +varying vec2 diffuseMapUV; +#endif + +#if @emissiveMap +uniform sampler2D emissiveMap; +varying vec2 emissiveMapUV; +#endif + +#if @normalMap +uniform sampler2D normalMap; +varying vec2 normalMapUV; +varying vec4 passTangent; +#endif + +uniform bool noAlpha; + +varying float euclideanDepth; +varying float linearDepth; + +#define PER_PIXEL_LIGHTING 1 + +varying vec3 passViewPos; +varying vec3 passNormal; + +#include "vertexcolors.glsl" +#include "shadows_fragment.glsl" +#include "lighting.glsl" +#include "alpha.glsl" + +uniform float emissiveMult; + +void main() +{ +#if @diffuseMap + gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); + gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, adjustedDiffuseUV); +#else + gl_FragData[0] = vec4(1.0); +#endif + + vec4 diffuseColor = getDiffuseColor(); + gl_FragData[0].a *= diffuseColor.a; + alphaTest(); + +#if @normalMap + vec4 normalTex = texture2D(normalMap, normalMapUV); + // Must flip Y for DirectX format normal maps + normalTex.y = 1.0 - normalTex.y; + + 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)); +#else + vec3 viewNormal = gl_NormalMatrix * normalize(passNormal); +#endif + + float shadowing = unshadowedLightRatio(linearDepth); + vec3 diffuseLight, ambientLight; + doLighting(passViewPos, normalize(viewNormal), shadowing, diffuseLight, ambientLight); + vec3 emission = getEmissionColor().xyz * emissiveMult; +#if @emissiveMap + emission *= texture2D(emissiveMap, emissiveMapUV).xyz; +#endif + vec3 lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + emission; + +#if @clamp + lighting = clamp(lighting, vec3(0.0), vec3(1.0)); +#else + lighting = max(lighting, 0.0); +#endif + + gl_FragData[0].xyz *= lighting; + + float shininess = gl_FrontMaterial.shininess; + vec3 matSpec = getSpecularColor().xyz; +#if @normalMap + matSpec *= normalTex.a; +#endif + + if (matSpec != vec3(0.0)) + gl_FragData[0].xyz += getSpecular(normalize(viewNormal), normalize(passViewPos.xyz), shininess, matSpec) * shadowing; +#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); + +#if @translucentFramebuffer + if (noAlpha) + gl_FragData[0].a = 1.0; +#endif + + applyShadowDebugOverlay(); +} diff --git a/files/shaders/nv_default_vertex.glsl b/files/shaders/nv_default_vertex.glsl new file mode 100644 index 0000000000..7c9d434f18 --- /dev/null +++ b/files/shaders/nv_default_vertex.glsl @@ -0,0 +1,58 @@ +#version 120 + +#if @diffuseMap +varying vec2 diffuseMapUV; +#endif + +#if @emissiveMap +varying vec2 emissiveMapUV; +#endif + +#if @normalMap +varying vec2 normalMapUV; +varying vec4 passTangent; +#endif + +varying float euclideanDepth; +varying float linearDepth; + +varying vec3 passViewPos; +varying vec3 passNormal; + +#define PER_PIXEL_LIGHTING 1 + +#include "vertexcolors.glsl" +#include "shadows_vertex.glsl" +#include "lighting.glsl" + +void main(void) +{ + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + + vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); + gl_ClipVertex = viewPos; + euclideanDepth = length(viewPos.xyz); + linearDepth = gl_Position.z; + +#if @diffuseMap + diffuseMapUV = (gl_TextureMatrix[@diffuseMapUV] * gl_MultiTexCoord@diffuseMapUV).xy; +#endif + +#if @emissiveMap + emissiveMapUV = (gl_TextureMatrix[@emissiveMapUV] * gl_MultiTexCoord@emissiveMapUV).xy; +#endif + +#if @normalMap + normalMapUV = (gl_TextureMatrix[@normalMapUV] * gl_MultiTexCoord@normalMapUV).xy; + passTangent = gl_MultiTexCoord7.xyzw; +#endif + + passColor = gl_Color; + passViewPos = viewPos.xyz; + passNormal = gl_Normal.xyz; + +#if @shadows_enabled + vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); + setupShadowCoords(viewPos, viewNormal); +#endif +} diff --git a/files/shaders/nv_nolighting_fragment.glsl b/files/shaders/nv_nolighting_fragment.glsl new file mode 100644 index 0000000000..27679a069f --- /dev/null +++ b/files/shaders/nv_nolighting_fragment.glsl @@ -0,0 +1,55 @@ +#version 120 + +#if @useGPUShader4 + #extension GL_EXT_gpu_shader4: require +#endif + +#if @diffuseMap +uniform sampler2D diffuseMap; +varying vec2 diffuseMapUV; +#endif + +uniform bool noAlpha; + +#if @radialFog +varying float euclideanDepth; +#else +varying float linearDepth; +#endif + +uniform bool useFalloff; + +varying float passFalloff; + +#include "vertexcolors.glsl" +#include "alpha.glsl" + +void main() +{ +#if @diffuseMap + gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); + gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, diffuseMapUV); +#else + gl_FragData[0] = vec4(1.0); +#endif + + gl_FragData[0] *= getDiffuseColor(); + + if (useFalloff) + gl_FragData[0].a *= passFalloff; + + alphaTest(); + +#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 + +#if @translucentFramebuffer + if (noAlpha) + gl_FragData[0].a = 1.0; +#endif + + gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); +} diff --git a/files/shaders/nv_nolighting_vertex.glsl b/files/shaders/nv_nolighting_vertex.glsl new file mode 100644 index 0000000000..275f1e5730 --- /dev/null +++ b/files/shaders/nv_nolighting_vertex.glsl @@ -0,0 +1,49 @@ +#version 120 + +#if @diffuseMap +varying vec2 diffuseMapUV; +#endif + +#if @radialFog +varying float euclideanDepth; +#else +varying float linearDepth; +#endif + +uniform bool useFalloff; +uniform vec4 falloffParams; + +varying vec3 passViewPos; +varying float passFalloff; + +#include "vertexcolors.glsl" + +void main(void) +{ + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + + vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); + gl_ClipVertex = viewPos; +#if @radialFog + euclideanDepth = length(viewPos.xyz); +#else + linearDepth = gl_Position.z; +#endif + +#if @diffuseMap + diffuseMapUV = (gl_TextureMatrix[@diffuseMapUV] * gl_MultiTexCoord@diffuseMapUV).xy; +#endif + + passColor = gl_Color; + if (useFalloff) + { + vec3 viewNormal = gl_NormalMatrix * normalize(gl_Normal.xyz); + vec3 viewDir = normalize(viewPos.xyz); + float viewAngle = abs(dot(viewNormal, viewDir)); + passFalloff = smoothstep(falloffParams.y, falloffParams.x, viewAngle); + } + else + { + passFalloff = 1.0; + } +} diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index e0d7833c9e..6b67be937a 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -64,6 +64,8 @@ varying float linearDepth; #if !PER_PIXEL_LIGHTING centroid varying vec3 passLighting; centroid varying vec3 shadowDiffuseLighting; +#else +uniform float emissiveMult; #endif varying vec3 passViewPos; varying vec3 passNormal; @@ -168,7 +170,8 @@ void main() #else vec3 diffuseLight, ambientLight; doLighting(passViewPos, normalize(viewNormal), shadowing, diffuseLight, ambientLight); - lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz; + vec3 emission = getEmissionColor().xyz * emissiveMult; + lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + emission; #endif #if @clamp diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 15467933b7..bf5bdb40c2 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -45,6 +45,7 @@ varying float linearDepth; #if !PER_PIXEL_LIGHTING centroid varying vec3 passLighting; centroid varying vec3 shadowDiffuseLighting; +uniform float emissiveMult; #endif varying vec3 passViewPos; varying vec3 passNormal; @@ -114,7 +115,8 @@ void main(void) #if !PER_PIXEL_LIGHTING vec3 diffuseLight, ambientLight; doLighting(viewPos.xyz, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting); - passLighting = getDiffuseColor().xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz; + vec3 emission = getEmissionColor().xyz * emissiveMult; + passLighting = getDiffuseColor().xyz * diffuseLight + getAmbientColor().xyz * ambientLight + emission; shadowDiffuseLighting *= getDiffuseColor().xyz; #endif From 580fa78034faaa6b51ca0ae6fdd631ef2e0ef69f Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 22 Mar 2021 20:43:34 +0100 Subject: [PATCH 31/49] Don't purge summon effects with invalid creature ids --- apps/openmw/mwmechanics/summoning.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index 0f699ccade..9f65f3d6c9 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -145,6 +145,12 @@ namespace MWMechanics for (std::map::iterator it = creatureMap.begin(); it != creatureMap.end(); ) { + if(it->second == -1) + { + // Keep the spell effect active if we failed to spawn anything + it++; + continue; + } MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second); if (ptr.isEmpty() || (ptr.getClass().getCreatureStats(ptr).isDead() && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished())) { From 8e9bd5c0bd78da03ed9ae8c4fb1145c306afc2c5 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 22 Mar 2021 20:44:13 +0100 Subject: [PATCH 32/49] Don't throw an exception when equipping a bound item fails --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/actors.cpp | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ac01c00ff..6c36ce3eba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -112,6 +112,7 @@ Bug #5899: Visible modal windows and dropdowns crashing game on exit Bug #5902: NiZBufferProperty is unable to disable the depth test Bug #5906: Sunglare doesn't work with Mesa drivers and AMD GPUs + Bug #5912: ImprovedBound mod doesn't work Feature #390: 3rd person look "over the shoulder" Feature #832: OpenMW-CS: Handle deleted references Feature #1536: Show more information about level on menu diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index c0a1371585..a1e2d2a726 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -346,7 +346,11 @@ namespace MWMechanics if (actor != MWMechanics::getPlayer()) return; - MWWorld::Ptr newItem = *store.getSlot(slot); + MWWorld::Ptr newItem; + auto it = store.getSlot(slot); + // Equip can fail because beast races cannot equip boots/helmets + if(it != store.end()) + newItem = *it; if (newItem.isEmpty() || boundPtr != newItem) return; From 5e1960a76a3e8dcf63a1f12e654020364a8d3236 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 22 Mar 2021 22:29:10 +0100 Subject: [PATCH 33/49] Disallow inserting containers, creatures, and npcs from the save game not present in content files --- apps/openmw/mwworld/esmstore.cpp | 6 ++++-- apps/openmw/mwworld/store.cpp | 12 +++++++++--- apps/openmw/mwworld/store.hpp | 6 +++--- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 90bc80b484..5fa4684283 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -384,12 +384,14 @@ void ESMStore::validate() case ESM::REC_ENCH: case ESM::REC_SPEL: case ESM::REC_WEAP: - case ESM::REC_NPC_: case ESM::REC_LEVI: case ESM::REC_LEVC: + mStores[type]->read (reader); + return true; + case ESM::REC_NPC_: case ESM::REC_CREA: case ESM::REC_CONT: - mStores[type]->read (reader); + mStores[type]->read (reader, true); return true; case ESM::REC_DYNA: diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 69ca1f8c27..9d6552106b 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -250,9 +250,15 @@ namespace MWWorld } } template - T *Store::insert(const T &item) + T *Store::insert(const T &item, bool overrideOnly) { std::string id = Misc::StringUtils::lowerCase(item.mId); + if(overrideOnly) + { + auto it = mStatic.find(id); + if(it == mStatic.end()) + return nullptr; + } std::pair result = mDynamic.insert(std::pair(id, item)); T *ptr = &result.first->second; @@ -337,13 +343,13 @@ namespace MWWorld } } template - RecordId Store::read(ESM::ESMReader& reader) + RecordId Store::read(ESM::ESMReader& reader, bool overrideOnly) { T record; bool isDeleted = false; record.load (reader, isDeleted); - insert (record); + insert (record, overrideOnly); return RecordId(record.mId, isDeleted); } diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index cb9f4f1e02..9cb1c7473b 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -46,7 +46,7 @@ namespace MWWorld virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const {} - virtual RecordId read (ESM::ESMReader& reader) { return RecordId(); } + virtual RecordId read (ESM::ESMReader& reader, bool overrideOnly = false) { return RecordId(); } ///< Read into dynamic storage }; @@ -192,7 +192,7 @@ namespace MWWorld /// @note The record identifiers are listed in the order that the records were defined by the content files. void listIdentifier(std::vector &list) const override; - T *insert(const T &item); + T *insert(const T &item, bool overrideOnly = false); T *insertStatic(const T &item); bool eraseStatic(const std::string &id) override; @@ -201,7 +201,7 @@ namespace MWWorld RecordId load(ESM::ESMReader &esm) override; void write(ESM::ESMWriter& writer, Loading::Listener& progress) const override; - RecordId read(ESM::ESMReader& reader) override; + RecordId read(ESM::ESMReader& reader, bool overrideOnly = false) override; }; template <> From e79036f4e008b17c1b6c78ff0055951ede736e22 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 23 Mar 2021 20:43:52 +0100 Subject: [PATCH 34/49] Don't erase the player --- apps/openmw/mwworld/esmstore.hpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index d69c56d8c0..4d962b28c3 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -80,8 +80,6 @@ namespace MWWorld std::map mStores; - ESM::NPC mPlayerTemplate; - unsigned int mDynamicCount; mutable std::map > mSpellListCache; @@ -172,14 +170,13 @@ namespace MWWorld for (std::map::iterator it = mStores.begin(); it != mStores.end(); ++it) it->second->clearDynamic(); - mNpcs.insert(mPlayerTemplate); + movePlayerRecord(); } void movePlayerRecord () { - mPlayerTemplate = *mNpcs.find("player"); - mNpcs.eraseStatic(mPlayerTemplate.mId); - mNpcs.insert(mPlayerTemplate); + auto player = mNpcs.find("player"); + mNpcs.insert(*player); } void load(ESM::ESMReader &esm, Loading::Listener* listener); From cf5a93d7121412d6487f011451c873d07b83036b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 23 Mar 2021 21:07:57 +0100 Subject: [PATCH 35/49] Also run NPC validation for modified base records --- apps/openmw/mwbase/world.hpp | 2 + apps/openmw/mwstate/statemanagerimp.cpp | 1 + apps/openmw/mwworld/esmstore.cpp | 103 ++++++++++++++---------- apps/openmw/mwworld/esmstore.hpp | 3 + apps/openmw/mwworld/worldimp.cpp | 5 ++ apps/openmw/mwworld/worldimp.hpp | 2 + 6 files changed, 73 insertions(+), 43 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 27980f0705..5e844ffae7 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -443,6 +443,8 @@ namespace MWBase virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0; virtual void disableDeferredPreviewRotation() = 0; + virtual void saveLoaded() = 0; + virtual void setupPlayer() = 0; virtual void renderPlayer() = 0; diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index f605344bf7..93f75c6f10 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -505,6 +505,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str character->getPath().filename().string()); MWBase::Environment::get().getWindowManager()->setNewGame(false); + MWBase::Environment::get().getWorld()->saveLoaded(); MWBase::Environment::get().getWorld()->setupPlayer(); MWBase::Environment::get().getWorld()->renderPlayer(); MWBase::Environment::get().getWindowManager()->updatePlayer(); diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 5fa4684283..986c4f8580 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -48,6 +48,57 @@ namespace } } } + + std::vector getNPCsToReplace(const MWWorld::Store& factions, const MWWorld::Store& classes, const std::map& npcs) + { + // Cache first class from store - we will use it if current class is not found + std::string defaultCls; + auto it = classes.begin(); + if (it != classes.end()) + defaultCls = it->mId; + else + throw std::runtime_error("List of NPC classes is empty!"); + + // Validate NPCs for non-existing class and faction. + // We will replace invalid entries by fixed ones + std::vector npcsToReplace; + + for (auto it : npcs) + { + ESM::NPC npc = it.second; + bool changed = false; + + const std::string npcFaction = npc.mFaction; + if (!npcFaction.empty()) + { + const ESM::Faction *fact = factions.search(npcFaction); + if (!fact) + { + Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent faction '" << npc.mFaction << "', ignoring it."; + npc.mFaction.clear(); + npc.mNpdt.mRank = 0; + changed = true; + } + } + + std::string npcClass = npc.mClass; + if (!npcClass.empty()) + { + const ESM::Class *cls = classes.search(npcClass); + if (!cls) + { + Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent class '" << npc.mClass << "', using '" << defaultCls << "' class as replacement."; + npc.mClass = defaultCls; + changed = true; + } + } + + if (changed) + npcsToReplace.push_back(npc); + } + + return npcsToReplace; + } } namespace MWWorld @@ -218,49 +269,7 @@ int ESMStore::getRefCount(const std::string& id) const void ESMStore::validate() { - // Cache first class from store - we will use it if current class is not found - std::string defaultCls = ""; - Store::iterator it = mClasses.begin(); - if (it != mClasses.end()) - defaultCls = it->mId; - else - throw std::runtime_error("List of NPC classes is empty!"); - - // Validate NPCs for non-existing class and faction. - // We will replace invalid entries by fixed ones - std::vector npcsToReplace; - for (ESM::NPC npc : mNpcs) - { - bool changed = false; - - const std::string npcFaction = npc.mFaction; - if (!npcFaction.empty()) - { - const ESM::Faction *fact = mFactions.search(npcFaction); - if (!fact) - { - Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent faction '" << npc.mFaction << "', ignoring it."; - npc.mFaction.clear(); - npc.mNpdt.mRank = 0; - changed = true; - } - } - - std::string npcClass = npc.mClass; - if (!npcClass.empty()) - { - const ESM::Class *cls = mClasses.search(npcClass); - if (!cls) - { - Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent class '" << npc.mClass << "', using '" << defaultCls << "' class as replacement."; - npc.mClass = defaultCls; - changed = true; - } - } - - if (changed) - npcsToReplace.push_back(npc); - } + std::vector npcsToReplace = getNPCsToReplace(mFactions, mClasses, mNpcs.mStatic); for (const ESM::NPC &npc : npcsToReplace) { @@ -331,6 +340,14 @@ void ESMStore::validate() } } +void ESMStore::validateDynamic() +{ + std::vector npcsToReplace = getNPCsToReplace(mFactions, mClasses, mNpcs.mDynamic); + + for (const ESM::NPC &npc : npcsToReplace) + mNpcs.insert(npc); +} + int ESMStore::countSavedGameRecords() const { return 1 // DYNA (dynamic name counter) diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 4d962b28c3..26f497a527 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -179,6 +179,9 @@ namespace MWWorld mNpcs.insert(*player); } + /// Validate entries in store after loading a save + void validateDynamic(); + void load(ESM::ESMReader &esm, Loading::Listener* listener); template diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 01b90e9078..ce09cf1122 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2473,6 +2473,11 @@ namespace MWWorld mRendering->getCamera()->adjustCameraDistance(dist); } + void World::saveLoaded() + { + mStore.validateDynamic(); + } + void World::setupPlayer() { const ESM::NPC *player = mStore.get().find("player"); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 23153a31c4..e2d3449304 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -549,6 +549,8 @@ namespace MWWorld void applyDeferredPreviewRotationToPlayer(float dt) override; void disableDeferredPreviewRotation() override; + void saveLoaded() override; + void setupPlayer() override; void renderPlayer() override; From 313355cb3d72ba2ddd4b1557d801a0852151bf2f Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 23 Mar 2021 21:40:32 +0100 Subject: [PATCH 36/49] Fix default max tile size in navigator doc --- docs/source/reference/modding/settings/navigator.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/reference/modding/settings/navigator.rst b/docs/source/reference/modding/settings/navigator.rst index af40ac750f..fcef549d0e 100644 --- a/docs/source/reference/modding/settings/navigator.rst +++ b/docs/source/reference/modding/settings/navigator.rst @@ -305,7 +305,7 @@ tile size :Type: integer :Range: > 0 -:Default: 64 +:Default: 128 The width and height of each tile. From 39c0ce9ddf5d1c85c2e4cac4eb6b1f6ad980f2d0 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 23 Mar 2021 23:15:13 +0100 Subject: [PATCH 37/49] Build limited path for far destinations When distance between start and end point is greater than max radius of area possibly covered by navmesh there is no way to find path via navmesh. Also if distance is greater than cell size navmesh might not exists withing mentioned area because cell is not loaded therefore navmesh is not generated. So minumum of these values is used to limit max path distance. Assuming that path actually exists it's possible to build path to the edge of a circle. When actor reaches initial edge path is built further. However it will not be optimal. --- apps/openmw/mwmechanics/aipackage.cpp | 2 +- apps/openmw/mwmechanics/pathfinding.cpp | 17 +++++++++++++++++ apps/openmw/mwmechanics/pathfinding.hpp | 4 ++++ components/detournavigator/navigator.hpp | 2 ++ components/detournavigator/navigatorimpl.cpp | 6 ++++++ components/detournavigator/navigatorimpl.hpp | 2 ++ components/detournavigator/navigatorstub.hpp | 5 +++++ components/detournavigator/settingsutils.hpp | 12 +++++++++++- 8 files changed, 48 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 00e1c9c3a8..214aad320b 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -114,7 +114,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& if (wasShortcutting || doesPathNeedRecalc(dest, actor)) // if need to rebuild path { const auto pathfindingHalfExtents = world->getPathfindingHalfExtents(actor); - mPathFinder.buildPath(actor, position, dest, actor.getCell(), getPathGridGraph(actor.getCell()), + mPathFinder.buildLimitedPath(actor, position, dest, actor.getCell(), getPathGridGraph(actor.getCell()), pathfindingHalfExtents, getNavigatorFlags(actor), getAreaCosts(actor)); mRotateOnTheRunChecks = 3; diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 93ae90547c..781b897a73 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -442,4 +442,21 @@ namespace MWMechanics std::copy(prePath.rbegin(), prePath.rend(), std::front_inserter(mPath)); } + + void PathFinder::buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, + const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents, + const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts) + { + const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); + const auto maxDistance = std::min( + navigator->getMaxNavmeshAreaRealRadius(), + static_cast(Constants::CellSizeInUnits) + ); + const auto startToEnd = endPoint - startPoint; + const auto distance = startToEnd.length(); + if (distance <= maxDistance) + return buildPath(actor, startPoint, endPoint, cell, pathgridGraph, halfExtents, flags, areaCosts); + const auto end = startPoint + startToEnd * maxDistance / distance; + buildPath(actor, startPoint, end, cell, pathgridGraph, halfExtents, flags, areaCosts); + } } diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index b5c376b8ca..ed88a57ca0 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -101,6 +101,10 @@ namespace MWMechanics void buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts); + void buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, + const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents, + 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, bool canMoveByZ); diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index ef61f78c68..d08bfa640f 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -235,6 +235,8 @@ namespace DetourNavigator const osg::Vec3f& end, const Flags includeFlags) const; virtual RecastMeshTiles getRecastMeshTiles() = 0; + + virtual float getMaxNavmeshAreaRealRadius() const = 0; }; } diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index 142ba590d2..abfb20ba80 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -213,4 +213,10 @@ namespace DetourNavigator ++it; } } + + float NavigatorImpl::getMaxNavmeshAreaRealRadius() const + { + const auto& settings = getSettings(); + return getRealTileSize(settings) * getMaxNavmeshAreaRadius(settings); + } } diff --git a/components/detournavigator/navigatorimpl.hpp b/components/detournavigator/navigatorimpl.hpp index e197c71b78..74fff0dea4 100644 --- a/components/detournavigator/navigatorimpl.hpp +++ b/components/detournavigator/navigatorimpl.hpp @@ -60,6 +60,8 @@ namespace DetourNavigator RecastMeshTiles getRecastMeshTiles() override; + float getMaxNavmeshAreaRealRadius() const override; + private: Settings mSettings; NavMeshManager mNavMeshManager; diff --git a/components/detournavigator/navigatorstub.hpp b/components/detournavigator/navigatorstub.hpp index f1f9e06ef3..f6892bf1b5 100644 --- a/components/detournavigator/navigatorstub.hpp +++ b/components/detournavigator/navigatorstub.hpp @@ -92,6 +92,11 @@ namespace DetourNavigator return {}; } + float getMaxNavmeshAreaRealRadius() const override + { + return std::numeric_limits::max(); + } + private: Settings mDefaultSettings {}; SharedNavMeshCacheItem mEmptyNavMeshCacheItem; diff --git a/components/detournavigator/settingsutils.hpp b/components/detournavigator/settingsutils.hpp index 39ffc03d19..8f1c96e286 100644 --- a/components/detournavigator/settingsutils.hpp +++ b/components/detournavigator/settingsutils.hpp @@ -1,4 +1,4 @@ -#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGSUTILS_H +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGSUTILS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGSUTILS_H #include "settings.hpp" @@ -89,6 +89,16 @@ namespace DetourNavigator transform.getOrigin() + btVector3(0, 0, getSwimLevel(settings, agentHalfExtentsZ) - agentHalfExtentsZ) ); } + + inline float getRealTileSize(const Settings& settings) + { + return settings.mTileSize * settings.mCellSize / settings.mRecastScaleFactor; + } + + inline float getMaxNavmeshAreaRadius(const Settings& settings) + { + return std::floor(std::sqrt(settings.mMaxTilesNumber / osg::PI)) - 1; + } } #endif From f32e1790bcdf1b85e655d0a8ae34a45aa463ed4d Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 23 Mar 2021 23:48:11 +0100 Subject: [PATCH 38/49] Add half extents to AiEscord max distance For actors with big bounding box given constants may not work properly like it's not possible to get close enough to actor from a given angle to make it move. --- apps/openmw/mwmechanics/aiescort.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 5dc1e44db0..8c5230221c 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -82,14 +82,16 @@ namespace MWMechanics mRemainingDuration = mDuration; return true; } - mMaxDist = 450; + const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); + mMaxDist = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())) + 450.0f; } else { // Stop moving if the player is too far away MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1); actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - mMaxDist = 250; + const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); + mMaxDist = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())) + 250.0f; } return false; From 453e94ea9fa31a0949b89b52edba4516586fc2ba Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 23 Mar 2021 23:59:45 +0100 Subject: [PATCH 39/49] Use half extents for destination distance tolerance in AiEscort For actors moving in water destination may be located at such z coordinate that they can't reach. --- apps/openmw/mwmechanics/aiescort.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 8c5230221c..75c0461105 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -73,25 +73,25 @@ namespace MWMechanics const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mTargetActorRefId, false); const osg::Vec3f leaderPos = actor.getRefData().getPosition().asVec3(); const osg::Vec3f followerPos = follower.getRefData().getPosition().asVec3(); + const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); + const float maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); if ((leaderPos - followerPos).length2() <= mMaxDist * mMaxDist) { const osg::Vec3f dest(mX, mY, mZ); - if (pathTo(actor, dest, duration)) //Returns true on path complete + if (pathTo(actor, dest, duration, maxHalfExtent)) //Returns true on path complete { mRemainingDuration = mDuration; return true; } - const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); - mMaxDist = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())) + 450.0f; + mMaxDist = maxHalfExtent + 450.0f; } else { // Stop moving if the player is too far away MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1); actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); - mMaxDist = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())) + 250.0f; + mMaxDist = maxHalfExtent + 250.0f; } return false; From e8c2cd15f09bc23bfa6d639bfd59e90ca5302d18 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 24 Mar 2021 00:03:22 +0100 Subject: [PATCH 40/49] Add #5914 to the changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c36ce3eba..f8d3a65f68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -113,6 +113,7 @@ Bug #5902: NiZBufferProperty is unable to disable the depth test Bug #5906: Sunglare doesn't work with Mesa drivers and AMD GPUs Bug #5912: ImprovedBound mod doesn't work + Bug #5914: BM: The Swimmer can't reach destination Feature #390: 3rd person look "over the shoulder" Feature #832: OpenMW-CS: Handle deleted references Feature #1536: Show more information about level on menu From cf52bee188b1d16e524a17b80dd870a8b94d6d56 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 24 Mar 2021 16:32:15 +0400 Subject: [PATCH 41/49] Fix build with OSG 3.4 --- components/shader/shadervisitor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 1af9f94c57..612c9011d2 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include From 91bca0cb1fbcf4ed08acb9dfdfedde63a7bdcf06 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 24 Mar 2021 13:28:34 -0700 Subject: [PATCH 42/49] attempt to fix build issue --- CI/before_script.msvc.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index b2b6483ec7..93d995c995 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -849,8 +849,8 @@ fi fi # check version - pip list | grep 'aqtinstall\s*1.1.3' - if [ $? -ne 0 ]; then + aqt-venv/${VENV_BIN_DIR}/pip list | grep 'aqtinstall\s*1.1.3' || [[ $? -ne 0 ]] + if [[ $? -eq 0 ]]; then echo " Installing aqt wheel into virtualenv..." run_cmd "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall==1.1.3 fi From 72a2e3722ef0d9c273679df8ee4e5d85afa0245b Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 24 Mar 2021 14:01:40 -0700 Subject: [PATCH 43/49] update syntax --- CI/before_script.msvc.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 93d995c995..e1afdb059e 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -849,8 +849,8 @@ fi fi # check version - aqt-venv/${VENV_BIN_DIR}/pip list | grep 'aqtinstall\s*1.1.3' || [[ $? -ne 0 ]] - if [[ $? -eq 0 ]]; then + aqt-venv/${VENV_BIN_DIR}/pip list | grep 'aqtinstall\s*1.1.3' || [ $? -ne 0 ] + if [ $? -eq 0 ]; then echo " Installing aqt wheel into virtualenv..." run_cmd "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall==1.1.3 fi From e56efdd562eb9eefcce5ab536373f219ceb55b64 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 23 Mar 2021 15:52:37 -0700 Subject: [PATCH 44/49] change aim calculation --- apps/openmw/mwworld/worldimp.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 01b90e9078..e5fa7322ce 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3867,11 +3867,14 @@ namespace MWWorld return false; } - osg::Vec3f World::aimToTarget(const ConstPtr &actor, const MWWorld::ConstPtr& target) + osg::Vec3f World::aimToTarget(const ConstPtr &actor, const ConstPtr &target) { osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3(); - weaponPos.z() += mPhysics->getHalfExtents(actor).z(); - osg::Vec3f targetPos = mPhysics->getCollisionObjectPosition(target); + osg::Vec3f weaponHalfExtents = mPhysics->getHalfExtents(actor); + osg::Vec3f targetPos = target.getRefData().getPosition().asVec3(); + osg::Vec3f targetHalfExtents = mPhysics->getHalfExtents(target); + weaponPos.z() += weaponHalfExtents.z() * 2 * 0.75; // projectilemanager.cpp spawns bolts at 0.75 actor height + targetPos.z() += targetHalfExtents.z(); return (targetPos - weaponPos); } From 2cd96e56d5c6f5359f4f2e45d0b5e37572c7fc3e Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 24 Mar 2021 10:21:37 -0700 Subject: [PATCH 45/49] create constant and use constant in other parts of the code base --- AUTHORS.md | 1 + CHANGELOG.md | 1 + apps/openmw/mwmechanics/aicast.cpp | 6 ++++-- apps/openmw/mwworld/projectilemanager.cpp | 3 +-- apps/openmw/mwworld/worldimp.cpp | 2 +- components/misc/constants.hpp | 3 +++ 6 files changed, 11 insertions(+), 5 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 53efdd286f..b30168fd8f 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -131,6 +131,7 @@ Programmers Martin Otto (MAtahualpa) Mateusz Kołaczek (PL_kolek) Mateusz Malisz (malice) + Max Henzerling (SaintMercury) megaton Michael Hogan (Xethik) Michael Mc Donnell diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c36ce3eba..fe53643129 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,6 +88,7 @@ Bug #5656: Sneaking characters block hits while standing Bug #5661: Region sounds don't play at the right interval Bug #5675: OpenMW-cs. FRMR subrecords are saved with the wrong MastIdx + Bug #5680: Enemy AI incorrectly aims projectiles 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 diff --git a/apps/openmw/mwmechanics/aicast.cpp b/apps/openmw/mwmechanics/aicast.cpp index af3aac340e..630c04a6a7 100644 --- a/apps/openmw/mwmechanics/aicast.cpp +++ b/apps/openmw/mwmechanics/aicast.cpp @@ -1,5 +1,7 @@ #include "aicast.hpp" +#include + #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" @@ -54,12 +56,12 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac if (target != actor && target.getClass().isActor()) { osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(target); - targetPos.z() += halfExtents.z() * 2 * 0.75f; + targetPos.z() += halfExtents.z() * 2 * Constants::TorsoHeight; } osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3(); osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); - actorPos.z() += halfExtents.z() * 2 * 0.75f; + actorPos.z() += halfExtents.z() * 2 * Constants::TorsoHeight; osg::Vec3f dir = targetPos - actorPos; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index f483905ddf..b463fc09ed 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -265,9 +265,8 @@ namespace MWWorld osg::Vec3f pos = caster.getRefData().getPosition().asVec3(); if (caster.getClass().isActor()) { - // Spawn at 0.75 * ActorHeight // Note: we ignore the collision box offset, this is required to make some flying creatures work as intended. - pos.z() += mPhysics->getRenderingHalfExtents(caster).z() * 2 * 0.75; + pos.z() += mPhysics->getRenderingHalfExtents(caster).z() * 2 * Constants::TorsoHeight; } if (MWBase::Environment::get().getWorld()->isUnderwater(caster.getCell(), pos)) // Underwater casting not possible diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index e5fa7322ce..b37d326f07 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3873,7 +3873,7 @@ namespace MWWorld osg::Vec3f weaponHalfExtents = mPhysics->getHalfExtents(actor); osg::Vec3f targetPos = target.getRefData().getPosition().asVec3(); osg::Vec3f targetHalfExtents = mPhysics->getHalfExtents(target); - weaponPos.z() += weaponHalfExtents.z() * 2 * 0.75; // projectilemanager.cpp spawns bolts at 0.75 actor height + weaponPos.z() += weaponHalfExtents.z() * 2 * Constants::TorsoHeight; targetPos.z() += targetHalfExtents.z(); return (targetPos - weaponPos); } diff --git a/components/misc/constants.hpp b/components/misc/constants.hpp index 1053b1c560..bfd3933fc7 100644 --- a/components/misc/constants.hpp +++ b/components/misc/constants.hpp @@ -33,6 +33,9 @@ const std::string NightDayLabel = "NightDaySwitch"; // A label to mark visual switches for herbalism feature const std::string HerbalismLabel = "HerbalismSwitch"; +// Percentage height at which projectiles are spawned from an actor +const float TorsoHeight = 0.75f; + } #endif From dc09616e59a2b81f6fa9d44708f47cbf09757def Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 24 Mar 2021 11:15:09 -0700 Subject: [PATCH 46/49] change bugfix name to be same as ticket name --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe53643129..d0217d835e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,7 +88,7 @@ Bug #5656: Sneaking characters block hits while standing Bug #5661: Region sounds don't play at the right interval Bug #5675: OpenMW-cs. FRMR subrecords are saved with the wrong MastIdx - Bug #5680: Enemy AI incorrectly aims projectiles + Bug #5680: Bull Netches incorrectly aim over the player character's head and always miss 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 From ccd3cbc69abf40ab4ddfac4ae9a7e6fa79666fe0 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 26 Mar 2021 23:36:12 +0100 Subject: [PATCH 47/49] Use saved actor position instead of reading again RefData in unstuck. It is a race condition to do so. --- apps/openmw/mwphysics/movementsolver.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index a393ec5784..1c04ee65a5 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -459,8 +459,7 @@ namespace MWPhysics const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, physicActor->getHalfExtents().z()); // use a 3d approximation of the movement vector to better judge player intent - const ESM::Position& refpos = ptr.getRefData().getPosition(); - auto velocity = (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement; + auto velocity = (osg::Quat(actor.mRefpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(actor.mRefpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement; // try to pop outside of the world before doing anything else if we're inside of it if (!physicActor->getOnGround() || physicActor->getOnSlope()) velocity += physicActor->getInertialForce(); From dbd6e3bfee7b58d5d527a87c4b31d3ecf0a52658 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 26 Mar 2021 23:43:49 +0100 Subject: [PATCH 48/49] Replace pointless usage of shared_ptr by unique_ptr / non-owning raw pointer for btCollisionWorld. --- apps/openmw/mwphysics/mtphysics.cpp | 14 +++++++------- apps/openmw/mwphysics/mtphysics.hpp | 4 ++-- apps/openmw/mwphysics/physicssystem.cpp | 4 ++-- apps/openmw/mwphysics/physicssystem.hpp | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 11eb7f909c..209c5aa653 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -137,11 +137,11 @@ namespace namespace MWPhysics { - PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, std::shared_ptr collisionWorld) + PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, btCollisionWorld *collisionWorld) : mDefaultPhysicsDt(physicsDt) , mPhysicsDt(physicsDt) , mTimeAccum(0.f) - , mCollisionWorld(std::move(collisionWorld)) + , mCollisionWorld(collisionWorld) , mNumJobs(0) , mRemainingSteps(0) , mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics")) @@ -185,7 +185,7 @@ namespace MWPhysics if (data.mActor.lock()) { std::unique_lock lock(mCollisionWorldMutex); - MovementSolver::unstuck(data, mCollisionWorld.get()); + MovementSolver::unstuck(data, mCollisionWorld); } }); @@ -381,7 +381,7 @@ namespace MWPhysics void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback) { std::shared_lock lock(mCollisionWorldMutex); - ContactTestWrapper::contactTest(mCollisionWorld.get(), colObj, resultCallback); + ContactTestWrapper::contactTest(mCollisionWorld, colObj, resultCallback); } std::optional PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target) @@ -532,7 +532,7 @@ namespace MWPhysics if(const auto actor = mActorsFrameData[job].mActor.lock()) { MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet); - MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData); + MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld, *mWorldFrameData); } } @@ -594,8 +594,8 @@ namespace MWPhysics { for (auto& actorData : mActorsFrameData) { - MovementSolver::unstuck(actorData, mCollisionWorld.get()); - MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData); + MovementSolver::unstuck(actorData, mCollisionWorld); + MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld, *mWorldFrameData); } updateActorsPositions(); diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 137755c21e..22cab3241d 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -25,7 +25,7 @@ namespace MWPhysics class PhysicsTaskScheduler { public: - PhysicsTaskScheduler(float physicsDt, std::shared_ptr collisionWorld); + PhysicsTaskScheduler(float physicsDt, btCollisionWorld* collisionWorld); ~PhysicsTaskScheduler(); /// @brief move actors taking into account desired movements and collisions @@ -67,7 +67,7 @@ namespace MWPhysics float mDefaultPhysicsDt; float mPhysicsDt; float mTimeAccum; - std::shared_ptr mCollisionWorld; + btCollisionWorld* mCollisionWorld; std::vector mLOSCache; std::set, std::owner_less>> mUpdateAabb; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 65ed13f802..f93ffe76d8 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -79,7 +79,7 @@ namespace MWPhysics mDispatcher = std::make_unique(mCollisionConfiguration.get()); mBroadphase = std::make_unique(); - mCollisionWorld = std::make_shared(mDispatcher.get(), mBroadphase.get(), mCollisionConfiguration.get()); + mCollisionWorld = std::make_unique(mDispatcher.get(), mBroadphase.get(), mCollisionConfiguration.get()); // Don't update AABBs of all objects every frame. Most objects in MW are static, so we don't need this. // Should a "static" object ever be moved, we have to update its AABB manually using DynamicsWorld::updateSingleAabb. @@ -97,7 +97,7 @@ namespace MWPhysics } } - mTaskScheduler = std::make_unique(mPhysicsDt, mCollisionWorld); + mTaskScheduler = std::make_unique(mPhysicsDt, mCollisionWorld.get()); mDebugDrawer = std::make_unique(mParentNode, mCollisionWorld.get(), mDebugDrawEnabled); } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 3548239867..5ccbf1a380 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -259,7 +259,7 @@ namespace MWPhysics std::unique_ptr mBroadphase; std::unique_ptr mCollisionConfiguration; std::unique_ptr mDispatcher; - std::shared_ptr mCollisionWorld; + std::unique_ptr mCollisionWorld; std::unique_ptr mTaskScheduler; std::unique_ptr mShapeManager; From b58244ac26f3fa970863c2fc713de3368cba3e7f Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 26 Mar 2021 23:45:56 +0100 Subject: [PATCH 49/49] Guard the Bullet drawing method with a read lock on the btCollisionWorld. It closes a race on the collision shapes coordinates. --- apps/openmw/mwphysics/mtphysics.cpp | 10 +++++++++- apps/openmw/mwphysics/mtphysics.hpp | 9 ++++++++- apps/openmw/mwphysics/physicssystem.cpp | 4 ++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 209c5aa653..4be8b2396f 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -9,6 +9,7 @@ #include "components/settings/settings.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/movement.hpp" +#include "../mwrender/bulletdebugdraw.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" @@ -137,11 +138,12 @@ namespace namespace MWPhysics { - PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, btCollisionWorld *collisionWorld) + PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, btCollisionWorld *collisionWorld, MWRender::DebugDrawer* debugDrawer) : mDefaultPhysicsDt(physicsDt) , mPhysicsDt(physicsDt) , mTimeAccum(0.f) , mCollisionWorld(collisionWorld) + , mDebugDrawer(debugDrawer) , mNumJobs(0) , mRemainingSteps(0) , mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics")) @@ -626,4 +628,10 @@ namespace MWPhysics mTimeBegin = mTimer->tick(); mFrameNumber = frameNumber; } + + void PhysicsTaskScheduler::debugDraw() + { + std::shared_lock lock(mCollisionWorldMutex); + mDebugDrawer->step(); + } } diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 22cab3241d..6d2c392c09 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -20,12 +20,17 @@ namespace Misc class Barrier; } +namespace MWRender +{ + class DebugDrawer; +} + namespace MWPhysics { class PhysicsTaskScheduler { public: - PhysicsTaskScheduler(float physicsDt, btCollisionWorld* collisionWorld); + PhysicsTaskScheduler(float physicsDt, btCollisionWorld* collisionWorld, MWRender::DebugDrawer* debugDrawer); ~PhysicsTaskScheduler(); /// @brief move actors taking into account desired movements and collisions @@ -49,6 +54,7 @@ namespace MWPhysics void removeCollisionObject(btCollisionObject* collisionObject); void updateSingleAabb(std::weak_ptr ptr, bool immediate=false); bool getLineOfSight(const std::weak_ptr& actor1, const std::weak_ptr& actor2); + void debugDraw(); private: void syncComputation(); @@ -68,6 +74,7 @@ namespace MWPhysics float mPhysicsDt; float mTimeAccum; btCollisionWorld* mCollisionWorld; + MWRender::DebugDrawer* mDebugDrawer; std::vector mLOSCache; std::set, std::owner_less>> mUpdateAabb; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index f93ffe76d8..ac8dd92f86 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -97,8 +97,8 @@ namespace MWPhysics } } - mTaskScheduler = std::make_unique(mPhysicsDt, mCollisionWorld.get()); mDebugDrawer = std::make_unique(mParentNode, mCollisionWorld.get(), mDebugDrawEnabled); + mTaskScheduler = std::make_unique(mPhysicsDt, mCollisionWorld.get(), mDebugDrawer.get()); } PhysicsSystem::~PhysicsSystem() @@ -827,7 +827,7 @@ namespace MWPhysics void PhysicsSystem::debugDraw() { if (mDebugDrawEnabled) - mDebugDrawer->step(); + mTaskScheduler->debugDraw(); } bool PhysicsSystem::isActorStandingOn(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const