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 face13d193..b1fea773e9 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 @@ -87,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: 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 @@ -109,6 +111,10 @@ 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 + 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 @@ -141,6 +147,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/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 0c26801cc4..e1afdb059e 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 @@ -838,9 +848,11 @@ fi wrappedExit 1 fi - if ! [ -e "aqt-venv/${VENV_BIN_DIR}/aqt" ]; then + # check version + 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==0.9.2 + run_cmd "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall==1.1.3 fi popd > /dev/null @@ -868,6 +880,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 +896,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 +1074,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 @@ -1067,7 +1088,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 diff --git a/CMakeLists.txt b/CMakeLists.txt index fa11942050..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) @@ -344,7 +350,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 +362,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() 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/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/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/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/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/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 62b3d32d04..54c09e00f1 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(""); @@ -495,6 +495,8 @@ namespace MWGui } else allow(GW_ALL); + + mStatsWatcher->forceUpdate(); } WindowManager::~WindowManager() 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..72d5d96a22 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; @@ -1901,14 +1905,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 +1962,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 +1992,7 @@ namespace MWMechanics } if (aiActive && inProcessingRange) { - if (timerUpdateAITargets == 0) + if (engageCombatTimerStatus == Misc::TimerStatus::Elapsed) { if (!isPlayer) adjustCommandedActor(iter->first); @@ -2076,7 +2079,6 @@ namespace MWMechanics if (avoidCollisions) predictAndAvoidCollisions(); - timerUpdateAITargets += duration; timerUpdateHeadTrack += duration; timerUpdateEquippedLight += duration; timerUpdateHello += duration; 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/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/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 5dc1e44db0..75c0461105 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -73,23 +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; } - mMaxDist = 450; + 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; - mMaxDist = 250; + mMaxDist = maxHalfExtent + 250.0f; } return false; diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 99132b711a..214aad320b 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); @@ -115,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; @@ -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/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/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/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); 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())) { 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) 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/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/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 5f0322a1f2..1c04ee65a5 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,13 +443,23 @@ 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()); // 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(); @@ -470,6 +486,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 +520,11 @@ namespace MWPhysics } } } + else + { + physicActor->setStuckFrames(0); + physicActor->setLastStuckPosition({0, 0, 0}); + } collisionObject->setWorldTransform(oldTransform); actor.mPosition = tempPosition; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 2b0db5b82c..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" @@ -101,7 +102,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); } @@ -137,10 +138,12 @@ namespace namespace MWPhysics { - PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, std::shared_ptr collisionWorld) - : mPhysicsDt(physicsDt) + PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, btCollisionWorld *collisionWorld, MWRender::DebugDrawer* debugDrawer) + : mDefaultPhysicsDt(physicsDt) + , mPhysicsDt(physicsDt) , mTimeAccum(0.f) - , mCollisionWorld(std::move(collisionWorld)) + , mCollisionWorld(collisionWorld) + , mDebugDrawer(debugDrawer) , mNumJobs(0) , mRemainingSteps(0) , mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics")) @@ -152,6 +155,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) @@ -179,16 +187,18 @@ namespace MWPhysics if (data.mActor.lock()) { std::unique_lock lock(mCollisionWorldMutex); - MovementSolver::unstuck(data, mCollisionWorld.get()); + MovementSolver::unstuck(data, mCollisionWorld); } }); mPostStepBarrier = std::make_unique(mNumThreads, [&]() { if (mRemainingSteps) + { --mRemainingSteps; + updateActorsPositions(); + } mNextJob.store(0, std::memory_order_release); - updateActorsPositions(); }); mPostSimBarrier = std::make_unique(mNumThreads, [&]() @@ -218,13 +228,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 @@ -249,14 +307,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; @@ -267,20 +332,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) @@ -308,7 +383,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) @@ -459,7 +534,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); } } @@ -521,8 +596,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(); @@ -553,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 b35ebd5ee9..6d2c392c09 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -13,18 +13,24 @@ #include "physicssystem.hpp" #include "ptrholder.hpp" +#include "components/misc/budgetmeasurement.hpp" namespace Misc { class Barrier; } +namespace MWRender +{ + class DebugDrawer; +} + namespace MWPhysics { class PhysicsTaskScheduler { public: - PhysicsTaskScheduler(float physicsDt, std::shared_ptr collisionWorld); + PhysicsTaskScheduler(float physicsDt, btCollisionWorld* collisionWorld, MWRender::DebugDrawer* debugDrawer); ~PhysicsTaskScheduler(); /// @brief move actors taking into account desired movements and collisions @@ -32,7 +38,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); @@ -48,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(); @@ -58,13 +65,16 @@ 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; + btCollisionWorld* mCollisionWorld; + MWRender::DebugDrawer* mDebugDrawer; std::vector mLOSCache; std::set, std::owner_less>> mUpdateAabb; @@ -94,6 +104,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..ac8dd92f86 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,8 +97,8 @@ namespace MWPhysics } } - mTaskScheduler = std::make_unique(mPhysicsDt, mCollisionWorld); mDebugDrawer = std::make_unique(mParentNode, mCollisionWorld.get(), mDebugDrawEnabled); + mTaskScheduler = std::make_unique(mPhysicsDt, mCollisionWorld.get(), mDebugDrawer.get()); } PhysicsSystem::~PhysicsSystem() @@ -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); @@ -832,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 diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 80b2d98bcf..5ccbf1a380 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -252,14 +252,14 @@ namespace MWPhysics void updateWater(); - std::vector prepareFrameData(int numSteps); + std::vector prepareFrameData(bool willSimulate); osg::ref_ptr mUnrefQueue; 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; 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/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. 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/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/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 44465e5a76..fc9735d576 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) @@ -109,7 +109,7 @@ namespace MWSound SoundManager::~SoundManager() { - clear(); + SoundManager::clear(); mSoundBuffers.clear(); mOutput.reset(); } @@ -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) 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/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/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 90bc80b484..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) @@ -384,12 +401,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/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index d69c56d8c0..26f497a527 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,16 +170,18 @@ 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); } + /// 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/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: 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/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 <> diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 01b90e9078..d6a4de71bd 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"); @@ -3867,11 +3872,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 * Constants::TorsoHeight; + targetPos.z() += targetHalfExtents.z(); return (targetPos - weaponPos); } 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; 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/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 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/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 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 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 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() 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/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 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 902f15fb35..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) { @@ -1725,11 +1804,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 @@ -1752,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: @@ -1804,6 +1945,7 @@ namespace NifOsg bool hasMatCtrl = false; int lightmode = 1; + float emissiveMult = 1.f; for (const Nif::Property* property : properties) { @@ -1823,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); @@ -1943,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 f40cc04ead..ef89246d9f 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -785,7 +785,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..612c9011d2 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -42,7 +43,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 +53,7 @@ namespace Shader , mTranslucentFramebuffer(false) , mShaderManager(shaderManager) , mImageManager(imageManager) - , mDefaultVsTemplate(defaultVsTemplate) - , mDefaultFsTemplate(defaultFsTemplate) + , mDefaultShaderPrefix(defaultShaderPrefix) { mRequirements.emplace_back(); } @@ -129,6 +129,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 +444,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/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; } 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. 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. diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 613de8fa80..fc00ae2545 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,15 +70,16 @@ 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() + # 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) @@ -81,8 +91,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 +112,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. 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 @@ - - - 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 d3b7bfc4d7..e26d0f44cd 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -68,6 +68,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; @@ -172,7 +174,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 c1eae07223..104f42c09c 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -53,6 +53,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; @@ -121,7 +122,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 diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index c7f2c46574..1397f65e7e 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -37,20 +37,21 @@ 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 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; 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; @@ -227,7 +228,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; @@ -251,7 +255,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