From 32cc14981eb4fafcb1396edf62d83aa6da0aaf02 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Tue, 22 Dec 2020 20:40:52 +0300 Subject: [PATCH 001/171] Don't move characters if their animation can't move them --- apps/openmw/mwmechanics/character.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 178486965..1eb58f4d3 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2423,7 +2423,7 @@ void CharacterController::update(float duration, bool animationOnly) moved.y() *= scale; // Ensure we're moving in generally the right direction... - if(speed > 0.f) + if (speed > 0.f && moved != osg::Vec3f()) { float l = moved.length(); if (std::abs(movement.x() - moved.x()) > std::abs(moved.x()) / 2 || From fd77ef6b0113a30997cc594d1ef3440ad4daad91 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Wed, 23 Dec 2020 03:33:34 +0300 Subject: [PATCH 002/171] Only ignore plain text after the last EOL tag (#5627) --- CHANGELOG.md | 1 + apps/openmw/mwgui/formatting.cpp | 23 ++++++++++++++--------- apps/openmw/mwgui/formatting.hpp | 2 ++ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b75a179b1..e557c7bcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,7 @@ Bug #5604: Only one valid NIF root node is loaded from a single file Bug #5611: Usable items with "0 Uses" should be used only once Bug #5622: Can't properly interact with the console when in pause menu + Bug #5627: Bookart not shown if it isn't followed by
statement Bug #5633: Damage Spells in effect before god mode is enabled continue to hurt the player character and can kill them Bug #5639: Tooltips cover Messageboxes Bug #5644: Summon effects running on the player during game initialization cause crashes diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index 75bf8a342..156dc5b0d 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -30,14 +30,18 @@ namespace MWGui // vanilla game does not show any text after the last EOL tag. const std::string lowerText = Misc::StringUtils::lowerCase(mText); - int brIndex = lowerText.rfind("
"); - int pIndex = lowerText.rfind("

"); - if (brIndex == pIndex) - mText = ""; - else if (brIndex > pIndex) - mText = mText.substr(0, brIndex+4); - else - mText = mText.substr(0, pIndex+3); + size_t brIndex = lowerText.rfind("
"); + size_t pIndex = lowerText.rfind("

"); + mPlainTextEnd = 0; + if (brIndex != pIndex) + { + if (brIndex != std::string::npos && pIndex != std::string::npos) + mPlainTextEnd = std::max(brIndex, pIndex); + else if (brIndex != std::string::npos) + mPlainTextEnd = brIndex; + else + mPlainTextEnd = pIndex; + } registerTag("br", Event_BrTag); registerTag("p", Event_PTag); @@ -103,7 +107,8 @@ namespace MWGui { if (!mIgnoreLineEndings || ch != '\n') { - mBuffer.push_back(ch); + if (mIndex < mPlainTextEnd) + mBuffer.push_back(ch); mIgnoreLineEndings = false; mIgnoreNewlineTags = false; } diff --git a/apps/openmw/mwgui/formatting.hpp b/apps/openmw/mwgui/formatting.hpp index d56351414..12a3d46ca 100644 --- a/apps/openmw/mwgui/formatting.hpp +++ b/apps/openmw/mwgui/formatting.hpp @@ -73,6 +73,8 @@ namespace MWGui bool mClosingTag; std::map mTagTypes; std::string mBuffer; + + size_t mPlainTextEnd; }; class Paginator From ebb564ad226aaba712e69c2207d7baff69473f26 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 27 Dec 2020 17:31:55 +0100 Subject: [PATCH 003/171] call moveObject() after applying waterwalking This unbreak abot's boat mods: they're continually teleporting the boats (who is an actor with waterwalking effect). As such, the physics simulation was never run and the boat never went to sea level. --- apps/openmw/mwphysics/physicssystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index cc4d6bba8..b50366ade 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -935,7 +935,7 @@ namespace MWPhysics if (mMoveToWaterSurface) { mPosition.z() = mWaterlevel; - mActorRaw->setPosition(mPosition); + MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition.x(), mPosition.y(), mPosition.z()); } mOldHeight = mPosition.z(); mRefpos = mActorRaw->getPtr().getRefData().getPosition(); From 18ef32ca82bd9c3cfcdc9f420131e4697ee054c9 Mon Sep 17 00:00:00 2001 From: wareya Date: Sun, 27 Dec 2020 22:16:11 +0000 Subject: [PATCH 004/171] values for this higher than sGroundOffset cause jittering on some surface; use safe-seeming value slightly less than sGroundOffset --- CHANGELOG.md | 10 + CHANGELOG_PR.md | 2 +- CI/before_install.android.sh | 2 +- CI/before_script.msvc.sh | 2 +- CMakeLists.txt | 5 +- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwphysics/actor.cpp | 26 +- apps/openmw/mwphysics/actor.hpp | 3 + .../closestnotmeconvexresultcallback.cpp | 61 +++- .../closestnotmeconvexresultcallback.hpp | 3 +- apps/openmw/mwphysics/constants.hpp | 12 +- apps/openmw/mwphysics/contacttestwrapper.cpp | 21 ++ apps/openmw/mwphysics/contacttestwrapper.h | 14 + apps/openmw/mwphysics/movementsolver.cpp | 331 ++++++++++++++---- apps/openmw/mwphysics/movementsolver.hpp | 37 +- apps/openmw/mwphysics/mtphysics.cpp | 16 +- apps/openmw/mwphysics/raycasting.hpp | 2 +- apps/openmw/mwphysics/stepper.cpp | 239 +++++++------ apps/openmw/mwphysics/stepper.hpp | 3 +- apps/openmw/mwphysics/trace.cpp | 4 +- appveyor.yml | 2 +- cmake/OSIdentity.cmake | 67 ++++ components/CMakeLists.txt | 7 +- 23 files changed, 658 insertions(+), 213 deletions(-) create mode 100644 apps/openmw/mwphysics/contacttestwrapper.cpp create mode 100644 apps/openmw/mwphysics/contacttestwrapper.h create mode 100644 cmake/OSIdentity.cmake diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b139253f..661a058d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,14 @@ Bug #832: OpenMW-CS: Handle deleted references Bug #1662: Qt4 and Windows binaries crash if there's a non-ASCII character in a file path/config path + Bug #1901: Actors colliding behaviour is different from vanilla Bug #1952: Incorrect particle lighting Bug #2069: Fireflies in Fireflies invade Morrowind look wrong Bug #2311: Targeted scripts are not properly supported on non-unique RefIDs Bug #2473: Unable to overstock merchants Bug #2798: Mutable ESM records Bug #2976 [reopened]: Issues combining settings from the command line and both config files + Bug #3137: Walking into a wall prevents jumping Bug #3372: Projectiles and magic bolts go through moving targets Bug #3676: NiParticleColorModifier isn't applied properly Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects @@ -18,6 +20,11 @@ Bug #4021: Attributes and skills are not stored as floats Bug #4055: Local scripts don't inherit variables from their base record Bug #4083: Door animation freezes when colliding with actors + Bug #4247: Cannot walk up stairs in Ebonheart docks + Bug #4447: Actor collision capsule shape allows looking through some walls + Bug #4465: Collision shape overlapping causes twitching + Bug #4476: Abot Gondoliers: player hangs in air during scenic travel + Bug #4568: Too many actors in one spot can push other actors out of bounds Bug #4623: Corprus implementation is incorrect Bug #4631: Setting MSAA level too high doesn't fall back to highest supported level Bug #4764: Data race in osg ParticleSystem @@ -71,12 +78,15 @@ 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 #5681: Player character can clip or pass through bridges instead of colliding against them + Bug #5683: Player character can get stuck with MR's balmora's wooden gate Bug #5688: Water shader broken indoors with enable indoor shadows = false Bug #5695: ExplodeSpell for actors doesn't target the ground Bug #5703: OpenMW-CS menu system crashing on XFCE Bug #5706: AI sequences stop looping after the saved game is reloaded Bug #5731: Editor: skirts are invisible on characters Bug #5758: Paralyzed actors behavior is inconsistent with vanilla + Bug #5762: Movement solver is insufficiently robust Feature #390: 3rd person look "over the shoulder" Feature #1536: Show more information about level on menu Feature #2386: Distant Statics in the form of Object Paging diff --git a/CHANGELOG_PR.md b/CHANGELOG_PR.md index 8a7948bb7..69ad0cc1b 100644 --- a/CHANGELOG_PR.md +++ b/CHANGELOG_PR.md @@ -41,4 +41,4 @@ Editor Bug Fixes: Miscellaneous: - Prevent save-game bloating by using an appropriate fog texture format (#5108) -- Ensure that 'Enchantment autocalc" flag is treated as flag in OpenMW-CS and in our esm tools (#5363) \ No newline at end of file +- Ensure that 'Enchantment autocalc" flag is treated as flag in OpenMW-CS and in our esm tools (#5363) diff --git a/CI/before_install.android.sh b/CI/before_install.android.sh index 791377dd9..0243a9609 100755 --- a/CI/before_install.android.sh +++ b/CI/before_install.android.sh @@ -1,4 +1,4 @@ #!/bin/sh -ex -curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/android/openmw-android-deps-20201018.zip -o ~/openmw-android-deps.zip +curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/android/openmw-android-deps-20201129.zip -o ~/openmw-android-deps.zip unzip -o ~/openmw-android-deps -d /usr/lib/android-sdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr > /dev/null diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 0ef67f47e..0c26801cc 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -73,7 +73,7 @@ CONFIGURATIONS=() TEST_FRAMEWORK="" GOOGLE_INSTALL_ROOT="" INSTALL_PREFIX="." -BULLET_DOUBLE="" +BULLET_DOUBLE=true BULLET_DBL="" BULLET_DBL_DISPLAY="Single precision" diff --git a/CMakeLists.txt b/CMakeLists.txt index be7bc79e3..4a615e843 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,9 @@ cmake_minimum_required(VERSION 3.1.0) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +# Detect OS +include(cmake/OSIdentity.cmake) + # for link time optimization, remove if cmake version is >= 3.9 if(POLICY CMP0069) cmake_policy(SET CMP0069 NEW) @@ -21,7 +24,7 @@ option(BUILD_NIFTEST "Build nif file tester" ON) option(BUILD_DOCS "Build documentation." OFF ) option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF) -option(BULLET_USE_DOUBLES "Use double precision for Bullet" OFF) +option(BULLET_USE_DOUBLES "Use double precision for Bullet" ON) set(OpenGL_GL_PREFERENCE LEGACY) # Use LEGACY as we use GL2; GLNVD is for GL3 and up. diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 6b6134dfb..a4c3b9136 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -73,7 +73,7 @@ add_openmw_dir (mwworld add_openmw_dir (mwphysics physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver projectile - closestnotmeconvexresultcallback raycasting mtphysics + closestnotmeconvexresultcallback raycasting mtphysics contacttestwrapper ) add_openmw_dir (mwclass diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 015424db5..3b7b0d061 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -1,6 +1,5 @@ #include "actor.hpp" -#include #include #include @@ -14,6 +13,8 @@ #include "collisiontype.hpp" #include "mtphysics.hpp" +#include + namespace MWPhysics { @@ -52,17 +53,8 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic Log(Debug::Error) << "Error: Failed to calculate bounding box for actor \"" << ptr.getCellRef().getRefId() << "\"."; } - // Use capsule shape only if base is square (nonuniform scaling apparently doesn't work on it) - if (std::abs(mHalfExtents.x()-mHalfExtents.y())= mHalfExtents.x()) - { - mShape.reset(new btCapsuleShapeZ(mHalfExtents.x(), 2*mHalfExtents.z() - 2*mHalfExtents.x())); - mRotationallyInvariant = true; - } - else - { - mShape.reset(new btBoxShape(Misc::Convert::toBullet(mHalfExtents))); - mRotationallyInvariant = false; - } + mShape.reset(new btBoxShape(Misc::Convert::toBullet(mHalfExtents))); + mRotationallyInvariant = (mMeshTranslation.x() == 0.0 && mMeshTranslation.y() == 0.0) && std::fabs(mHalfExtents.x() - mHalfExtents.y()) < 2.2; mConvexShape = static_cast(mShape.get()); @@ -72,8 +64,11 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic mCollisionObject->setCollisionShape(mShape.get()); mCollisionObject->setUserPointer(this); - updateRotation(); updateScale(); + + if(!mRotationallyInvariant) + updateRotation(); + updatePosition(); addCollisionMask(getCollisionMask()); updateCollisionObjectPosition(); @@ -154,6 +149,11 @@ osg::Vec3f Actor::getSimulationPosition() const return mSimulationPosition; } +osg::Vec3f Actor::getScaledMeshTranslation() const +{ + return mRotation * osg::componentMultiply(mMeshTranslation, mScale); +} + void Actor::updateCollisionObjectPosition() { std::scoped_lock lock(mPositionMutex); diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 18419c2c8..9d129f2ba 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -82,6 +82,9 @@ namespace MWPhysics */ osg::Vec3f getOriginalHalfExtents() const; + /// Returns the mesh translation, scaled and rotated as necessary + osg::Vec3f getScaledMeshTranslation() const; + /** * Returns the position of the collision body * @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space. diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp index 18c196e44..8dd5a624f 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp @@ -1,15 +1,39 @@ +#include + #include "closestnotmeconvexresultcallback.hpp" +#include "collisiontype.hpp" +#include "contacttestwrapper.h" #include +#include #include "collisiontype.hpp" #include "projectile.hpp" namespace MWPhysics { - ClosestNotMeConvexResultCallback::ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot) + class ActorOverlapTester : public btCollisionWorld::ContactResultCallback + { + public: + bool overlapping = false; + + btScalar addSingleResult(btManifoldPoint& cp, + const btCollisionObjectWrapper* colObj0Wrap, + int partId0, + int index0, + const btCollisionObjectWrapper* colObj1Wrap, + int partId1, + int index1) + { + if(cp.getDistance() <= 0.0f) + overlapping = true; + return btScalar(1); + } + }; + + ClosestNotMeConvexResultCallback::ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world) : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)), - mMe(me), mMotion(motion), mMinCollisionDot(minCollisionDot) + mMe(me), mMotion(motion), mMinCollisionDot(minCollisionDot), mWorld(world) { } @@ -18,6 +42,39 @@ namespace MWPhysics if (convexResult.m_hitCollisionObject == mMe) return btScalar(1); + // override data for actor-actor collisions + // vanilla Morrowind seems to make overlapping actors collide as though they are both cylinders with a diameter of the distance between them + // For some reason this doesn't work as well as it should when using capsules, but it still helps a lot. + if(convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor) + { + ActorOverlapTester isOverlapping; + // FIXME: This is absolutely terrible and bullet should feel terrible for not making contactPairTest const-correct. + ContactTestWrapper::contactPairTest(const_cast(mWorld), const_cast(mMe), const_cast(convexResult.m_hitCollisionObject), isOverlapping); + + if(isOverlapping.overlapping) + { + auto originA = Misc::Convert::toOsg(mMe->getWorldTransform().getOrigin()); + auto originB = Misc::Convert::toOsg(convexResult.m_hitCollisionObject->getWorldTransform().getOrigin()); + osg::Vec3f motion = Misc::Convert::toOsg(mMotion); + osg::Vec3f normal = (originA-originB); + normal.z() = 0; + normal.normalize(); + // only collide if horizontally moving towards the hit actor (note: the motion vector appears to be inverted) + // FIXME: This kinda screws with standing on actors that walk up slopes for some reason. Makes you fall through them. + // It happens in vanilla Morrowind too, but much less often. + // I tried hunting down why but couldn't figure it out. Possibly a stair stepping or ground ejection bug. + if(normal * motion > 0.0f) + { + convexResult.m_hitFraction = 0.0f; + convexResult.m_hitNormalLocal = Misc::Convert::toBullet(normal); + return ClosestConvexResultCallback::addSingleResult(convexResult, true); + } + else + { + return btScalar(1); + } + } + } if (convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Projectile) { auto* projectileHolder = static_cast(convexResult.m_hitCollisionObject->getUserPointer()); diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp index 97aaa64a1..538721ad8 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp +++ b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp @@ -10,7 +10,7 @@ namespace MWPhysics class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback { public: - ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot); + ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world); btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) override; @@ -18,6 +18,7 @@ namespace MWPhysics const btCollisionObject *mMe; const btVector3 mMotion; const btScalar mMinCollisionDot; + const btCollisionWorld * mWorld; }; } diff --git a/apps/openmw/mwphysics/constants.hpp b/apps/openmw/mwphysics/constants.hpp index 46367ab34..c6f2f3b53 100644 --- a/apps/openmw/mwphysics/constants.hpp +++ b/apps/openmw/mwphysics/constants.hpp @@ -5,12 +5,22 @@ namespace MWPhysics { static const float sStepSizeUp = 34.0f; static const float sStepSizeDown = 62.0f; - static const float sMinStep = 10.f; + + 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 + // 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 const float sGroundOffset = 1.0f; static const 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; + // Allows for more precise movement solving without getting stuck or snagging too easily. + static const 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; } #endif diff --git a/apps/openmw/mwphysics/contacttestwrapper.cpp b/apps/openmw/mwphysics/contacttestwrapper.cpp new file mode 100644 index 000000000..c11a7e292 --- /dev/null +++ b/apps/openmw/mwphysics/contacttestwrapper.cpp @@ -0,0 +1,21 @@ +#include + +#include "contacttestwrapper.h" + +namespace MWPhysics +{ + // Concurrent calls to contactPairTest (and by extension contactTest) are forbidden. + static std::mutex contactMutex; + void ContactTestWrapper::contactTest(btCollisionWorld* collisionWorld, btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback) + { + std::unique_lock lock(contactMutex); + collisionWorld->contactTest(colObj, resultCallback); + } + + void ContactTestWrapper::contactPairTest(btCollisionWorld* collisionWorld, btCollisionObject* colObjA, btCollisionObject* colObjB, btCollisionWorld::ContactResultCallback& resultCallback) + { + std::unique_lock lock(contactMutex); + collisionWorld->contactPairTest(colObjA, colObjB, resultCallback); + } + +} diff --git a/apps/openmw/mwphysics/contacttestwrapper.h b/apps/openmw/mwphysics/contacttestwrapper.h new file mode 100644 index 000000000..b3b6edc59 --- /dev/null +++ b/apps/openmw/mwphysics/contacttestwrapper.h @@ -0,0 +1,14 @@ +#ifndef OPENMW_MWPHYSICS_CONTACTTESTWRAPPER_H +#define OPENMW_MWPHYSICS_CONTACTTESTWRAPPER_H + +#include + +namespace MWPhysics +{ + struct ContactTestWrapper + { + static void contactTest(btCollisionWorld* collisionWorld, btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback); + static void contactPairTest(btCollisionWorld* collisionWorld, btCollisionObject* colObjA, btCollisionObject* colObjB, btCollisionWorld::ContactResultCallback& resultCallback); + }; +} +#endif diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 1feaf3c94..69bf04236 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -17,10 +17,13 @@ #include "actor.hpp" #include "collisiontype.hpp" #include "constants.hpp" +#include "contacttestwrapper.h" #include "physicssystem.hpp" #include "stepper.hpp" #include "trace.h" +#include + namespace MWPhysics { static bool isActor(const btCollisionObject *obj) @@ -29,12 +32,50 @@ namespace MWPhysics return obj->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor; } - template - static bool isWalkableSlope(const Vec3 &normal) + class ContactCollectionCallback : public btCollisionWorld::ContactResultCallback { - static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(sMaxSlope)); - return (normal.z() > sMaxSlopeCos); - } + public: + ContactCollectionCallback(const btCollisionObject * me, osg::Vec3f velocity) : mMe(me) + { + m_collisionFilterGroup = me->getBroadphaseHandle()->m_collisionFilterGroup; + m_collisionFilterMask = me->getBroadphaseHandle()->m_collisionFilterMask; + mVelocity = Misc::Convert::toBullet(velocity); + } + virtual btScalar addSingleResult(btManifoldPoint & contact, const btCollisionObjectWrapper * colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper * colObj1Wrap, int partId1, int index1) + { + if (isActor(colObj0Wrap->getCollisionObject()) && isActor(colObj1Wrap->getCollisionObject())) + return 0.0; + // ignore overlap if we're moving in the same direction as it would push us out (don't change this to >=, that would break detection when not moving) + if (contact.m_normalWorldOnB.dot(mVelocity) > 0.0) + return 0.0; + auto delta = contact.m_normalWorldOnB * -contact.m_distance1; + mContactSum += delta; + mMaxX = std::max(std::abs(delta.x()), mMaxX); + mMaxY = std::max(std::abs(delta.y()), mMaxY); + mMaxZ = std::max(std::abs(delta.z()), mMaxZ); + if (contact.m_distance1 < mDistance) + { + mDistance = contact.m_distance1; + mNormal = contact.m_normalWorldOnB; + mDelta = delta; + return mDistance; + } + else + { + return 0.0; + } + } + btScalar mMaxX = 0.0; + btScalar mMaxY = 0.0; + btScalar mMaxZ = 0.0; + btVector3 mContactSum{0.0, 0.0, 0.0}; + btVector3 mNormal{0.0, 0.0, 0.0}; // points towards "me" + btVector3 mDelta{0.0, 0.0, 0.0}; // points towards "me" + btScalar mDistance = 0.0; // negative or zero + protected: + btVector3 mVelocity; + const btCollisionObject * mMe; + }; osg::Vec3f MovementSolver::traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight) { @@ -99,13 +140,13 @@ namespace MWPhysics } const btCollisionObject *colobj = physicActor->getCollisionObject(); - osg::Vec3f halfExtents = physicActor->getHalfExtents(); - // NOTE: here we don't account for the collision box translation (i.e. physicActor->getPosition() - refpos.pos). - // That means the collision shape used for moving this actor is in a different spot than the collision shape - // other actors are using to collide against this actor. - // While this is strictly speaking wrong, it's needed for MW compatibility. - actor.mPosition.z() += halfExtents.z(); + // Adjust for collision mesh offset relative to actor's "location" + // (doTrace doesn't take local/interior collision shape translation into account, so we have to do it on our own) + // for compatibility with vanilla assets, we have to derive this from the vertical half extent instead of from internal hull translation + // if not for this hack, the "correct" collision hull position would be physicActor->getScaledMeshTranslation() + osg::Vec3f halfExtents = physicActor->getHalfExtents(); + actor.mPosition.z() += halfExtents.z(); // vanilla-accurate static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get().find("fSwimHeightScale")->mValue.getFloat(); float swimlevel = actor.mWaterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale); @@ -156,6 +197,13 @@ namespace MWPhysics * The initial velocity was set earlier (see above). */ float remainingTime = time; + bool seenGround = physicActor->getOnGround() && !physicActor->getOnSlope() && !actor.mFlying; + + int numTimesSlid = 0; + osg::Vec3f lastSlideNormal(0,0,1); + osg::Vec3f lastSlideNormalFallback(0,0,1); + bool forceGroundTest = false; + for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f; ++iterations) { osg::Vec3f nextpos = newPosition + velocity * remainingTime; @@ -164,7 +212,7 @@ namespace MWPhysics if(!actor.mFlying && nextpos.z() > swimlevel && newPosition.z() < swimlevel) { const osg::Vec3f down(0,0,-1); - velocity = slide(velocity, down); + velocity = reject(velocity, down); // NOTE: remainingTime is unchanged before the loop continues continue; // velocity updated, calculate nextpos again } @@ -193,92 +241,158 @@ namespace MWPhysics break; } - // We are touching something. - if (tracer.mFraction < 1E-9f) - { - // Try to separate by backing off slighly to unstuck the solver - osg::Vec3f backOff = (newPosition - tracer.mHitPoint) * 1E-2f; - newPosition += backOff; - } + if (isWalkableSlope(tracer.mPlaneNormal) && !actor.mFlying && newPosition.z() >= swimlevel) + seenGround = true; // We hit something. Check if we can step up. float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + halfExtents.z(); osg::Vec3f oldPosition = newPosition; - bool result = false; + bool usedStepLogic = false; if (hitHeight < sStepSizeUp && !isActor(tracer.mHitObject)) { // Try to step up onto it. - // NOTE: stepMove does not allow stepping over, modifies newPosition if successful - result = stepper.step(newPosition, velocity*remainingTime, remainingTime); + // NOTE: this modifies newPosition and velocity on its own if successful + usedStepLogic = stepper.step(newPosition, velocity, remainingTime, seenGround, iterations == 0); } - if (result) + if (usedStepLogic) { // don't let pure water creatures move out of water after stepMove const auto ptr = physicActor->getPtr(); if (ptr.getClass().isPureWaterCreature(ptr) && newPosition.z() + halfExtents.z() > actor.mWaterlevel) newPosition = oldPosition; + else if(!actor.mFlying && actor.mPosition.z() >= swimlevel) + forceGroundTest = true; } else { - // Can't move this way, try to find another spot along the plane - osg::Vec3f newVelocity = slide(velocity, tracer.mPlaneNormal); + // Can't step up, so slide against what we ran into + remainingTime *= (1.0f-tracer.mFraction); + + auto planeNormal = tracer.mPlaneNormal; + + // If we touched the ground this frame, and whatever we ran into is a wall of some sort, + // pretend that its collision normal is pointing horizontally + // (fixes snagging on slightly downward-facing walls, and crawling up the bases of very steep walls because of the collision margin) + if (seenGround && !isWalkableSlope(planeNormal) && planeNormal.z() != 0) + { + planeNormal.z() = 0; + planeNormal.normalize(); + } + + // Move up to what we ran into (with a bit of a collision margin) + if ((newPosition-tracer.mEndPos).length2() > sCollisionMargin*sCollisionMargin) + { + auto direction = velocity; + direction.normalize(); + newPosition = tracer.mEndPos; + newPosition -= direction*sCollisionMargin; + } + + osg::Vec3f newVelocity = (velocity * planeNormal <= 0.0) ? reject(velocity, planeNormal) : velocity; + bool usedSeamLogic = false; + + // check for the current and previous collision planes forming an acute angle; slide along the seam if they do + if(numTimesSlid > 0) + { + auto dotA = lastSlideNormal * planeNormal; + auto dotB = lastSlideNormalFallback * planeNormal; + if(numTimesSlid <= 1) // ignore fallback normal if this is only the first or second slide + dotB = 1.0; + if(dotA <= 0.0 || dotB <= 0.0) + { + osg::Vec3f bestNormal = lastSlideNormal; + // use previous-to-previous collision plane if it's acute with current plane but actual previous plane isn't + if(dotB < dotA) + { + bestNormal = lastSlideNormalFallback; + lastSlideNormal = lastSlideNormalFallback; + } + + auto constraintVector = bestNormal ^ planeNormal; // cross product + if(constraintVector.length2() > 0) // only if it's not zero length + { + constraintVector.normalize(); + newVelocity = project(velocity, constraintVector); + + // version of surface rejection for acute crevices/seams + auto averageNormal = bestNormal + planeNormal; + averageNormal.normalize(); + tracer.doTrace(colobj, newPosition, newPosition + averageNormal*(sCollisionMargin*2.0), collisionWorld); + newPosition = (newPosition + tracer.mEndPos)/2.0; + + usedSeamLogic = true; + } + } + } + // otherwise just keep the normal vector rejection + + // if this isn't the first iteration, or if the first iteration is also the last iteration, + // move away from the collision plane slightly, if possible + // this reduces getting stuck in some concave geometry, like the gaps above the railings in some ald'ruhn buildings + // this is different from the normal collision margin, because the normal collision margin is along the movement path, + // but this is along the collision normal + if(!usedSeamLogic && (iterations > 0 || remainingTime < 0.01f)) + { + tracer.doTrace(colobj, newPosition, newPosition + planeNormal*(sCollisionMargin*2.0), collisionWorld); + newPosition = (newPosition + tracer.mEndPos)/2.0; + } - // Do not allow sliding upward if there is gravity. - // Stepping will have taken care of that. - if(!(newPosition.z() < swimlevel || actor.mFlying)) - newVelocity.z() = std::min(newVelocity.z(), 0.0f); + // Do not allow sliding up steep slopes if there is gravity. + if (newPosition.z() >= swimlevel && !actor.mFlying && !isWalkableSlope(planeNormal)) + newVelocity.z() = std::min(newVelocity.z(), velocity.z()); - if ((newVelocity-velocity).length2() < 0.01) + if (newVelocity * origVelocity <= 0.0f) break; - if ((newVelocity * origVelocity) <= 0.f) - break; // ^ dot product + numTimesSlid += 1; + lastSlideNormalFallback = lastSlideNormal; + lastSlideNormal = planeNormal; velocity = newVelocity; } } bool isOnGround = false; bool isOnSlope = false; - if (!(inertia.z() > 0.f) && !(newPosition.z() < swimlevel)) + if (forceGroundTest || (inertia.z() <= 0.f && newPosition.z() >= swimlevel)) { osg::Vec3f from = newPosition; - osg::Vec3f to = newPosition - (physicActor->getOnGround() ? osg::Vec3f(0,0,sStepSizeDown + 2*sGroundOffset) : osg::Vec3f(0,0,2*sGroundOffset)); + auto dropDistance = 2*sGroundOffset + (physicActor->getOnGround() ? sStepSizeDown : 0); + osg::Vec3f to = newPosition - osg::Vec3f(0,0,dropDistance); tracer.doTrace(colobj, from, to, collisionWorld); - if(tracer.mFraction < 1.0f && !isActor(tracer.mHitObject)) + if(tracer.mFraction < 1.0f) { - const btCollisionObject* standingOn = tracer.mHitObject; - PtrHolder* ptrHolder = static_cast(standingOn->getUserPointer()); - if (ptrHolder) - actor.mStandingOn = ptrHolder->getPtr(); - - if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water) - physicActor->setWalkingOnWater(true); - if (!actor.mFlying) - newPosition.z() = tracer.mEndPos.z() + sGroundOffset; + if (!isActor(tracer.mHitObject)) + { + isOnGround = true; + isOnSlope = !isWalkableSlope(tracer.mPlaneNormal); - isOnGround = true; + const btCollisionObject* standingOn = tracer.mHitObject; + PtrHolder* ptrHolder = static_cast(standingOn->getUserPointer()); + if (ptrHolder) + actor.mStandingOn = ptrHolder->getPtr(); - isOnSlope = !isWalkableSlope(tracer.mPlaneNormal); - } - else - { - // standing on actors is not allowed (see above). - // in addition to that, apply a sliding effect away from the center of the actor, - // so that we do not stay suspended in air indefinitely. - if (tracer.mFraction < 1.0f && isActor(tracer.mHitObject)) - { - if (osg::Vec3f(velocity.x(), velocity.y(), 0).length2() < 100.f*100.f) + if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water) + physicActor->setWalkingOnWater(true); + if (!actor.mFlying && !isOnSlope) { - btVector3 aabbMin, aabbMax; - tracer.mHitObject->getCollisionShape()->getAabb(tracer.mHitObject->getWorldTransform(), aabbMin, aabbMax); - btVector3 center = (aabbMin + aabbMax) / 2.f; - inertia = osg::Vec3f(actor.mPosition.x() - center.x(), actor.mPosition.y() - center.y(), 0); - inertia.normalize(); - inertia *= 100; + if (tracer.mFraction*dropDistance > sGroundOffset) + newPosition.z() = tracer.mEndPos.z() + sGroundOffset; + else + { + newPosition.z() = tracer.mEndPos.z(); + tracer.doTrace(colobj, newPosition, newPosition + osg::Vec3f(0, 0, 2*sGroundOffset), collisionWorld); + newPosition = (newPosition+tracer.mEndPos)/2.0; + } } } + else + { + // Vanilla allows actors to float on top of other actors. Do not push them off. + if (!actor.mFlying && isWalkableSlope(tracer.mPlaneNormal) && tracer.mEndPos.z()+sGroundOffset <= newPosition.z()) + newPosition.z() = tracer.mEndPos.z() + sGroundOffset; - isOnGround = false; + isOnGround = false; + } } } @@ -298,7 +412,98 @@ namespace MWPhysics physicActor->setOnGround(isOnGround); physicActor->setOnSlope(isOnSlope); - newPosition.z() -= halfExtents.z(); // remove what was added at the beginning actor.mPosition = newPosition; + // remove what was added earlier in compensating for doTrace not taking interior transformation into account + actor.mPosition.z() -= halfExtents.z(); // vanilla-accurate + } + + btVector3 addMarginToDelta(btVector3 delta) + { + if(delta.length2() == 0.0) + return delta; + return delta + delta.normalized() * sCollisionMargin; + } + + void MovementSolver::unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld) + { + const auto& ptr = actor.mActorRaw->getPtr(); + if (!ptr.getClass().isMobile(ptr)) + return; + + auto* physicActor = actor.mActorRaw; + if(!physicActor->getCollisionMode()) // noclipping/tcl + return; + + auto* collisionObject = physicActor->getCollisionObject(); + auto tempPosition = actor.mPosition; + + // 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; + // 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(); + + // because of the internal collision box offset hack, and the fact that we're moving the collision box manually, + // we need to replicate part of the collision box's transform process from scratch + osg::Vec3f refPosition = tempPosition + verticalHalfExtent; + osg::Vec3f goodPosition = refPosition; + const btTransform oldTransform = collisionObject->getWorldTransform(); + btTransform newTransform = oldTransform; + + auto gatherContacts = [&](btVector3 newOffset) -> ContactCollectionCallback + { + goodPosition = refPosition + Misc::Convert::toOsg(addMarginToDelta(newOffset)); + newTransform.setOrigin(Misc::Convert::toBullet(goodPosition)); + collisionObject->setWorldTransform(newTransform); + + ContactCollectionCallback callback{collisionObject, velocity}; + ContactTestWrapper::contactTest(const_cast(collisionWorld), collisionObject, callback); + return callback; + }; + + // check whether we're inside the world with our collision box with manually-derived offset + auto contactCallback = gatherContacts({0.0, 0.0, 0.0}); + if(contactCallback.mDistance < -sAllowedPenetration) + { + // we are; try moving it out of the world + auto positionDelta = contactCallback.mContactSum; + // limit rejection delta to the largest known individual rejections + if(std::abs(positionDelta.x()) > contactCallback.mMaxX) + positionDelta *= contactCallback.mMaxX / std::abs(positionDelta.x()); + if(std::abs(positionDelta.y()) > contactCallback.mMaxY) + positionDelta *= contactCallback.mMaxY / std::abs(positionDelta.y()); + if(std::abs(positionDelta.z()) > contactCallback.mMaxZ) + positionDelta *= contactCallback.mMaxZ / std::abs(positionDelta.z()); + + auto contactCallback2 = gatherContacts(positionDelta); + // successfully moved further out from contact (does not have to be in open space, just less inside of things) + if(contactCallback2.mDistance > contactCallback.mDistance) + tempPosition = goodPosition - verticalHalfExtent; + // try again but only upwards (fixes some bad coc floors) + else + { + // upwards-only offset + auto contactCallback3 = gatherContacts({0.0, 0.0, std::abs(positionDelta.z())}); + // success + if(contactCallback3.mDistance > contactCallback.mDistance) + tempPosition = goodPosition - verticalHalfExtent; + else + // try again but fixed distance up + { + auto contactCallback4 = gatherContacts({0.0, 0.0, 10.0}); + // success + if(contactCallback4.mDistance > contactCallback.mDistance) + tempPosition = goodPosition - verticalHalfExtent; + } + } + } + + collisionObject->setWorldTransform(oldTransform); + actor.mPosition = tempPosition; } } diff --git a/apps/openmw/mwphysics/movementsolver.hpp b/apps/openmw/mwphysics/movementsolver.hpp index 75fba1cf0..76141ec0e 100644 --- a/apps/openmw/mwphysics/movementsolver.hpp +++ b/apps/openmw/mwphysics/movementsolver.hpp @@ -5,6 +5,9 @@ #include +#include "constants.hpp" +#include "../mwworld/ptr.hpp" + class btCollisionWorld; namespace MWWorld @@ -14,29 +17,35 @@ namespace MWWorld namespace MWPhysics { + /// Vector projection + static inline osg::Vec3f project(const osg::Vec3f& u, const osg::Vec3f &v) + { + return v * (u * v); + } + + /// Vector rejection + static inline osg::Vec3f reject(const osg::Vec3f& direction, const osg::Vec3f &planeNormal) + { + return direction - project(direction, planeNormal); + } + + template + static bool isWalkableSlope(const Vec3 &normal) + { + static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(sMaxSlope)); + return (normal.z() > sMaxSlopeCos); + } + class Actor; struct ActorFrameData; struct WorldFrameData; class MovementSolver { - private: - ///Project a vector u on another vector v - static inline osg::Vec3f project(const osg::Vec3f& u, const osg::Vec3f &v) - { - return v * (u * v); - // ^ dot product - } - - ///Helper for computing the character sliding - static inline osg::Vec3f slide(const osg::Vec3f& direction, const osg::Vec3f &planeNormal) - { - return direction - project(direction, planeNormal); - } - public: static osg::Vec3f traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight); static void move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, WorldFrameData& worldData); + static void unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld); }; } diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index f610ceaea..0d1e5962a 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -13,6 +13,7 @@ #include "../mwworld/player.hpp" #include "actor.hpp" +#include "contacttestwrapper.h" #include "movementsolver.hpp" #include "mtphysics.hpp" #include "object.hpp" @@ -167,7 +168,14 @@ namespace MWPhysics mPreStepBarrier = std::make_unique(mNumThreads, [&]() { + if (mDeferAabbUpdate) updateAabbs(); + for (auto& data : mActorsFrameData) + if (data.mActor.lock()) + { + std::unique_lock lock(mCollisionWorldMutex); + MovementSolver::unstuck(data, mCollisionWorld.get()); + } }); mPostStepBarrier = std::make_unique(mNumThreads, [&]() @@ -295,7 +303,7 @@ namespace MWPhysics void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback) { std::shared_lock lock(mCollisionWorldMutex); - mCollisionWorld->contactTest(colObj, resultCallback); + ContactTestWrapper::contactTest(mCollisionWorld.get(), colObj, resultCallback); } std::optional PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target) @@ -438,8 +446,7 @@ namespace MWPhysics if (!mNewFrame) mHasJob.wait(lock, [&]() { return mQuit || mNewFrame; }); - if (mDeferAabbUpdate) - mPreStepBarrier->wait(); + mPreStepBarrier->wait(); int job = 0; while (mRemainingSteps && (job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) @@ -505,7 +512,10 @@ namespace MWPhysics while (mRemainingSteps--) { for (auto& actorData : mActorsFrameData) + { + MovementSolver::unstuck(actorData, mCollisionWorld.get()); MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData); + } updateActorsPositions(); } diff --git a/apps/openmw/mwphysics/raycasting.hpp b/apps/openmw/mwphysics/raycasting.hpp index 8ca6965d8..b176e8330 100644 --- a/apps/openmw/mwphysics/raycasting.hpp +++ b/apps/openmw/mwphysics/raycasting.hpp @@ -16,7 +16,7 @@ namespace MWPhysics osg::Vec3f mHitNormal; MWWorld::Ptr mHitObject; }; - + class RayCastingInterface { public: diff --git a/apps/openmw/mwphysics/stepper.cpp b/apps/openmw/mwphysics/stepper.cpp index 0ab383dd1..2a28381be 100644 --- a/apps/openmw/mwphysics/stepper.cpp +++ b/apps/openmw/mwphysics/stepper.cpp @@ -7,6 +7,7 @@ #include "collisiontype.hpp" #include "constants.hpp" +#include "movementsolver.hpp" namespace MWPhysics { @@ -24,125 +25,155 @@ namespace MWPhysics Stepper::Stepper(const btCollisionWorld *colWorld, const btCollisionObject *colObj) : mColWorld(colWorld) , mColObj(colObj) - , mHaveMoved(true) { } - bool Stepper::step(osg::Vec3f &position, const osg::Vec3f &toMove, float &remainingTime) + bool Stepper::step(osg::Vec3f &position, osg::Vec3f &velocity, float &remainingTime, const bool & onGround, bool firstIteration) { - /* - * Slide up an incline or set of stairs. Should be called only after a - * collision detection otherwise unnecessary tracing will be performed. - * - * NOTE: with a small change this method can be used to step over an obstacle - * of height sStepSize. - * - * If successful return 'true' and update 'position' to the new possible - * location and adjust 'remainingTime'. - * - * If not successful return 'false'. May fail for these reasons: - * - can't move directly up from current position - * - having moved up by between epsilon() and sStepSize, can't move forward - * - having moved forward by between epsilon() and toMove, - * = moved down between 0 and just under sStepSize but slope was too steep, or - * = moved the full sStepSize down (FIXME: this could be a bug) - * - * Starting position. Obstacle or stairs with height upto sStepSize in front. - * - * +--+ +--+ |XX - * | | -------> toMove | | +--+XX - * | | | | |XXXXX - * | | +--+ | | +--+XXXXX - * | | |XX| | | |XXXXXXXX - * +--+ +--+ +--+ +-------- - * ============================================== - */ - - /* - * Try moving up sStepSize using stepper. - * FIXME: does not work in case there is no front obstacle but there is one above - * - * +--+ +--+ - * | | | | - * | | | | |XX - * | | | | +--+XX - * | | | | |XXXXX - * +--+ +--+ +--+ +--+XXXXX - * |XX| |XXXXXXXX - * +--+ +-------- - * ============================================== - */ - if (mHaveMoved) - { - mHaveMoved = false; + if(velocity.x() == 0.0 && velocity.y() == 0.0) + return false; + + // Stairstepping algorithms work by moving up to avoid the step, moving forwards, then moving back down onto the ground. + // This algorithm has a couple of minor problems, but they don't cause problems for sane geometry, and just prevent stepping on insane geometry. - mUpStepper.doTrace(mColObj, position, position+osg::Vec3f(0.0f,0.0f,sStepSizeUp), mColWorld); - if (mUpStepper.mFraction < std::numeric_limits::epsilon()) - return false; // didn't even move the smallest representable amount - // (TODO: shouldn't this be larger? Why bother with such a small amount?) + mUpStepper.doTrace(mColObj, position, position+osg::Vec3f(0.0f,0.0f,sStepSizeUp), mColWorld); + + float upDistance = 0; + if(!mUpStepper.mHitObject) + upDistance = sStepSizeUp; + else if(mUpStepper.mFraction*sStepSizeUp > sCollisionMargin) + upDistance = mUpStepper.mFraction*sStepSizeUp - sCollisionMargin; + else + { + return false; } - /* - * Try moving from the elevated position using tracer. - * - * +--+ +--+ - * | | |YY| FIXME: collision with object YY - * | | +--+ - * | | - * <------------------->| | - * +--+ +--+ - * |XX| the moved amount is toMove*tracer.mFraction - * +--+ - * ============================================== - */ - osg::Vec3f tracerPos = mUpStepper.mEndPos; - mTracer.doTrace(mColObj, tracerPos, tracerPos + toMove, mColWorld); - if (mTracer.mFraction < std::numeric_limits::epsilon()) - return false; // didn't even move the smallest representable amount - - /* - * Try moving back down sStepSizeDown using stepper. - * NOTE: if there is an obstacle below (e.g. stairs), we'll be "stepping up". - * Below diagram is the case where we "stepped over" an obstacle in front. - * - * +--+ - * |YY| - * +--+ +--+ - * | | - * | | - * +--+ | | - * |XX| | | - * +--+ +--+ - * ============================================== - */ - mDownStepper.doTrace(mColObj, mTracer.mEndPos, mTracer.mEndPos-osg::Vec3f(0.0f,0.0f,sStepSizeDown), mColWorld); - if (!canStepDown(mDownStepper)) + auto toMove = velocity * remainingTime; + + osg::Vec3f tracerPos = position + osg::Vec3f(0.0f, 0.0f, upDistance); + + osg::Vec3f tracerDest; + auto normalMove = toMove; + auto moveDistance = normalMove.normalize(); + // attempt 1: normal movement + // attempt 2: fixed distance movement, only happens on the first movement solver iteration/bounce each frame to avoid a glitch + // attempt 3: further, less tall fixed distance movement, same as above + // If you're making a full conversion you should purge the logic for attempts 2 and 3. Attempts 2 and 3 just try to work around problems with vanilla Morrowind assets. + int attempt = 0; + float downStepSize; + while(attempt < 3) { - // Try again with increased step length - if (mTracer.mFraction < 1.0f || toMove.length2() > sMinStep*sMinStep) - return false; + attempt++; - osg::Vec3f direction = toMove; - direction.normalize(); - mTracer.doTrace(mColObj, tracerPos, tracerPos + direction*sMinStep, mColWorld); - if (mTracer.mFraction < 0.001f) + if(attempt == 1) + tracerDest = tracerPos + toMove; + else if (!firstIteration || !sDoExtraStairHacks) // first attempt failed and not on first movement solver iteration, can't retry -- or we have extra hacks disabled + { return false; + } + else if(attempt == 2) + { + moveDistance = sMinStep; + tracerDest = tracerPos + normalMove*sMinStep; + } + else if(attempt == 3) + { + if(upDistance > sStepSizeUp) + { + upDistance = sStepSizeUp; + tracerPos = position + osg::Vec3f(0.0f, 0.0f, upDistance); + } + moveDistance = sMinStep2; + tracerDest = tracerPos + normalMove*sMinStep2; + } + + mTracer.doTrace(mColObj, tracerPos, tracerDest, mColWorld); + if(mTracer.mHitObject) + { + // map against what we hit, minus the safety margin + moveDistance *= mTracer.mFraction; + if(moveDistance <= sCollisionMargin) // didn't move enough to accomplish anything + { + return false; + } + + moveDistance -= sCollisionMargin; + tracerDest = tracerPos + normalMove*moveDistance; + + // safely eject from what we hit by the safety margin + auto tempDest = tracerDest + mTracer.mPlaneNormal*sCollisionMargin*2; + + ActorTracer tempTracer; + tempTracer.doTrace(mColObj, tracerDest, tempDest, mColWorld); + + if(tempTracer.mFraction > 0.5f) // distance to any object is greater than sCollisionMargin (we checked sCollisionMargin*2 distance) + { + auto effectiveFraction = tempTracer.mFraction*2.0f - 1.0f; + tracerDest += mTracer.mPlaneNormal*sCollisionMargin*effectiveFraction; + } + } + + if(attempt > 2) // do not allow stepping down below original height for attempt 3 + downStepSize = upDistance; + else + downStepSize = moveDistance + upDistance + sStepSizeDown; + mDownStepper.doTrace(mColObj, tracerDest, tracerDest + osg::Vec3f(0.0f, 0.0f, -downStepSize), mColWorld); + + // can't step down onto air, non-walkable-slopes, or actors + // NOTE: using a capsule causes isWalkableSlope (used in canStepDown) to fail on certain geometry that were intended to be valid at the bottoms of stairs + // (like the bottoms of the staircases in aldruhn's guild of mages) + // The old code worked around this by trying to do mTracer again with a fixed distance of sMinStep (10.0) but it caused all sorts of other problems. + // Switched back to cylinders to avoid that and similer problems. + if(canStepDown(mDownStepper)) + { + break; + } + else + { + // do not try attempt 3 if we just tried attempt 2 and the horizontal distance was rather large + // (forces actor to get snug against the defective ledge for attempt 3 to be tried) + if(attempt == 2 && moveDistance > upDistance-(mDownStepper.mFraction*downStepSize)) + { + return false; + } + // do next attempt if first iteration of movement solver and not out of attempts + if(firstIteration && attempt < 3) + { + continue; + } - mDownStepper.doTrace(mColObj, mTracer.mEndPos, mTracer.mEndPos-osg::Vec3f(0.0f,0.0f,sStepSizeDown), mColWorld); - if (!canStepDown(mDownStepper)) return false; + } } - if (mDownStepper.mFraction < 1.0f) + // note: can't downstep onto actors so no need to pick safety margin + float downDistance = 0; + if(mDownStepper.mFraction*downStepSize > sCollisionMargin) + downDistance = mDownStepper.mFraction*downStepSize - sCollisionMargin; + + if(downDistance-sCollisionMargin-sGroundOffset > upDistance && !onGround) + return false; + + auto newpos = tracerDest + osg::Vec3f(0.0f, 0.0f, -downDistance); + + if((position-newpos).length2() < sCollisionMargin*sCollisionMargin) + return false; + + if(mTracer.mHitObject) { - // only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall. - // TODO: stepper.mPlaneNormal does not appear to be reliable - needs more testing - // NOTE: caller's variables 'position' & 'remainingTime' are modified here - position = mDownStepper.mEndPos; - remainingTime *= (1.0f-mTracer.mFraction); // remaining time is proportional to remaining distance - mHaveMoved = true; - return true; + auto planeNormal = mTracer.mPlaneNormal; + if (onGround && !isWalkableSlope(planeNormal) && planeNormal.z() != 0) + { + planeNormal.z() = 0; + planeNormal.normalize(); + } + velocity = reject(velocity, planeNormal); } - return false; + velocity = reject(velocity, mDownStepper.mPlaneNormal); + + position = newpos; + + remainingTime *= (1.0f-mTracer.mFraction); // remaining time is proportional to remaining distance + return true; } } diff --git a/apps/openmw/mwphysics/stepper.hpp b/apps/openmw/mwphysics/stepper.hpp index 27e6294b0..512493c52 100644 --- a/apps/openmw/mwphysics/stepper.hpp +++ b/apps/openmw/mwphysics/stepper.hpp @@ -20,12 +20,11 @@ namespace MWPhysics const btCollisionObject *mColObj; ActorTracer mTracer, mUpStepper, mDownStepper; - bool mHaveMoved; public: Stepper(const btCollisionWorld *colWorld, const btCollisionObject *colObj); - bool step(osg::Vec3f &position, const osg::Vec3f &toMove, float &remainingTime); + bool step(osg::Vec3f &position, osg::Vec3f &velocity, float &remainingTime, const bool & onGround, bool firstIteration); }; } diff --git a/apps/openmw/mwphysics/trace.cpp b/apps/openmw/mwphysics/trace.cpp index 58082f4db..f50b6100a 100644 --- a/apps/openmw/mwphysics/trace.cpp +++ b/apps/openmw/mwphysics/trace.cpp @@ -24,7 +24,7 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star to.setOrigin(btend); const btVector3 motion = btstart-btend; - ClosestNotMeConvexResultCallback newTraceCallback(actor, motion, btScalar(0.0)); + ClosestNotMeConvexResultCallback newTraceCallback(actor, motion, btScalar(0.0), world); // Inherit the actor's collision group and mask newTraceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup; newTraceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask; @@ -62,7 +62,7 @@ void ActorTracer::findGround(const Actor* actor, const osg::Vec3f& start, const btTransform to(trans.getBasis(), btend); const btVector3 motion = btstart-btend; - ClosestNotMeConvexResultCallback newTraceCallback(actor->getCollisionObject(), motion, btScalar(0.0)); + ClosestNotMeConvexResultCallback newTraceCallback(actor->getCollisionObject(), motion, btScalar(0.0), world); // Inherit the actor's collision group and mask newTraceCallback.m_collisionFilterGroup = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterGroup; newTraceCallback.m_collisionFilterMask = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterMask; diff --git a/appveyor.yml b/appveyor.yml index e2c13ed94..ed6f727be 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -50,7 +50,7 @@ install: before_build: - cmd: git submodule update --init --recursive - - cmd: sh %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh -c %configuration% -p %PLATFORM% -v %msvc% -V -i %APPVEYOR_BUILD_FOLDER%\install + - cmd: sh %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh -c %configuration% -p %PLATFORM% -v %msvc% -V -i %APPVEYOR_BUILD_FOLDER%\install -D build_script: - cmd: if %PLATFORM%==Win32 set build=MSVC%msvc%_32 diff --git a/cmake/OSIdentity.cmake b/cmake/OSIdentity.cmake new file mode 100644 index 000000000..40b7b2089 --- /dev/null +++ b/cmake/OSIdentity.cmake @@ -0,0 +1,67 @@ +if (UNIX) + + if (APPLE) + + set(CMAKE_OS_NAME "OSX" CACHE STRING "Operating system name" FORCE) + + else (APPLE) + + ## Check for Debian GNU/Linux ________________ + + find_file(DEBIAN_FOUND debian_version debconf.conf + PATHS /etc + ) + if (DEBIAN_FOUND) + set(CMAKE_OS_NAME "Debian" CACHE STRING "Operating system name" FORCE) + endif (DEBIAN_FOUND) + + ## Check for Fedora _________________________ + + find_file(FEDORA_FOUND fedora-release + PATHS /etc + ) + if (FEDORA_FOUND) + set(CMAKE_OS_NAME "Fedora" CACHE STRING "Operating system name" FORCE) + endif (FEDORA_FOUND) + + ## Check for RedHat _________________________ + + find_file(REDHAT_FOUND redhat-release inittab.RH + PATHS /etc + ) + if (REDHAT_FOUND) + set(CMAKE_OS_NAME "RedHat" CACHE STRING "Operating system name" FORCE) + endif (REDHAT_FOUND) + + ## Extra check for Ubuntu ____________________ + + if (DEBIAN_FOUND) + + ## At its core Ubuntu is a Debian system, with + ## a slightly altered configuration; hence from + ## a first superficial inspection a system will + ## be considered as Debian, which signifies an + ## extra check is required. + + find_file(UBUNTU_EXTRA legal issue + PATHS /etc + ) + + if (UBUNTU_EXTRA) + ## Scan contents of file + file(STRINGS ${UBUNTU_EXTRA} UBUNTU_FOUND + REGEX Ubuntu + ) + ## Check result of string search + if (UBUNTU_FOUND) + set(CMAKE_OS_NAME "Ubuntu" CACHE STRING "Operating system name" FORCE) + set(DEBIAN_FOUND FALSE) + endif (UBUNTU_FOUND) + + endif (UBUNTU_EXTRA) + + endif (DEBIAN_FOUND) + + endif (APPLE) + +endif (UNIX) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 10a5d22fb..832fc611f 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -236,7 +236,6 @@ target_link_libraries(components ${OSGGA_LIBRARIES} ${OSGSHADOW_LIBRARIES} ${OSGANIMATION_LIBRARIES} - ${BULLET_LIBRARIES} ${SDL2_LIBRARIES} ${OPENGL_gl_LIBRARY} ${MyGUI_LIBRARIES} @@ -246,6 +245,12 @@ target_link_libraries(components RecastNavigation::Recast ) +if (BULLET_USE_DOUBLES AND (UBUNTU_FOUND OR DEBIAN_FOUND)) + target_link_libraries(components BulletCollision-float64 LinearMath-float64) +else() + target_link_libraries(components ${BULLET_LIBRARIES}) +endif() + if (WIN32) target_link_libraries(components ${Boost_LOCALE_LIBRARY} From 6a12c2b58b80ca69c93575820be8666d493f60b1 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 28 Dec 2020 12:06:41 +0400 Subject: [PATCH 005/171] Fix GCC build warnings --- apps/openmw/mwmechanics/aifollow.cpp | 1 - apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp | 2 +- apps/openmw/mwphysics/movementsolver.cpp | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 1e4fb206e..f31be9c10 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -119,7 +119,6 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte auto followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowingByIndex(target); if (followers.size() >= 2 && followers.cbegin()->first != mFollowIndex) { - osg::Vec3f::value_type maxSize = 0; for(auto& follower : followers) { auto halfExtent = MWBase::Environment::get().getWorld()->getHalfExtents(follower.second).y(); diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp index 8dd5a624f..27e7a390c 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp @@ -23,7 +23,7 @@ namespace MWPhysics int index0, const btCollisionObjectWrapper* colObj1Wrap, int partId1, - int index1) + int index1) override { if(cp.getDistance() <= 0.0f) overlapping = true; diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 69bf04236..9d2957409 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -41,7 +41,7 @@ namespace MWPhysics m_collisionFilterMask = me->getBroadphaseHandle()->m_collisionFilterMask; mVelocity = Misc::Convert::toBullet(velocity); } - virtual btScalar addSingleResult(btManifoldPoint & contact, const btCollisionObjectWrapper * colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper * colObj1Wrap, int partId1, int index1) + btScalar addSingleResult(btManifoldPoint & contact, const btCollisionObjectWrapper * colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper * colObj1Wrap, int partId1, int index1) override { if (isActor(colObj0Wrap->getCollisionObject()) && isActor(colObj1Wrap->getCollisionObject())) return 0.0; From 66fee6dccbc539770a3adc3ead060c0dbd14e27b Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 28 Dec 2020 11:45:17 +0400 Subject: [PATCH 006/171] Enhance level-up tooltip --- apps/openmw/mwgui/statswindow.cpp | 11 +++++++---- files/mygui/openmw_tooltips.layout | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index e02b2f45f..66a1ea1ef 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -336,14 +337,16 @@ namespace MWGui int max = MWBase::Environment::get().getWorld()->getStore().get().find("iLevelUpTotal")->mValue.getInteger(); getWidget(levelWidget, i==0 ? "Level_str" : "LevelText"); - std::string detail; + std::stringstream detail; for (int i = 0; i < ESM::Attribute::Length; ++i) { if (auto increase = PCstats.getLevelUpAttributeIncrease(i)) - detail += (detail.empty() ? "" : "\n") + ESM::Attribute::sAttributeNames[i] + " x" + MyGUI::utility::toString(increase); + detail << (detail.str().empty() ? "" : "\n") << "#{" + << MyGUI::TextIterator::toTagsString(ESM::Attribute::sGmstAttributeIds[i]) + << "} x" << MyGUI::utility::toString(increase); } - if (!detail.empty()) - levelWidget->setUserString("Caption_LevelDetailText", detail); + if (!detail.str().empty()) + levelWidget->setUserString("Caption_LevelDetailText", MyGUI::LanguageManager::getInstance().replaceTags(detail.str())); levelWidget->setUserString("RangePosition_LevelProgress", MyGUI::utility::toString(PCstats.getLevelProgress())); levelWidget->setUserString("Range_LevelProgress", MyGUI::utility::toString(max)); levelWidget->setUserString("Caption_LevelProgressText", MyGUI::utility::toString(PCstats.getLevelProgress()) + "/" diff --git a/files/mygui/openmw_tooltips.layout b/files/mygui/openmw_tooltips.layout index 59050f932..bb505d253 100644 --- a/files/mygui/openmw_tooltips.layout +++ b/files/mygui/openmw_tooltips.layout @@ -272,6 +272,7 @@ + From ee2416017ecd15baed2dd213d1cca8184d916dfc Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 28 Dec 2020 12:19:22 +0400 Subject: [PATCH 007/171] Fix tag mismatch --- components/nifbullet/bulletnifloader.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index 054b33fed..17a0d3e8b 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -23,7 +23,7 @@ class btCollisionShape; namespace Nif { - class Node; + struct Node; struct Transformation; struct NiTriShape; struct NiTriStrips; From 694b9c15059f7fd3d3cc67acb94339d15e30b918 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 28 Dec 2020 18:11:51 +0100 Subject: [PATCH 008/171] Fixed by 7cac7fa87052e9e14a2e2ec3b5fb489a4820cdbb --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 661a058d4..15bb77e73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Bug #3862: Random container contents behave differently than vanilla Bug #3929: Leveled list merchant containers respawn on barter Bug #4021: Attributes and skills are not stored as floats + Bug #4039: Multiple followers should have the same following distance Bug #4055: Local scripts don't inherit variables from their base record Bug #4083: Door animation freezes when colliding with actors Bug #4247: Cannot walk up stairs in Ebonheart docks From e80d34268ca8d1c5c37ec51d5bb7dec8e67d13cf Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 28 Dec 2020 21:11:58 +0400 Subject: [PATCH 009/171] Fix defines names --- apps/openmw/mwrender/landmanager.hpp | 4 ++-- apps/openmw/mwrender/objectpaging.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/landmanager.hpp b/apps/openmw/mwrender/landmanager.hpp index ea73f11c2..1694bd243 100644 --- a/apps/openmw/mwrender/landmanager.hpp +++ b/apps/openmw/mwrender/landmanager.hpp @@ -1,5 +1,5 @@ -#ifndef OPENMW_COMPONENTS_ESMTERRAIN_LANDMANAGER_H -#define OPENMW_COMPONENTS_ESMTERRAIN_LANDMANAGER_H +#ifndef OPENMW_MWRENDER_LANDMANAGER_H +#define OPENMW_MWRENDER_LANDMANAGER_H #include diff --git a/apps/openmw/mwrender/objectpaging.hpp b/apps/openmw/mwrender/objectpaging.hpp index ff32dadd4..65f53d530 100644 --- a/apps/openmw/mwrender/objectpaging.hpp +++ b/apps/openmw/mwrender/objectpaging.hpp @@ -1,5 +1,5 @@ -#ifndef OPENMW_COMPONENTS_ESMPAGING_CHUNKMANAGER_H -#define OPENMW_COMPONENTS_ESMPAGING_CHUNKMANAGER_H +#ifndef OPENMW_MWRENDER_OBJECTPAGING_H +#define OPENMW_MWRENDER_OBJECTPAGING_H #include #include From e4e19bab1b3b7bbd1f0eb45c58209f731d6d55f2 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Mon, 28 Dec 2020 21:42:04 +0300 Subject: [PATCH 010/171] Make min and max view distance accurate to vanilla --- docs/source/reference/modding/settings/camera.rst | 2 +- files/mygui/openmw_settings_window.layout | 10 +++++----- files/settings-default.cfg | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/source/reference/modding/settings/camera.rst b/docs/source/reference/modding/settings/camera.rst index 5701947dc..c30dcb3f3 100644 --- a/docs/source/reference/modding/settings/camera.rst +++ b/docs/source/reference/modding/settings/camera.rst @@ -46,7 +46,7 @@ viewing distance :Type: floating point :Range: > 0 -:Default: 6656.0 +:Default: 7168.0 This value controls the maximum visible distance (also called the far clipping plane). Larger values significantly improve rendering in exterior spaces, diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index f61af5a10..14ab7c9de 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -377,25 +377,25 @@ - + - - + + - + - + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index c8805faa3..d0793fc81 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -23,7 +23,7 @@ small feature culling pixel size = 2.0 # Maximum visible distance. Caution: this setting # can dramatically affect performance, see documentation for details. -viewing distance = 6656.0 +viewing distance = 7168.0 # Camera field of view in degrees (e.g. 30.0 to 110.0). # Does not affect the player's hands in the first person camera. From e46472442a01ca792371b6da20e42f74033bda26 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 29 Dec 2020 01:40:30 +0000 Subject: [PATCH 011/171] Switch torches to shields for hostile NPCs --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/actors.cpp | 7 +++++++ apps/openmw/mwworld/inventorystore.cpp | 8 ++++++++ apps/openmw/mwworld/inventorystore.hpp | 2 ++ 4 files changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15bb77e73..ce0cce952 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Bug #5101: Hostile followers travel with the player Bug #5108: Savegame bloating due to inefficient fog textures format Bug #5165: Active spells should use real time intead of timestamps + Bug #5300: NPCs don't switch from torch to shield when starting combat Bug #5358: ForceGreeting always resets the dialogue window completely Bug #5363: Enchantment autocalc not always 0/1 Bug #5364: Script fails/stops if trying to startscript an unknown script diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 88c402b14..c9fcf8280 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1430,6 +1430,13 @@ namespace MWMechanics if (heldIter != inventoryStore.end() && heldIter->getTypeName() != typeid(ESM::Light).name()) inventoryStore.unequipItem(*heldIter, ptr); } + else if (heldIter == inventoryStore.end() || heldIter->getTypeName() == typeid(ESM::Light).name()) + { + // For hostile NPCs, see if they have anything better to equip first + auto shield = inventoryStore.getPreferredShield(ptr); + if(shield != inventoryStore.end()) + inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, shield, ptr); + } heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 386bbb30a..38007c1cb 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -556,6 +556,14 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) } } +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getPreferredShield(const MWWorld::Ptr& actor) +{ + TSlots slots; + initSlots (slots); + autoEquipArmor(actor, slots); + return slots[Slot_CarriedLeft]; +} + const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects() const { return mMagicEffects; diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index e70c21480..df69a5709 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -153,6 +153,8 @@ namespace MWWorld ContainerStoreIterator getSlot (int slot); ConstContainerStoreIterator getSlot(int slot) const; + ContainerStoreIterator getPreferredShield(const MWWorld::Ptr& actor); + void unequipAll(const MWWorld::Ptr& actor); ///< Unequip all currently equipped items. From 3bf641d3ce975fd0d39c22036822e800508146d0 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 29 Dec 2020 21:45:59 +0100 Subject: [PATCH 012/171] Show mesh origin --- CHANGELOG.md | 1 + apps/openmw/engine.cpp | 1 + apps/openmw/mwbase/environment.cpp | 12 ++++++++++++ apps/openmw/mwbase/environment.hpp | 10 ++++++++++ apps/openmw/mwscript/miscextensions.cpp | 15 ++++++++++++++- components/bsa/bsa_file.hpp | 5 +++++ components/vfs/archive.hpp | 5 +++++ components/vfs/bsaarchive.cpp | 17 +++++++++++++++++ components/vfs/bsaarchive.hpp | 2 ++ components/vfs/filesystemarchive.cpp | 15 +++++++++++++++ components/vfs/filesystemarchive.hpp | 3 +++ components/vfs/manager.cpp | 11 +++++++++++ components/vfs/manager.hpp | 1 + 13 files changed, 97 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce0cce952..141edcad2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -116,6 +116,7 @@ Feature #5672: Make stretch menu background configuration more accessible Feature #5692: Improve spell/magic item search to factor in magic effect names Feature #5730: Add graphic herbalism option to the launcher and documents + Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used. Task #5480: Drop Qt4 support Task #5520: Improve cell name autocompleter implementation diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 8f23f710d..81a97b0f2 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -749,6 +749,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) // Create dialog system mEnvironment.setJournal (new MWDialogue::Journal); mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mTranslationDataStorage)); + mEnvironment.setResourceSystem(mResourceSystem.get()); // scripts if (mCompileAll) diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index aca2685e0..edb10d945 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include "world.hpp" #include "scriptmanager.hpp" #include "dialoguemanager.hpp" @@ -76,6 +78,11 @@ void MWBase::Environment::setStateManager (StateManager *stateManager) mStateManager = stateManager; } +void MWBase::Environment::setResourceSystem (Resource::ResourceSystem *resourceSystem) +{ + mResourceSystem = resourceSystem; +} + void MWBase::Environment::setFrameDuration (float duration) { mFrameDuration = duration; @@ -158,6 +165,11 @@ MWBase::StateManager *MWBase::Environment::getStateManager() const return mStateManager; } +Resource::ResourceSystem *MWBase::Environment::getResourceSystem() const +{ + return mResourceSystem; +} + float MWBase::Environment::getFrameDuration() const { return mFrameDuration; diff --git a/apps/openmw/mwbase/environment.hpp b/apps/openmw/mwbase/environment.hpp index 80e6a6243..7871153cc 100644 --- a/apps/openmw/mwbase/environment.hpp +++ b/apps/openmw/mwbase/environment.hpp @@ -6,6 +6,11 @@ namespace osg class Stats; } +namespace Resource +{ + class ResourceSystem; +} + namespace MWBase { class World; @@ -37,6 +42,7 @@ namespace MWBase Journal *mJournal; InputManager *mInputManager; StateManager *mStateManager; + Resource::ResourceSystem *mResourceSystem; float mFrameDuration; float mFrameRateLimit; @@ -70,6 +76,8 @@ namespace MWBase void setStateManager (StateManager *stateManager); + void setResourceSystem (Resource::ResourceSystem *resourceSystem); + void setFrameDuration (float duration); ///< Set length of current frame in seconds. @@ -95,6 +103,8 @@ namespace MWBase StateManager *getStateManager() const; + Resource::ResourceSystem *getResourceSystem() const; + float getFrameDuration() const; void cleanup(); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index a288d6673..d78337a62 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -13,10 +13,15 @@ #include #include +#include + +#include #include #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/scriptmanager.hpp" @@ -1377,7 +1382,15 @@ namespace MWScript msg << "Grid: " << cell->getCell()->getGridX() << " " << cell->getCell()->getGridY() << std::endl; osg::Vec3f pos (ptr.getRefData().getPosition().asVec3()); msg << "Coordinates: " << pos.x() << " " << pos.y() << " " << pos.z() << std::endl; - msg << "Model: " << ptr.getClass().getModel(ptr) << std::endl; + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + std::string model = ::Misc::ResourceHelpers::correctActorModelPath(ptr.getClass().getModel(ptr), vfs); + msg << "Model: " << model << std::endl; + if(!model.empty()) + { + const std::string archive = vfs->getArchive(model); + if(!archive.empty()) + msg << "(" << archive << ")" << std::endl; + } if (!ptr.getClass().getScript(ptr).empty()) msg << "Script: " << ptr.getClass().getScript(ptr) << std::endl; } diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index 037802739..3e7538401 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -135,6 +135,11 @@ public: /// @note Thread safe. const FileList &getList() const { return mFiles; } + + const std::string& getFilename() const + { + return mFilename; + } }; } diff --git a/components/vfs/archive.hpp b/components/vfs/archive.hpp index b36c7117b..971ac15b3 100644 --- a/components/vfs/archive.hpp +++ b/components/vfs/archive.hpp @@ -23,6 +23,11 @@ namespace VFS /// List all resources contained in this archive, and run the resource names through the given normalize function. virtual void listResources(std::map& out, char (*normalize_function) (char)) = 0; + + /// True if this archive contains the provided normalized file. + virtual bool contains(const std::string& file, char (*normalize_function) (char)) const = 0; + + virtual std::string getDescription() const = 0; }; } diff --git a/components/vfs/bsaarchive.cpp b/components/vfs/bsaarchive.cpp index ac65c58a1..e6d779aab 100644 --- a/components/vfs/bsaarchive.cpp +++ b/components/vfs/bsaarchive.cpp @@ -39,6 +39,23 @@ void BsaArchive::listResources(std::map &out, char (*normal } } +bool BsaArchive::contains(const std::string& file, char (*normalize_function)(char)) const +{ + for (const auto& it : mResources) + { + std::string ent = it.mInfo->name; + std::transform(ent.begin(), ent.end(), ent.begin(), normalize_function); + if(file == ent) + return true; + } + return false; +} + +std::string BsaArchive::getDescription() const +{ + return std::string{"BSA: "} + mFile->getFilename(); +} + // ------------------------------------------------------------------------------ BsaArchiveFile::BsaArchiveFile(const Bsa::BSAFile::FileStruct *info, Bsa::BSAFile* bsa) diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 65a9db16c..c979b5ce7 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -24,6 +24,8 @@ namespace VFS BsaArchive(const std::string& filename); virtual ~BsaArchive(); void listResources(std::map& out, char (*normalize_function) (char)) override; + bool contains(const std::string& file, char (*normalize_function) (char)) const override; + std::string getDescription() const override; private: std::unique_ptr mFile; diff --git a/components/vfs/filesystemarchive.cpp b/components/vfs/filesystemarchive.cpp index ce4ff020e..17f3891ec 100644 --- a/components/vfs/filesystemarchive.cpp +++ b/components/vfs/filesystemarchive.cpp @@ -53,6 +53,21 @@ namespace VFS } } + bool FileSystemArchive::contains(const std::string& file, char (*normalize_function)(char)) const + { + for (const auto& it : mIndex) + { + if(it.first == file) + return true; + } + return false; + } + + std::string FileSystemArchive::getDescription() const + { + return std::string{"DIR: "} + mPath; + } + // ---------------------------------------------------------------------------------- FileSystemArchiveFile::FileSystemArchiveFile(const std::string &path) diff --git a/components/vfs/filesystemarchive.hpp b/components/vfs/filesystemarchive.hpp index d228ba87c..70463d32f 100644 --- a/components/vfs/filesystemarchive.hpp +++ b/components/vfs/filesystemarchive.hpp @@ -25,6 +25,9 @@ namespace VFS void listResources(std::map& out, char (*normalize_function) (char)) override; + bool contains(const std::string& file, char (*normalize_function) (char)) const override; + + std::string getDescription() const override; private: typedef std::map index; diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index c19882381..c7abc2483 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -96,4 +96,15 @@ namespace VFS normalize_path(name, mStrict); } + std::string Manager::getArchive(const std::string& name) const + { + std::string normalized = name; + normalize_path(normalized, mStrict); + for(const auto archive : mArchives) + { + if(archive->contains(normalized, mStrict ? &strict_normalize_char : &nonstrict_normalize_char)) + return archive->getDescription(); + } + return {}; + } } diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index c5f0a8fec..5a09a995e 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -58,6 +58,7 @@ namespace VFS /// @note May be called from any thread once the index has been built. Files::IStreamPtr getNormalized(const std::string& normalizedName) const; + std::string getArchive(const std::string& name) const; private: bool mStrict; From 630ec36d1fcaa68805731c4ac6bc4123b173a8f0 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 30 Dec 2020 10:35:51 +0100 Subject: [PATCH 013/171] iterate in reverse order --- components/vfs/manager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index c7abc2483..045fe3cf5 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -100,10 +100,10 @@ namespace VFS { std::string normalized = name; normalize_path(normalized, mStrict); - for(const auto archive : mArchives) + for(auto it = mArchives.rbegin(); it != mArchives.rend(); ++it) { - if(archive->contains(normalized, mStrict ? &strict_normalize_char : &nonstrict_normalize_char)) - return archive->getDescription(); + if((*it)->contains(normalized, mStrict ? &strict_normalize_char : &nonstrict_normalize_char)) + return (*it)->getDescription(); } return {}; } From 57c92673bc804653fd9502fa2b90c597f3620562 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 30 Dec 2020 16:09:12 +0100 Subject: [PATCH 014/171] Consider a path completed if it was non-empty --- apps/openmw/mwmechanics/aipackage.cpp | 2 +- apps/openmw/mwmechanics/pathfinding.cpp | 4 ++++ apps/openmw/mwmechanics/pathfinding.hpp | 9 +++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 8880820dd..8af2c71ed 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -158,7 +158,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& zTurn(actor, getZAngleToPoint(position, dest)); smoothTurn(actor, getXAngleToPoint(position, dest), 0); world->removeActorPath(actor); - return isDestReached; + return isDestReached || mPathFinder.pathWasPossible(); } world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest); diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index a82dcf717..77ab78fd7 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -318,6 +318,7 @@ namespace MWMechanics mPath.clear(); mPath.push_back(endPoint); mConstructed = true; + mPossible = true; } void PathFinder::buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, @@ -329,6 +330,7 @@ namespace MWMechanics buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath)); mConstructed = true; + mPossible = !mPath.empty(); } void PathFinder::buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, @@ -342,6 +344,7 @@ namespace MWMechanics mPath.push_back(endPoint); mConstructed = true; + mPossible = !mPath.empty(); } void PathFinder::buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, @@ -367,6 +370,7 @@ namespace MWMechanics mPath.push_back(endPoint); mConstructed = true; + mPossible = !mPath.empty(); } bool PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 5af822fee..ed786dc84 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -74,6 +74,7 @@ namespace MWMechanics public: PathFinder() : mConstructed(false) + , mPossible(false) , mCell(nullptr) { } @@ -81,6 +82,7 @@ namespace MWMechanics void clearPath() { mConstructed = false; + mPossible = false; mPath.clear(); mCell = nullptr; } @@ -109,6 +111,11 @@ namespace MWMechanics return mConstructed && mPath.empty(); } + bool pathWasPossible() const + { + return mPossible; + } + /// In radians float getZAngleToNext(float x, float y) const; @@ -137,6 +144,7 @@ namespace MWMechanics void addPointToPath(const osg::Vec3f& point) { mConstructed = true; + mPossible = true; mPath.push_back(point); } @@ -196,6 +204,7 @@ namespace MWMechanics private: bool mConstructed; + bool mPossible; std::deque mPath; const MWWorld::CellStore* mCell; From dbdd397716b366baf492f21af3a71ed12e885e0d Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 1 Jan 2021 16:54:45 +0100 Subject: [PATCH 015/171] Remove deadcode. --- apps/launcher/advancedpage.cpp | 4 +--- apps/launcher/advancedpage.hpp | 4 +--- apps/launcher/datafilespage.hpp | 4 ---- apps/launcher/graphicspage.cpp | 3 +-- apps/launcher/graphicspage.hpp | 3 +-- apps/launcher/maindialog.cpp | 4 ++-- apps/launcher/utils/cellnameloader.cpp | 3 ++- components/config/gamesettings.cpp | 2 -- components/config/gamesettings.hpp | 5 ++--- components/config/launchersettings.cpp | 4 ---- components/config/launchersettings.hpp | 3 --- 11 files changed, 10 insertions(+), 29 deletions(-) diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index ed0407235..6f59ade7a 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -10,11 +10,9 @@ #include -Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, - Config::GameSettings &gameSettings, +Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings, Settings::Manager &engineSettings, QWidget *parent) : QWidget(parent) - , mCfgMgr(cfg) , mGameSettings(gameSettings) , mEngineSettings(engineSettings) { diff --git a/apps/launcher/advancedpage.hpp b/apps/launcher/advancedpage.hpp index ef2f4740f..a373fae43 100644 --- a/apps/launcher/advancedpage.hpp +++ b/apps/launcher/advancedpage.hpp @@ -9,7 +9,6 @@ #include -namespace Files { struct ConfigurationManager; } namespace Config { class GameSettings; } namespace Launcher @@ -19,7 +18,7 @@ namespace Launcher Q_OBJECT public: - AdvancedPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, + AdvancedPage(Config::GameSettings &gameSettings, Settings::Manager &engineSettings, QWidget *parent = nullptr); bool loadSettings(); @@ -35,7 +34,6 @@ namespace Launcher void slotViewOverShoulderToggled(bool checked); private: - Files::ConfigurationManager &mCfgMgr; Config::GameSettings &mGameSettings; Settings::Manager &mEngineSettings; QCompleter mCellNameCompleter; diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 92984847e..3d200e6d2 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -88,11 +88,7 @@ namespace Launcher QStringList previousSelectedFiles; QString mDataLocal; - void setPluginsCheckstates(Qt::CheckState state); - void buildView(); - void setupConfig(); - void readConfig(); void setProfile (int index, bool savePrevious); void setProfile (const QString &previous, const QString ¤t, bool savePrevious); void removeProfile (const QString &profile); diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index d7e7eabc5..d1cf3aa6f 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -29,9 +29,8 @@ QString getAspect(int x, int y) return QString(QString::number(xaspect) + ":" + QString::number(yaspect)); } -Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent) +Launcher::GraphicsPage::GraphicsPage(Settings::Manager &engineSettings, QWidget *parent) : QWidget(parent) - , mCfgMgr(cfg) , mEngineSettings(engineSettings) { setObjectName ("GraphicsPage"); diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index 55178e0d7..35f711500 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -20,7 +20,7 @@ namespace Launcher Q_OBJECT public: - GraphicsPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent = nullptr); + GraphicsPage(Settings::Manager &engineSettings, QWidget *parent = nullptr); void saveSettings(); bool loadSettings(); @@ -35,7 +35,6 @@ namespace Launcher void slotShadowDistLimitToggled(bool checked); private: - Files::ConfigurationManager &mCfgMgr; Settings::Manager &mEngineSettings; QVector mResolutionsPerScreen; diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index df2ba6891..215258bcd 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -126,9 +126,9 @@ void Launcher::MainDialog::createPages() mPlayPage = new PlayPage(this); mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this); - mGraphicsPage = new GraphicsPage(mCfgMgr, mEngineSettings, this); + mGraphicsPage = new GraphicsPage(mEngineSettings, this); mSettingsPage = new SettingsPage(mCfgMgr, mGameSettings, mLauncherSettings, this); - mAdvancedPage = new AdvancedPage(mCfgMgr, mGameSettings, mEngineSettings, this); + mAdvancedPage = new AdvancedPage(mGameSettings, mEngineSettings, this); // Set the combobox of the play page to imitate the combobox on the datafilespage mPlayPage->setProfilesModel(mDataFilesPage->profilesModel()); diff --git a/apps/launcher/utils/cellnameloader.cpp b/apps/launcher/utils/cellnameloader.cpp index 6d1ed2f49..e7f6e83e7 100644 --- a/apps/launcher/utils/cellnameloader.cpp +++ b/apps/launcher/utils/cellnameloader.cpp @@ -45,4 +45,5 @@ QString CellNameLoader::getCellName(ESM::ESMReader &esmReader) cell.loadNameAndData(esmReader, isDeleted); return QString::fromStdString(cell.mName); -} \ No newline at end of file +} + diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index 85c090a3f..d7fe7da94 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -14,8 +14,6 @@ Config::GameSettings::GameSettings(Files::ConfigurationManager &cfg) { } -Config::GameSettings::~GameSettings() = default; - void Config::GameSettings::validatePaths() { QStringList paths = mSettings.values(QString("data")); diff --git a/components/config/gamesettings.hpp b/components/config/gamesettings.hpp index ccb1d5fd2..263a151c9 100644 --- a/components/config/gamesettings.hpp +++ b/components/config/gamesettings.hpp @@ -21,7 +21,6 @@ namespace Config { public: GameSettings(Files::ConfigurationManager &cfg); - ~GameSettings(); inline QString value(const QString &key, const QString &defaultValue = QString()) { @@ -54,11 +53,11 @@ namespace Config mUserSettings.remove(key); } - inline QStringList getDataDirs() { return mDataDirs; } + inline QStringList getDataDirs() const { return mDataDirs; } inline void removeDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.removeAll(dir); } inline void addDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.append(dir); } - inline QString getDataLocal() {return mDataLocal; } + inline QString getDataLocal() const {return mDataLocal; } bool hasMaster(); diff --git a/components/config/launchersettings.cpp b/components/config/launchersettings.cpp index ff2c38438..025bc4382 100644 --- a/components/config/launchersettings.cpp +++ b/components/config/launchersettings.cpp @@ -12,10 +12,6 @@ const char Config::LauncherSettings::sLauncherConfigFileName[] = "launcher.cfg"; const char Config::LauncherSettings::sContentListsSectionPrefix[] = "Profiles/"; const char Config::LauncherSettings::sContentListSuffix[] = "/content"; -Config::LauncherSettings::LauncherSettings() = default; - -Config::LauncherSettings::~LauncherSettings() = default; - QStringList Config::LauncherSettings::subKeys(const QString &key) { QMultiMap settings = SettingsBase::getSettings(); diff --git a/components/config/launchersettings.hpp b/components/config/launchersettings.hpp index 1483052bb..da492c85c 100644 --- a/components/config/launchersettings.hpp +++ b/components/config/launchersettings.hpp @@ -9,9 +9,6 @@ namespace Config class LauncherSettings : public SettingsBase > { public: - LauncherSettings(); - ~LauncherSettings(); - bool writeFile(QTextStream &stream); /// \return names of all Content Lists in the launcher's .cfg file. From 5215ffd964aaa13297f8fd15d96a2f1a9e79eb9e Mon Sep 17 00:00:00 2001 From: fredzio Date: Tue, 22 Dec 2020 14:51:16 +0100 Subject: [PATCH 016/171] The debug drawer rely on Bullet to determines the vertices of collision shapes. It doesn't work in the case of spheres (used by projectiles): resulting shape is malformed. It can also leads to this error which makes the debug drawer non-working till game restart: CullVisitor::apply(Geode&) detected NaN, depth=nan, center=(nan nan nan), matrix={ -0.265814 -0.0639702 0.9619 0 0.964024 -0.0176387 0.265228 0 -4.29769e-09 0.997796 0.0663574 0 18178.6 -3550.91 42154.4 1 } Avoid this issue by using osg::Sphere While here, remove an unused function. We don't use Bullet's solver so we never have any contact points (in Bullet parlance). --- apps/openmw/mwrender/bulletdebugdraw.cpp | 22 +++++++++++++++++----- apps/openmw/mwrender/bulletdebugdraw.hpp | 4 +++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwrender/bulletdebugdraw.cpp b/apps/openmw/mwrender/bulletdebugdraw.cpp index 00529ef80..207b0ee50 100644 --- a/apps/openmw/mwrender/bulletdebugdraw.cpp +++ b/apps/openmw/mwrender/bulletdebugdraw.cpp @@ -7,6 +7,9 @@ #include #include +#include +#include +#include #include "bulletdebugdraw.hpp" #include "vismask.hpp" @@ -41,6 +44,14 @@ void DebugDrawer::createGeometry() mGeometry->addPrimitiveSet(mDrawArrays); mParentNode->addChild(mGeometry); + + auto* stateSet = new osg::StateSet; + stateSet->setAttributeAndModes(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE), osg::StateAttribute::ON); + mShapesRoot = new osg::Group; + mShapesRoot->setStateSet(stateSet); + mShapesRoot->setDataVariance(osg::Object::DYNAMIC); + mShapesRoot->setNodeMask(Mask_Debug); + mParentNode->addChild(mShapesRoot); } } @@ -49,6 +60,7 @@ void DebugDrawer::destroyGeometry() if (mGeometry) { mParentNode->removeChild(mGeometry); + mParentNode->removeChild(mShapesRoot); mGeometry = nullptr; mVertices = nullptr; mDrawArrays = nullptr; @@ -66,6 +78,7 @@ void DebugDrawer::step() { mVertices->clear(); mColors->clear(); + mShapesRoot->removeChildren(0, mShapesRoot->getNumChildren()); mWorld->debugDrawWorld(); showCollisions(); mDrawArrays->setCount(mVertices->size()); @@ -106,12 +119,11 @@ void DebugDrawer::showCollisions() mCollisionViews.end()); } -void DebugDrawer::drawContactPoint(const btVector3 &PointOnB, const btVector3 &normalOnB, btScalar distance, int lifeTime, const btVector3 &color) +void DebugDrawer::drawSphere(btScalar radius, const btTransform& transform, const btVector3& color) { - mVertices->push_back(Misc::Convert::toOsg(PointOnB)); - mVertices->push_back(Misc::Convert::toOsg(PointOnB) + (Misc::Convert::toOsg(normalOnB) * distance * 20)); - mColors->push_back({1,1,1,1}); - mColors->push_back({1,1,1,1}); + auto* geom = new osg::ShapeDrawable(new osg::Sphere(Misc::Convert::toOsg(transform.getOrigin()), radius)); + geom->setColor(osg::Vec4(1, 1, 1, 1)); + mShapesRoot->addChild(geom); } void DebugDrawer::reportErrorWarning(const char *warningString) diff --git a/apps/openmw/mwrender/bulletdebugdraw.hpp b/apps/openmw/mwrender/bulletdebugdraw.hpp index ec421bd74..b24640427 100644 --- a/apps/openmw/mwrender/bulletdebugdraw.hpp +++ b/apps/openmw/mwrender/bulletdebugdraw.hpp @@ -32,6 +32,7 @@ private: CollisionView(btVector3 orig, btVector3 normal) : mOrig(orig), mEnd(orig + normal * 20), mCreated(std::chrono::steady_clock::now()) {}; }; std::vector mCollisionViews; + osg::ref_ptr mShapesRoot; protected: osg::ref_ptr mParentNode; @@ -59,7 +60,8 @@ public: void showCollisions(); - void drawContactPoint(const btVector3& PointOnB,const btVector3& normalOnB,btScalar distance,int lifeTime,const btVector3& color) override; + void drawContactPoint(const btVector3& PointOnB,const btVector3& normalOnB,btScalar distance,int lifeTime,const btVector3& color) override {}; + void drawSphere(btScalar radius, const btTransform& transform, const btVector3& color) override; void reportErrorWarning(const char* warningString) override; From 701a5662b02b4d92c884774ddf75905b3f7b1909 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sun, 3 Jan 2021 09:33:03 +0000 Subject: [PATCH 017/171] Remove #5683 changelog entry --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce0cce952..fa3572282 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,7 +81,6 @@ Bug #5661: Region sounds don't play at the right interval Bug #5675: OpenMW-cs. FRMR subrecords are saved with the wrong MastIdx Bug #5681: Player character can clip or pass through bridges instead of colliding against them - Bug #5683: Player character can get stuck with MR's balmora's wooden gate Bug #5688: Water shader broken indoors with enable indoor shadows = false Bug #5695: ExplodeSpell for actors doesn't target the ground Bug #5703: OpenMW-CS menu system crashing on XFCE From 2a583e2337f8003a8ba47ac08fd8ab65d03d149b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 8 Jan 2021 17:24:13 +0100 Subject: [PATCH 018/171] consider empty paths as not constructed --- apps/openmw/mwmechanics/aipackage.cpp | 4 +++- apps/openmw/mwmechanics/pathfinding.cpp | 10 +++------- apps/openmw/mwmechanics/pathfinding.hpp | 9 --------- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 8af2c71ed..ce5673909 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -158,8 +158,10 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& zTurn(actor, getZAngleToPoint(position, dest)); smoothTurn(actor, getXAngleToPoint(position, dest), 0); world->removeActorPath(actor); - return isDestReached || mPathFinder.pathWasPossible(); + return true; } + else if (mPathFinder.getPath().empty()) + return false; world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest); diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 77ab78fd7..276321b81 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -318,7 +318,6 @@ namespace MWMechanics mPath.clear(); mPath.push_back(endPoint); mConstructed = true; - mPossible = true; } void PathFinder::buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, @@ -329,8 +328,7 @@ namespace MWMechanics buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath)); - mConstructed = true; - mPossible = !mPath.empty(); + mConstructed = !mPath.empty(); } void PathFinder::buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, @@ -343,8 +341,7 @@ namespace MWMechanics if (!buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, areaCosts, std::back_inserter(mPath))) mPath.push_back(endPoint); - mConstructed = true; - mPossible = !mPath.empty(); + mConstructed = !mPath.empty(); } void PathFinder::buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, @@ -369,8 +366,7 @@ namespace MWMechanics if (!hasNavMesh && mPath.empty()) mPath.push_back(endPoint); - mConstructed = true; - mPossible = !mPath.empty(); + mConstructed = !mPath.empty(); } bool PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index ed786dc84..5af822fee 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -74,7 +74,6 @@ namespace MWMechanics public: PathFinder() : mConstructed(false) - , mPossible(false) , mCell(nullptr) { } @@ -82,7 +81,6 @@ namespace MWMechanics void clearPath() { mConstructed = false; - mPossible = false; mPath.clear(); mCell = nullptr; } @@ -111,11 +109,6 @@ namespace MWMechanics return mConstructed && mPath.empty(); } - bool pathWasPossible() const - { - return mPossible; - } - /// In radians float getZAngleToNext(float x, float y) const; @@ -144,7 +137,6 @@ namespace MWMechanics void addPointToPath(const osg::Vec3f& point) { mConstructed = true; - mPossible = true; mPath.push_back(point); } @@ -204,7 +196,6 @@ namespace MWMechanics private: bool mConstructed; - bool mPossible; std::deque mPath; const MWWorld::CellStore* mCell; From 7d551b0cfde472da8acb7e8a8f1a0b1899694ad9 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 8 Jan 2021 23:21:39 +0100 Subject: [PATCH 019/171] Split long osg log messages into lines. --- apps/openmw/main.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 89aa2b9fd..8eaac36e8 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -254,7 +254,19 @@ namespace level = Debug::Debug; } std::string_view s(msgCopy); - Log(level) << (s.back() == '\n' ? s.substr(0, s.size() - 1) : s); + if (s.size() < 1024) + Log(level) << (s.back() == '\n' ? s.substr(0, s.size() - 1) : s); + else + { + while (!s.empty()) + { + size_t lineSize = 1; + while (lineSize < s.size() && s[lineSize - 1] != '\n') + lineSize++; + Log(level) << s.substr(0, s[lineSize - 1] == '\n' ? lineSize - 1 : lineSize); + s = s.substr(lineSize); + } + } } }; } From 944033db4e4e3d7d0205480a8763f4d714652d59 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 18 Jul 2020 23:56:14 +0200 Subject: [PATCH 020/171] Separate sound buffer pool from sound manager --- apps/openmw/CMakeLists.txt | 1 + apps/openmw/mwsound/sound.hpp | 7 + apps/openmw/mwsound/sound_buffer.cpp | 154 +++++++++++++++++++++ apps/openmw/mwsound/sound_buffer.hpp | 91 +++++++++++-- apps/openmw/mwsound/sound_output.hpp | 9 +- apps/openmw/mwsound/soundmanagerimp.cpp | 169 +++--------------------- apps/openmw/mwsound/soundmanagerimp.hpp | 29 +--- 7 files changed, 276 insertions(+), 184 deletions(-) create mode 100644 apps/openmw/mwsound/sound_buffer.cpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index a4c3b9136..f32240a91 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -58,6 +58,7 @@ add_openmw_dir (mwscript add_openmw_dir (mwsound soundmanagerimp openal_output ffmpeg_decoder sound sound_buffer sound_decoder sound_output loudness movieaudiofactory alext efx efx-presets regionsoundselector watersoundupdater volumesettings + sound_buffer ) add_openmw_dir (mwworld diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 9d264e1b6..d2e65c989 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -7,6 +7,13 @@ namespace MWSound { + // Extra play flags, not intended for caller use + enum PlayModeEx + { + Play_2D = 0, + Play_3D = 1 << 31, + }; + // For testing individual PlayMode flags inline int operator&(int a, PlayMode b) { return a & static_cast(b); } inline int operator&(PlayMode a, PlayMode b) { return static_cast(a) & static_cast(b); } diff --git a/apps/openmw/mwsound/sound_buffer.cpp b/apps/openmw/mwsound/sound_buffer.cpp new file mode 100644 index 000000000..0e25ff601 --- /dev/null +++ b/apps/openmw/mwsound/sound_buffer.cpp @@ -0,0 +1,154 @@ +#include "sound_buffer.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwworld/esmstore.hpp" + +#include +#include +#include + +#include +#include + +namespace MWSound +{ + namespace + { + struct AudioParams + { + float mAudioDefaultMinDistance; + float mAudioDefaultMaxDistance; + float mAudioMinDistanceMult; + float mAudioMaxDistanceMult; + }; + + AudioParams makeAudioParams(const MWBase::World& world) + { + const auto& settings = world.getStore().get(); + AudioParams params; + params.mAudioDefaultMinDistance = settings.find("fAudioDefaultMinDistance")->mValue.getFloat(); + params.mAudioDefaultMaxDistance = settings.find("fAudioDefaultMaxDistance")->mValue.getFloat(); + params.mAudioMinDistanceMult = settings.find("fAudioMinDistanceMult")->mValue.getFloat(); + params.mAudioMaxDistanceMult = settings.find("fAudioMaxDistanceMult")->mValue.getFloat(); + return params; + } + } + + SoundBufferPool::SoundBufferPool(const VFS::Manager& vfs, Sound_Output& output) : + mVfs(&vfs), + mOutput(&output), + mBufferCacheMax(std::max(Settings::Manager::getInt("buffer cache max", "Sound"), 1) * 1024 * 1024), + mBufferCacheMin(std::min(static_cast(std::max(Settings::Manager::getInt("buffer cache min", "Sound"), 1)) * 1024 * 1024, mBufferCacheMax)) + { + } + + SoundBufferPool::~SoundBufferPool() + { + clear(); + } + + Sound_Buffer* SoundBufferPool::lookup(const std::string& soundId) const + { + const auto it = mBufferNameMap.find(soundId); + if (it != mBufferNameMap.end()) + { + Sound_Buffer* sfx = it->second; + if (sfx->getHandle() != nullptr) + return sfx; + } + return nullptr; + } + + Sound_Buffer* SoundBufferPool::load(const std::string& soundId) + { + if (mBufferNameMap.empty()) + { + for (const ESM::Sound& sound : MWBase::Environment::get().getWorld()->getStore().get()) + insertSound(Misc::StringUtils::lowerCase(sound.mId), sound); + } + + Sound_Buffer* sfx; + const auto it = mBufferNameMap.find(soundId); + if (it != mBufferNameMap.end()) + sfx = it->second; + else + { + const ESM::Sound *sound = MWBase::Environment::get().getWorld()->getStore().get().search(soundId); + if (sound == nullptr) + return {}; + sfx = insertSound(soundId, *sound); + } + + if (sfx->getHandle() == nullptr) + { + Sound_Handle handle; + size_t size; + std::tie(handle, size) = mOutput->loadSound(sfx->getResourceName()); + if (handle == nullptr) + return {}; + + sfx->mHandle = handle; + + mBufferCacheSize += size; + if (mBufferCacheSize > mBufferCacheMax) + { + unloadUnused(); + if (!mUnusedBuffers.empty() && mBufferCacheSize > mBufferCacheMax) + Log(Debug::Warning) << "No unused sound buffers to free, using " << mBufferCacheSize << " bytes!"; + } + mUnusedBuffers.push_front(sfx); + } + + return sfx; + } + + void SoundBufferPool::clear() + { + for (auto &sfx : mSoundBuffers) + { + if(sfx.mHandle) + mOutput->unloadSound(sfx.mHandle); + sfx.mHandle = nullptr; + } + mUnusedBuffers.clear(); + } + + Sound_Buffer* SoundBufferPool::insertSound(const std::string& soundId, const ESM::Sound& sound) + { + static const AudioParams audioParams = makeAudioParams(*MWBase::Environment::get().getWorld()); + + float volume = static_cast(std::pow(10.0, (sound.mData.mVolume / 255.0 * 3348.0 - 3348.0) / 2000.0)); + float min = sound.mData.mMinRange; + float max = sound.mData.mMaxRange; + if (min == 0 && max == 0) + { + min = audioParams.mAudioDefaultMinDistance; + max = audioParams.mAudioDefaultMaxDistance; + } + + min *= audioParams.mAudioMinDistanceMult; + max *= audioParams.mAudioMaxDistanceMult; + min = std::max(min, 1.0f); + max = std::max(min, max); + + Sound_Buffer& sfx = mSoundBuffers.emplace_back("Sound/" + sound.mSound, volume, min, max); + mVfs->normalizeFilename(sfx.mResourceName); + + mBufferNameMap.emplace(soundId, &sfx); + return &sfx; + } + + void SoundBufferPool::unloadUnused() + { + while (!mUnusedBuffers.empty() && mBufferCacheSize > mBufferCacheMin) + { + Sound_Buffer* const unused = mUnusedBuffers.back(); + + mBufferCacheSize -= mOutput->unloadSound(unused->getHandle()); + unused->mHandle = nullptr; + + mUnusedBuffers.pop_back(); + } + } +} diff --git a/apps/openmw/mwsound/sound_buffer.hpp b/apps/openmw/mwsound/sound_buffer.hpp index 83b08d6be..e62392300 100644 --- a/apps/openmw/mwsound/sound_buffer.hpp +++ b/apps/openmw/mwsound/sound_buffer.hpp @@ -1,27 +1,100 @@ #ifndef GAME_SOUND_SOUND_BUFFER_H #define GAME_SOUND_SOUND_BUFFER_H +#include #include +#include +#include #include "sound_output.hpp" +namespace ESM +{ + struct Sound; +} + +namespace VFS +{ + class Manager; +} + namespace MWSound { + class SoundBufferPool; + class Sound_Buffer { - public: - std::string mResourceName; + public: + Sound_Buffer(std::string resname, float volume, float mindist, float maxdist) + : mResourceName(std::move(resname)), mVolume(volume), mMinDist(mindist), mMaxDist(maxdist) + {} + + const std::string& getResourceName() const noexcept { return mResourceName; } + + Sound_Handle getHandle() const noexcept { return mHandle; } + + float getVolume() const noexcept { return mVolume; } + + float getMinDist() const noexcept { return mMinDist; } + + float getMaxDist() const noexcept { return mMaxDist; } + + private: + std::string mResourceName; + float mVolume = 0; + float mMinDist = 0; + float mMaxDist = 0; + Sound_Handle mHandle = nullptr; + std::size_t mUses = 0; + + friend class SoundBufferPool; + }; + + class SoundBufferPool + { + public: + SoundBufferPool(const VFS::Manager& vfs, Sound_Output& output); + + SoundBufferPool(const SoundBufferPool&) = delete; + + ~SoundBufferPool(); + + Sound_Buffer* lookup(const std::string& soundId) const; + + Sound_Buffer* load(const std::string& soundId); + + void use(Sound_Buffer& sfx) + { + if (sfx.mUses++ == 0) + { + const auto it = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), &sfx); + if (it != mUnusedBuffers.end()) + mUnusedBuffers.erase(it); + } + } + + void release(Sound_Buffer& sfx) + { + if (--sfx.mUses == 0) + mUnusedBuffers.push_front(&sfx); + } - float mVolume; - float mMinDist, mMaxDist; + void clear(); - Sound_Handle mHandle; + private: + const VFS::Manager* const mVfs; + Sound_Output* mOutput; + std::deque mSoundBuffers; + std::unordered_map mBufferNameMap; + std::size_t mBufferCacheMax; + std::size_t mBufferCacheMin; + std::size_t mBufferCacheSize = 0; + // NOTE: unused buffers are stored in front-newest order. + std::deque mUnusedBuffers; - size_t mUses; + inline Sound_Buffer* insertSound(const std::string& soundId, const ESM::Sound& sound); - Sound_Buffer(std::string resname, float volume, float mindist, float maxdist) - : mResourceName(resname), mVolume(volume), mMinDist(mindist), mMaxDist(maxdist), mHandle(nullptr), mUses(0) - { } + inline void unloadUnused(); }; } diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 4075e36cc..9ec8b17dc 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -5,7 +5,7 @@ #include #include -#include "soundmanagerimp.hpp" +#include "../mwbase/soundmanager.hpp" namespace MWSound { @@ -25,6 +25,12 @@ namespace MWSound Auto }; + enum Environment + { + Env_Normal, + Env_Underwater + }; + class Sound_Output { SoundManager &mManager; @@ -81,6 +87,7 @@ namespace MWSound friend class OpenAL_Output; friend class SoundManager; + friend class SoundBufferPool; }; } diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 37ba80820..03ad58088 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -56,8 +56,7 @@ namespace MWSound : mVFS(vfs) , mOutput(new DEFAULT_OUTPUT(*this)) , mWaterSoundUpdater(makeWaterSoundUpdaterSettings()) - , mSoundBuffers(new SoundBufferList::element_type()) - , mBufferCacheSize(0) + , mSoundBuffers(*vfs, *mOutput) , mListenerUnderwater(false) , mListenerPos(0,0,0) , mListenerDir(1,0,0) @@ -69,11 +68,6 @@ namespace MWSound , mLastCell(nullptr) , mCurrentRegionSound(nullptr) { - mBufferCacheMin = std::max(Settings::Manager::getInt("buffer cache min", "Sound"), 1); - mBufferCacheMax = std::max(Settings::Manager::getInt("buffer cache max", "Sound"), 1); - mBufferCacheMax *= 1024*1024; - mBufferCacheMin = std::min(mBufferCacheMin*1024*1024, mBufferCacheMax); - if(!useSound) { Log(Debug::Info) << "Sound disabled."; @@ -116,13 +110,7 @@ namespace MWSound SoundManager::~SoundManager() { clear(); - for(Sound_Buffer &sfx : *mSoundBuffers) - { - if(sfx.mHandle) - mOutput->unloadSound(sfx.mHandle); - sfx.mHandle = nullptr; - } - mUnusedBuffers.clear(); + mSoundBuffers.clear(); mOutput.reset(); } @@ -132,112 +120,18 @@ namespace MWSound return DecoderPtr(new DEFAULT_DECODER (mVFS)); } - Sound_Buffer *SoundManager::insertSound(const std::string &soundId, const ESM::Sound *sound) - { - MWBase::World* world = MWBase::Environment::get().getWorld(); - static const float fAudioDefaultMinDistance = world->getStore().get().find("fAudioDefaultMinDistance")->mValue.getFloat(); - static const float fAudioDefaultMaxDistance = world->getStore().get().find("fAudioDefaultMaxDistance")->mValue.getFloat(); - static const float fAudioMinDistanceMult = world->getStore().get().find("fAudioMinDistanceMult")->mValue.getFloat(); - static const float fAudioMaxDistanceMult = world->getStore().get().find("fAudioMaxDistanceMult")->mValue.getFloat(); - float volume, min, max; - - volume = static_cast(pow(10.0, (sound->mData.mVolume / 255.0*3348.0 - 3348.0) / 2000.0)); - min = sound->mData.mMinRange; - max = sound->mData.mMaxRange; - if (min == 0 && max == 0) - { - min = fAudioDefaultMinDistance; - max = fAudioDefaultMaxDistance; - } - - min *= fAudioMinDistanceMult; - max *= fAudioMaxDistanceMult; - min = std::max(min, 1.0f); - max = std::max(min, max); - - Sound_Buffer *sfx = &*mSoundBuffers->insert(mSoundBuffers->end(), - Sound_Buffer("Sound/"+sound->mSound, volume, min, max) - ); - mVFS->normalizeFilename(sfx->mResourceName); - - mBufferNameMap.insert(std::make_pair(soundId, sfx)); - - return sfx; - } - // Lookup a soundId for its sound data (resource name, local volume, // minRange, and maxRange) Sound_Buffer *SoundManager::lookupSound(const std::string &soundId) const { - NameBufferMap::const_iterator snd = mBufferNameMap.find(soundId); - if(snd != mBufferNameMap.end()) - { - Sound_Buffer *sfx = snd->second; - if(sfx->mHandle) return sfx; - } - return nullptr; + return mSoundBuffers.lookup(soundId); } // Lookup a soundId for its sound data (resource name, local volume, // minRange, and maxRange), and ensure it's ready for use. Sound_Buffer *SoundManager::loadSound(const std::string &soundId) { -#ifdef __GNUC__ -#define LIKELY(x) __builtin_expect((bool)(x), true) -#define UNLIKELY(x) __builtin_expect((bool)(x), false) -#else -#define LIKELY(x) (bool)(x) -#define UNLIKELY(x) (bool)(x) -#endif - if(UNLIKELY(mBufferNameMap.empty())) - { - MWBase::World *world = MWBase::Environment::get().getWorld(); - for(const ESM::Sound &sound : world->getStore().get()) - insertSound(Misc::StringUtils::lowerCase(sound.mId), &sound); - } - - Sound_Buffer *sfx; - NameBufferMap::const_iterator snd = mBufferNameMap.find(soundId); - if(LIKELY(snd != mBufferNameMap.end())) - sfx = snd->second; - else - { - MWBase::World *world = MWBase::Environment::get().getWorld(); - const ESM::Sound *sound = world->getStore().get().search(soundId); - if(!sound) return nullptr; - sfx = insertSound(soundId, sound); - } -#undef LIKELY -#undef UNLIKELY - - if(!sfx->mHandle) - { - size_t size; - std::tie(sfx->mHandle, size) = mOutput->loadSound(sfx->mResourceName); - if(!sfx->mHandle) return nullptr; - - mBufferCacheSize += size; - if(mBufferCacheSize > mBufferCacheMax) - { - do { - if(mUnusedBuffers.empty()) - { - Log(Debug::Warning) << "No unused sound buffers to free, using " << mBufferCacheSize << " bytes!"; - break; - } - Sound_Buffer *unused = mUnusedBuffers.back(); - - size = mOutput->unloadSound(unused->mHandle); - mBufferCacheSize -= size; - unused->mHandle = nullptr; - - mUnusedBuffers.pop_back(); - } while(mBufferCacheSize > mBufferCacheMin); - } - mUnusedBuffers.push_front(sfx); - } - - return sfx; + return mSoundBuffers.load(soundId); } DecoderPtr SoundManager::loadVoice(const std::string &voicefile) @@ -624,23 +518,18 @@ namespace MWSound SoundPtr sound = getSoundRef(); sound->init([&] { SoundParams params; - params.mVolume = volume * sfx->mVolume; + params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); params.mPitch = pitch; params.mFlags = mode | type | Play_2D; return params; } ()); - if(!mOutput->playSound(sound.get(), sfx->mHandle, offset)) + if(!mOutput->playSound(sound.get(), sfx->getHandle(), offset)) return nullptr; - if(sfx->mUses++ == 0) - { - SoundList::iterator iter = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), sfx); - if(iter != mUnusedBuffers.end()) - mUnusedBuffers.erase(iter); - } Sound* result = sound.get(); mActiveSounds[MWWorld::ConstPtr()].emplace_back(std::move(sound), sfx); + mSoundBuffers.use(*sfx); return result; } @@ -668,40 +557,35 @@ namespace MWSound { sound->init([&] { SoundParams params; - params.mVolume = volume * sfx->mVolume; + params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); params.mPitch = pitch; params.mFlags = mode | type | Play_2D; return params; } ()); - played = mOutput->playSound(sound.get(), sfx->mHandle, offset); + played = mOutput->playSound(sound.get(), sfx->getHandle(), offset); } else { sound->init([&] { SoundParams params; params.mPos = objpos; - params.mVolume = volume * sfx->mVolume; + params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); params.mPitch = pitch; - params.mMinDistance = sfx->mMinDist; - params.mMaxDistance = sfx->mMaxDist; + params.mMinDistance = sfx->getMinDist(); + params.mMaxDistance = sfx->getMaxDist(); params.mFlags = mode | type | Play_3D; return params; } ()); - played = mOutput->playSound3D(sound.get(), sfx->mHandle, offset); + played = mOutput->playSound3D(sound.get(), sfx->getHandle(), offset); } if(!played) return nullptr; - if(sfx->mUses++ == 0) - { - SoundList::iterator iter = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), sfx); - if(iter != mUnusedBuffers.end()) - mUnusedBuffers.erase(iter); - } Sound* result = sound.get(); mActiveSounds[ptr].emplace_back(std::move(sound), sfx); + mSoundBuffers.use(*sfx); return result; } @@ -720,25 +604,20 @@ namespace MWSound sound->init([&] { SoundParams params; params.mPos = initialPos; - params.mVolume = volume * sfx->mVolume; + params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); params.mPitch = pitch; - params.mMinDistance = sfx->mMinDist; - params.mMaxDistance = sfx->mMaxDist; + params.mMinDistance = sfx->getMinDist(); + params.mMaxDistance = sfx->getMaxDist(); params.mFlags = mode | type | Play_3D; return params; } ()); - if(!mOutput->playSound3D(sound.get(), sfx->mHandle, offset)) + if(!mOutput->playSound3D(sound.get(), sfx->getHandle(), offset)) return nullptr; - if(sfx->mUses++ == 0) - { - SoundList::iterator iter = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), sfx); - if(iter != mUnusedBuffers.end()) - mUnusedBuffers.erase(iter); - } Sound* result = sound.get(); mActiveSounds[MWWorld::ConstPtr()].emplace_back(std::move(sound), sfx); + mSoundBuffers.use(*sfx); return result; } @@ -921,7 +800,7 @@ namespace MWSound case WaterSoundAction::DoNothing: break; case WaterSoundAction::SetVolume: - mNearWaterSound->setVolume(update.mVolume * sfx->mVolume); + mNearWaterSound->setVolume(update.mVolume * sfx->getVolume()); break; case WaterSoundAction::FinishSound: mOutput->finishSound(mNearWaterSound); @@ -1028,7 +907,6 @@ namespace MWSound while(sndidx != snditer->second.end()) { Sound *sound = sndidx->first.get(); - Sound_Buffer *sfx = sndidx->second; if(!ptr.isEmpty() && sound->getIs3D()) { @@ -1050,8 +928,7 @@ namespace MWSound mUnderwaterSound = nullptr; if (sound == mNearWaterSound) mNearWaterSound = nullptr; - if(sfx->mUses-- == 1) - mUnusedBuffers.push_front(sfx); + mSoundBuffers.release(*sndidx->second); sndidx = snditer->second.erase(sndidx); } else @@ -1313,9 +1190,7 @@ namespace MWSound for(SoundBufferRefPair &sndbuf : snd.second) { mOutput->finishSound(sndbuf.first.get()); - Sound_Buffer *sfx = sndbuf.second; - if(sfx->mUses-- == 1) - mUnusedBuffers.push_front(sfx); + mSoundBuffers.release(*sndbuf.second); } } mActiveSounds.clear(); diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index f69171a09..21004dc94 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -18,6 +17,7 @@ #include "watersoundupdater.hpp" #include "type.hpp" #include "volumesettings.hpp" +#include "sound_buffer.hpp" namespace VFS { @@ -36,17 +36,6 @@ namespace MWSound struct Sound_Decoder; class Sound; class Stream; - class Sound_Buffer; - - enum Environment { - Env_Normal, - Env_Underwater - }; - // Extra play flags, not intended for caller use - enum PlayModeEx { - Play_2D = 0, - Play_3D = 1<<31 - }; using SoundPtr = Misc::ObjectPtr; using StreamPtr = Misc::ObjectPtr; @@ -66,21 +55,7 @@ namespace MWSound WaterSoundUpdater mWaterSoundUpdater; - typedef std::unique_ptr > SoundBufferList; - // List of sound buffers, grown as needed. New enties are added to the - // back, allowing existing Sound_Buffer references/pointers to remain - // valid. - SoundBufferList mSoundBuffers; - size_t mBufferCacheMin; - size_t mBufferCacheMax; - size_t mBufferCacheSize; - - typedef std::unordered_map NameBufferMap; - NameBufferMap mBufferNameMap; - - // NOTE: unused buffers are stored in front-newest order. - typedef std::deque SoundList; - SoundList mUnusedBuffers; + SoundBufferPool mSoundBuffers; Misc::ObjectPool mSounds; From 799bd3379c36d14635bf200f28f8fe7938bb7e1a Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 8 Jan 2021 19:33:51 +0400 Subject: [PATCH 021/171] Move screenshots handling to the separate class --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwinput/actionmanager.cpp | 10 +- apps/openmw/mwrender/renderingmanager.cpp | 290 +----------------- apps/openmw/mwrender/renderingmanager.hpp | 9 +- apps/openmw/mwrender/screenshotmanager.cpp | 324 +++++++++++++++++++++ apps/openmw/mwrender/screenshotmanager.hpp | 40 +++ apps/openmw/mwworld/worldimp.cpp | 6 +- apps/openmw/mwworld/worldimp.hpp | 2 +- 9 files changed, 386 insertions(+), 299 deletions(-) create mode 100644 apps/openmw/mwrender/screenshotmanager.cpp create mode 100644 apps/openmw/mwrender/screenshotmanager.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index a4c3b9136..f96ddb27f 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -19,7 +19,7 @@ source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender actors objects renderingmanager animation rotatecontroller sky npcanimation vismask - creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation + creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager bulletdebugdraw globalmap characterpreview camera viewovershoulder localmap water terrainstorage ripplesimulation renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging ) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 958fcfb0e..a3b035a8d 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -497,7 +497,7 @@ namespace MWBase /// \todo this does not belong here virtual void screenshot (osg::Image* image, int w, int h) = 0; - virtual bool screenshot360 (osg::Image* image, std::string settingStr) = 0; + virtual bool screenshot360 (osg::Image* image) = 0; /// Find default position inside exterior cell specified by name /// \return false if exterior with given name not exists, true otherwise diff --git a/apps/openmw/mwinput/actionmanager.cpp b/apps/openmw/mwinput/actionmanager.cpp index b29aa58a2..e0fcc5ccf 100644 --- a/apps/openmw/mwinput/actionmanager.cpp +++ b/apps/openmw/mwinput/actionmanager.cpp @@ -333,12 +333,8 @@ namespace MWInput void ActionManager::screenshot() { - bool regularScreenshot = true; - - std::string settingStr; - - settingStr = Settings::Manager::getString("screenshot type","Video"); - regularScreenshot = settingStr.size() == 0 || settingStr.compare("regular") == 0; + const std::string& settingStr = Settings::Manager::getString("screenshot type", "Video"); + bool regularScreenshot = settingStr.size() == 0 || settingStr.compare("regular") == 0; if (regularScreenshot) { @@ -349,7 +345,7 @@ namespace MWInput { osg::ref_ptr screenshot (new osg::Image); - if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get(), settingStr)) + if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get())) { (*mScreenCaptureOperation) (*(screenshot.get()), 0); // FIXME: mScreenCaptureHandler->getCaptureOperation() causes crash for some reason diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 217f0b73c..6ce431d2e 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -2,8 +2,6 @@ #include #include -#include -#include #include #include @@ -13,25 +11,20 @@ #include #include #include -#include -#include #include -#include - #include #include #include -#include - #include #include #include #include + #include #include @@ -74,7 +67,7 @@ #include "recastmesh.hpp" #include "fogmanager.hpp" #include "objectpaging.hpp" - +#include "screenshotmanager.hpp" namespace MWRender { @@ -311,6 +304,8 @@ namespace MWRender if (Settings::Manager::getBool("view over shoulder", "Camera")) mViewOverShoulderController.reset(new ViewOverShoulderController(mCamera.get())); + mScreenshotManager.reset(new ScreenshotManager(viewer, mRootNode, sceneRoot, mResourceSystem, mWater.get())); + mViewer->setLightingMode(osgViewer::View::NO_LIGHT); osg::ref_ptr source = new osg::LightSource; @@ -695,298 +690,31 @@ namespace MWRender mSky->setWaterHeight(height); } - class NotifyDrawCompletedCallback : public osg::Camera::DrawCallback + void RenderingManager::screenshot(osg::Image* image, int w, int h) { - public: - NotifyDrawCompletedCallback(unsigned int frame) - : mDone(false), mFrame(frame) - { - } - - void operator () (osg::RenderInfo& renderInfo) const override - { - std::lock_guard lock(mMutex); - if (renderInfo.getState()->getFrameStamp()->getFrameNumber() >= mFrame) - { - mDone = true; - mCondition.notify_one(); - } - } - - void waitTillDone() - { - std::unique_lock lock(mMutex); - if (mDone) - return; - mCondition.wait(lock); - } - - mutable std::condition_variable mCondition; - mutable std::mutex mMutex; - mutable bool mDone; - unsigned int mFrame; - }; + mScreenshotManager->screenshot(image, w, h); + } - bool RenderingManager::screenshot360(osg::Image* image, std::string settingStr) + bool RenderingManager::screenshot360(osg::Image* image) { - int screenshotW = mViewer->getCamera()->getViewport()->width(); - int screenshotH = mViewer->getCamera()->getViewport()->height(); - int screenshotMapping = 0; - - std::vector settingArgs; - Misc::StringUtils::split(settingStr, settingArgs); - - if (settingArgs.size() > 0) - { - std::string typeStrings[4] = {"spherical","cylindrical","planet","cubemap"}; - bool found = false; - - for (int i = 0; i < 4; ++i) - if (settingArgs[0].compare(typeStrings[i]) == 0) - { - screenshotMapping = i; - found = true; - break; - } - - if (!found) - { - Log(Debug::Warning) << "Wrong screenshot type: " << settingArgs[0] << "."; - return false; - } - } - - // planet mapping needs higher resolution - int cubeSize = screenshotMapping == 2 ? screenshotW : screenshotW / 2; - - if (settingArgs.size() > 1) - screenshotW = std::min(10000,std::atoi(settingArgs[1].c_str())); - - if (settingArgs.size() > 2) - screenshotH = std::min(10000,std::atoi(settingArgs[2].c_str())); - - if (settingArgs.size() > 3) - cubeSize = std::min(5000,std::atoi(settingArgs[3].c_str())); - if (mCamera->isVanityOrPreviewModeEnabled()) { Log(Debug::Warning) << "Spherical screenshots are not allowed in preview mode."; return false; } - bool rawCubemap = screenshotMapping == 3; - - if (rawCubemap) - screenshotW = cubeSize * 6; // the image will consist of 6 cube sides in a row - else if (screenshotMapping == 2) - screenshotH = screenshotW; // use square resolution for planet mapping - - std::vector> images; - - for (int i = 0; i < 6; ++i) - images.push_back(new osg::Image); - - osg::Vec3 directions[6] = { - rawCubemap ? osg::Vec3(1,0,0) : osg::Vec3(0,0,1), - osg::Vec3(0,0,-1), - osg::Vec3(-1,0,0), - rawCubemap ? osg::Vec3(0,0,1) : osg::Vec3(1,0,0), - osg::Vec3(0,1,0), - osg::Vec3(0,-1,0)}; - - double rotations[] = { - -osg::PI / 2.0, - osg::PI / 2.0, - osg::PI, - 0, - osg::PI / 2.0, - osg::PI / 2.0}; - - double fovBackup = mFieldOfView; - mFieldOfView = 90.0; // each cubemap side sees 90 degrees - int maskBackup = mPlayerAnimation->getObjectRoot()->getNodeMask(); if (mCamera->isFirstPerson()) mPlayerAnimation->getObjectRoot()->setNodeMask(0); - for (int i = 0; i < 6; ++i) // for each cubemap side - { - osg::Matrixd transform = osg::Matrixd::rotate(osg::Vec3(0,0,-1),directions[i]); - - if (!rawCubemap) - transform *= osg::Matrixd::rotate(rotations[i],osg::Vec3(0,0,-1)); - - osg::Image *sideImage = images[i].get(); - screenshot(sideImage,cubeSize,cubeSize,transform); - - if (!rawCubemap) - sideImage->flipHorizontal(); - } + mScreenshotManager->screenshot360(image); mPlayerAnimation->getObjectRoot()->setNodeMask(maskBackup); - mFieldOfView = fovBackup; - - if (rawCubemap) // for raw cubemap don't run on GPU, just merge the images - { - image->allocateImage(cubeSize * 6,cubeSize,images[0]->r(),images[0]->getPixelFormat(),images[0]->getDataType()); - - for (int i = 0; i < 6; ++i) - osg::copyImage(images[i].get(),0,0,0,images[i]->s(),images[i]->t(),images[i]->r(),image,i * cubeSize,0,0); - - return true; - } - - // run on GPU now: - - osg::ref_ptr cubeTexture (new osg::TextureCubeMap); - cubeTexture->setResizeNonPowerOfTwoHint(false); - - cubeTexture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::NEAREST); - cubeTexture->setFilter(osg::Texture::MAG_FILTER,osg::Texture::NEAREST); - - cubeTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - cubeTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - - for (int i = 0; i < 6; ++i) - cubeTexture->setImage(i,images[i].get()); - - osg::ref_ptr screenshotCamera (new osg::Camera); - osg::ref_ptr quad (new osg::ShapeDrawable(new osg::Box(osg::Vec3(0,0,0),2.0))); - - std::map defineMap; - - Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); - osg::ref_ptr fragmentShader (shaderMgr.getShader("s360_fragment.glsl",defineMap,osg::Shader::FRAGMENT)); - osg::ref_ptr vertexShader (shaderMgr.getShader("s360_vertex.glsl", defineMap, osg::Shader::VERTEX)); - osg::ref_ptr stateset = new osg::StateSet; - - osg::ref_ptr program (new osg::Program); - program->addShader(fragmentShader); - program->addShader(vertexShader); - stateset->setAttributeAndModes(program, osg::StateAttribute::ON); - - stateset->addUniform(new osg::Uniform("cubeMap",0)); - stateset->addUniform(new osg::Uniform("mapping",screenshotMapping)); - stateset->setTextureAttributeAndModes(0,cubeTexture,osg::StateAttribute::ON); - - quad->setStateSet(stateset); - quad->setUpdateCallback(nullptr); - - screenshotCamera->addChild(quad); - - renderCameraToImage(screenshotCamera,image,screenshotW,screenshotH); return true; } - void RenderingManager::renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h) - { - camera->setNodeMask(Mask_RenderToTexture); - camera->attach(osg::Camera::COLOR_BUFFER, image); - camera->setRenderOrder(osg::Camera::PRE_RENDER); - camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); - camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT,osg::Camera::PIXEL_BUFFER_RTT); - - camera->setViewport(0, 0, w, h); - - osg::ref_ptr texture (new osg::Texture2D); - texture->setInternalFormat(GL_RGB); - texture->setTextureSize(w,h); - texture->setResizeNonPowerOfTwoHint(false); - texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - camera->attach(osg::Camera::COLOR_BUFFER,texture); - - image->setDataType(GL_UNSIGNED_BYTE); - image->setPixelFormat(texture->getInternalFormat()); - - mRootNode->addChild(camera); - - // The draw needs to complete before we can copy back our image. - osg::ref_ptr callback (new NotifyDrawCompletedCallback(0)); - camera->setFinalDrawCallback(callback); - - MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOn(false); - - mViewer->eventTraversal(); - mViewer->updateTraversal(); - mViewer->renderingTraversals(); - callback->waitTillDone(); - - MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOff(); - - // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed - mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); - - camera->removeChildren(0, camera->getNumChildren()); - mRootNode->removeChild(camera); - } - - class ReadImageFromFramebufferCallback : public osg::Drawable::DrawCallback - { - public: - ReadImageFromFramebufferCallback(osg::Image* image, int width, int height) - : mWidth(width), mHeight(height), mImage(image) - { - } - void drawImplementation(osg::RenderInfo& renderInfo,const osg::Drawable* /*drawable*/) const override - { - int screenW = renderInfo.getCurrentCamera()->getViewport()->width(); - int screenH = renderInfo.getCurrentCamera()->getViewport()->height(); - double imageaspect = (double)mWidth/(double)mHeight; - int leftPadding = std::max(0, static_cast(screenW - screenH * imageaspect) / 2); - int topPadding = std::max(0, static_cast(screenH - screenW / imageaspect) / 2); - int width = screenW - leftPadding*2; - int height = screenH - topPadding*2; - mImage->readPixels(leftPadding, topPadding, width, height, GL_RGB, GL_UNSIGNED_BYTE); - mImage->scaleImage(mWidth, mHeight, 1); - } - private: - int mWidth; - int mHeight; - osg::ref_ptr mImage; - }; - - void RenderingManager::screenshotFramebuffer(osg::Image* image, int w, int h) - { - osg::Camera* camera = mViewer->getCamera(); - osg::ref_ptr tempDrw = new osg::Drawable; - tempDrw->setDrawCallback(new ReadImageFromFramebufferCallback(image, w, h)); - tempDrw->setCullingActive(false); - tempDrw->getOrCreateStateSet()->setRenderBinDetails(100, "RenderBin", osg::StateSet::USE_RENDERBIN_DETAILS); // so its after all scene bins but before POST_RENDER gui camera - camera->addChild(tempDrw); - osg::ref_ptr callback (new NotifyDrawCompletedCallback(mViewer->getFrameStamp()->getFrameNumber())); - camera->setFinalDrawCallback(callback); - mViewer->eventTraversal(); - mViewer->updateTraversal(); - mViewer->renderingTraversals(); - callback->waitTillDone(); - // now that we've "used up" the current frame, get a fresh frame number for the next frame() following after the screenshot is completed - mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); - camera->removeChild(tempDrw); - camera->setFinalDrawCallback(nullptr); - } - - void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Matrixd cameraTransform) - { - osg::ref_ptr rttCamera (new osg::Camera); - rttCamera->setProjectionMatrixAsPerspective(mFieldOfView, w/float(h), mNearClip, mViewDistance); - rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * cameraTransform); - - rttCamera->setUpdateCallback(new NoTraverseCallback); - rttCamera->addChild(mSceneRoot); - - rttCamera->addChild(mWater->getReflectionCamera()); - rttCamera->addChild(mWater->getRefractionCamera()); - - rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); - - rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - renderCameraToImage(rttCamera.get(),image,w,h); - } - osg::Vec4f RenderingManager::getScreenBounds(const osg::BoundingBox &worldbb) { if (!worldbb.valid()) return osg::Vec4f(); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index ef28cf544..39d1a0194 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -74,6 +74,7 @@ namespace MWRender class StateUpdater; class EffectManager; + class ScreenshotManager; class FogManager; class SkyManager; class NpcAnimation; @@ -148,9 +149,8 @@ namespace MWRender void setWaterHeight(float level); /// Take a screenshot of w*h onto the given image, not including the GUI. - void screenshot(osg::Image* image, int w, int h, osg::Matrixd cameraTransform=osg::Matrixd()); // make a new render at given size - void screenshotFramebuffer(osg::Image* image, int w, int h); // copy directly from framebuffer and scale to given size - bool screenshot360(osg::Image* image, std::string settingStr); + void screenshot(osg::Image* image, int w, int h); + bool screenshot360(osg::Image* image); struct RayResult { @@ -248,8 +248,6 @@ namespace MWRender void reportStats() const; - void renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h); - void updateNavMesh(); void updateRecastMesh(); @@ -281,6 +279,7 @@ namespace MWRender std::unique_ptr mObjectPaging; std::unique_ptr mSky; std::unique_ptr mFog; + std::unique_ptr mScreenshotManager; std::unique_ptr mEffectManager; std::unique_ptr mShadowManager; osg::ref_ptr mPlayerAnimation; diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp new file mode 100644 index 000000000..89b225da4 --- /dev/null +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -0,0 +1,324 @@ +#include "screenshotmanager.hpp" + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "../mwgui/loadingscreen.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" + +#include "util.hpp" +#include "vismask.hpp" +#include "water.hpp" + +namespace MWRender +{ + enum Screenshot360Type + { + Spherical, + Cylindrical, + Planet, + RawCubemap + }; + + class NotifyDrawCompletedCallback : public osg::Camera::DrawCallback + { + public: + NotifyDrawCompletedCallback(unsigned int frame) + : mDone(false), mFrame(frame) + { + } + + void operator () (osg::RenderInfo& renderInfo) const override + { + std::lock_guard lock(mMutex); + if (renderInfo.getState()->getFrameStamp()->getFrameNumber() >= mFrame) + { + mDone = true; + mCondition.notify_one(); + } + } + + void waitTillDone() + { + std::unique_lock lock(mMutex); + if (mDone) + return; + mCondition.wait(lock); + } + + mutable std::condition_variable mCondition; + mutable std::mutex mMutex; + mutable bool mDone; + unsigned int mFrame; + }; + + class ReadImageFromFramebufferCallback : public osg::Drawable::DrawCallback + { + public: + ReadImageFromFramebufferCallback(osg::Image* image, int width, int height) + : mWidth(width), mHeight(height), mImage(image) + { + } + void drawImplementation(osg::RenderInfo& renderInfo,const osg::Drawable* /*drawable*/) const override + { + int screenW = renderInfo.getCurrentCamera()->getViewport()->width(); + int screenH = renderInfo.getCurrentCamera()->getViewport()->height(); + double imageaspect = (double)mWidth/(double)mHeight; + int leftPadding = std::max(0, static_cast(screenW - screenH * imageaspect) / 2); + int topPadding = std::max(0, static_cast(screenH - screenW / imageaspect) / 2); + int width = screenW - leftPadding*2; + int height = screenH - topPadding*2; + mImage->readPixels(leftPadding, topPadding, width, height, GL_RGB, GL_UNSIGNED_BYTE); + mImage->scaleImage(mWidth, mHeight, 1); + } + private: + int mWidth; + int mHeight; + osg::ref_ptr mImage; + }; + + ScreenshotManager::ScreenshotManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, osg::ref_ptr sceneRoot, Resource::ResourceSystem* resourceSystem, Water* water) + : mViewer(viewer) + , mRootNode(rootNode) + , mSceneRoot(sceneRoot) + , mResourceSystem(resourceSystem) + , mWater(water) + { + } + + void ScreenshotManager::screenshot(osg::Image* image, int w, int h) + { + osg::Camera* camera = mViewer->getCamera(); + osg::ref_ptr tempDrw = new osg::Drawable; + tempDrw->setDrawCallback(new ReadImageFromFramebufferCallback(image, w, h)); + tempDrw->setCullingActive(false); + tempDrw->getOrCreateStateSet()->setRenderBinDetails(100, "RenderBin", osg::StateSet::USE_RENDERBIN_DETAILS); // so its after all scene bins but before POST_RENDER gui camera + camera->addChild(tempDrw); + osg::ref_ptr callback (new NotifyDrawCompletedCallback(mViewer->getFrameStamp()->getFrameNumber())); + camera->setFinalDrawCallback(callback); + mViewer->eventTraversal(); + mViewer->updateTraversal(); + mViewer->renderingTraversals(); + callback->waitTillDone(); + // now that we've "used up" the current frame, get a fresh frame number for the next frame() following after the screenshot is completed + mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); + camera->removeChild(tempDrw); + camera->setFinalDrawCallback(nullptr); + } + + bool ScreenshotManager::screenshot360(osg::Image* image) + { + int screenshotW = mViewer->getCamera()->getViewport()->width(); + int screenshotH = mViewer->getCamera()->getViewport()->height(); + Screenshot360Type screenshotMapping = Spherical; + + const std::string& settingStr = Settings::Manager::getString("screenshot type", "Video"); + std::vector settingArgs; + Misc::StringUtils::split(settingStr, settingArgs); + + if (settingArgs.size() > 0) + { + std::string typeStrings[4] = {"spherical", "cylindrical", "planet", "cubemap"}; + bool found = false; + + for (int i = 0; i < 4; ++i) + { + if (settingArgs[0].compare(typeStrings[i]) == 0) + { + screenshotMapping = static_cast(i); + found = true; + break; + } + } + + if (!found) + { + Log(Debug::Warning) << "Wrong screenshot type: " << settingArgs[0] << "."; + return false; + } + } + + // planet mapping needs higher resolution + int cubeSize = screenshotMapping == Planet ? screenshotW : screenshotW / 2; + + if (settingArgs.size() > 1) + screenshotW = std::min(10000, std::atoi(settingArgs[1].c_str())); + + if (settingArgs.size() > 2) + screenshotH = std::min(10000, std::atoi(settingArgs[2].c_str())); + + if (settingArgs.size() > 3) + cubeSize = std::min(5000, std::atoi(settingArgs[3].c_str())); + + bool rawCubemap = screenshotMapping == RawCubemap; + + if (rawCubemap) + screenshotW = cubeSize * 6; // the image will consist of 6 cube sides in a row + else if (screenshotMapping == Planet) + screenshotH = screenshotW; // use square resolution for planet mapping + + std::vector> images; + + for (int i = 0; i < 6; ++i) + images.push_back(new osg::Image); + + osg::Vec3 directions[6] = { + rawCubemap ? osg::Vec3(1,0,0) : osg::Vec3(0,0,1), + osg::Vec3(0,0,-1), + osg::Vec3(-1,0,0), + rawCubemap ? osg::Vec3(0,0,1) : osg::Vec3(1,0,0), + osg::Vec3(0,1,0), + osg::Vec3(0,-1,0)}; + + double rotations[] = { + -osg::PI / 2.0, + osg::PI / 2.0, + osg::PI, + 0, + osg::PI / 2.0, + osg::PI / 2.0 }; + + for (int i = 0; i < 6; ++i) // for each cubemap side + { + osg::Matrixd transform = osg::Matrixd::rotate(osg::Vec3(0,0,-1), directions[i]); + + if (!rawCubemap) + transform *= osg::Matrixd::rotate(rotations[i],osg::Vec3(0,0,-1)); + + osg::Image *sideImage = images[i].get(); + makeCubemapScreenshot(sideImage, cubeSize, cubeSize, transform); + + if (!rawCubemap) + sideImage->flipHorizontal(); + } + + if (rawCubemap) // for raw cubemap don't run on GPU, just merge the images + { + image->allocateImage(cubeSize * 6,cubeSize,images[0]->r(),images[0]->getPixelFormat(),images[0]->getDataType()); + + for (int i = 0; i < 6; ++i) + osg::copyImage(images[i].get(),0,0,0,images[i]->s(),images[i]->t(),images[i]->r(),image,i * cubeSize,0,0); + + return true; + } + + // run on GPU now: + osg::ref_ptr cubeTexture (new osg::TextureCubeMap); + cubeTexture->setResizeNonPowerOfTwoHint(false); + + cubeTexture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::NEAREST); + cubeTexture->setFilter(osg::Texture::MAG_FILTER,osg::Texture::NEAREST); + + cubeTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + cubeTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + + for (int i = 0; i < 6; ++i) + cubeTexture->setImage(i, images[i].get()); + + osg::ref_ptr screenshotCamera(new osg::Camera); + osg::ref_ptr quad(new osg::ShapeDrawable(new osg::Box(osg::Vec3(0,0,0), 2.0))); + + std::map defineMap; + + Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); + osg::ref_ptr fragmentShader(shaderMgr.getShader("s360_fragment.glsl", defineMap,osg::Shader::FRAGMENT)); + osg::ref_ptr vertexShader(shaderMgr.getShader("s360_vertex.glsl", defineMap, osg::Shader::VERTEX)); + osg::ref_ptr stateset = new osg::StateSet; + + osg::ref_ptr program(new osg::Program); + program->addShader(fragmentShader); + program->addShader(vertexShader); + stateset->setAttributeAndModes(program, osg::StateAttribute::ON); + + stateset->addUniform(new osg::Uniform("cubeMap", 0)); + stateset->addUniform(new osg::Uniform("mapping", screenshotMapping)); + stateset->setTextureAttributeAndModes(0, cubeTexture, osg::StateAttribute::ON); + + quad->setStateSet(stateset); + quad->setUpdateCallback(nullptr); + + screenshotCamera->addChild(quad); + + renderCameraToImage(screenshotCamera, image, screenshotW, screenshotH); + + return true; + } + + void ScreenshotManager::renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h) + { + camera->setNodeMask(Mask_RenderToTexture); + camera->attach(osg::Camera::COLOR_BUFFER, image); + camera->setRenderOrder(osg::Camera::PRE_RENDER); + camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT,osg::Camera::PIXEL_BUFFER_RTT); + + camera->setViewport(0, 0, w, h); + + osg::ref_ptr texture (new osg::Texture2D); + texture->setInternalFormat(GL_RGB); + texture->setTextureSize(w,h); + texture->setResizeNonPowerOfTwoHint(false); + texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + camera->attach(osg::Camera::COLOR_BUFFER,texture); + + image->setDataType(GL_UNSIGNED_BYTE); + image->setPixelFormat(texture->getInternalFormat()); + + mRootNode->addChild(camera); + + // The draw needs to complete before we can copy back our image. + osg::ref_ptr callback (new NotifyDrawCompletedCallback(0)); + camera->setFinalDrawCallback(callback); + + MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOn(false); + + mViewer->eventTraversal(); + mViewer->updateTraversal(); + mViewer->renderingTraversals(); + callback->waitTillDone(); + + MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOff(); + + // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed + mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); + + camera->removeChildren(0, camera->getNumChildren()); + mRootNode->removeChild(camera); + } + + void ScreenshotManager::makeCubemapScreenshot(osg::Image *image, int w, int h, osg::Matrixd cameraTransform) + { + osg::ref_ptr rttCamera (new osg::Camera); + float nearClip = Settings::Manager::getFloat("near clip", "Camera"); + float viewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); + // each cubemap side sees 90 degrees + rttCamera->setProjectionMatrixAsPerspective(90.0, w/float(h), nearClip, viewDistance); + rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * cameraTransform); + + rttCamera->setUpdateCallback(new NoTraverseCallback); + rttCamera->addChild(mSceneRoot); + + rttCamera->addChild(mWater->getReflectionCamera()); + rttCamera->addChild(mWater->getRefractionCamera()); + + rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); + + rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + renderCameraToImage(rttCamera.get(),image,w,h); + } +} diff --git a/apps/openmw/mwrender/screenshotmanager.hpp b/apps/openmw/mwrender/screenshotmanager.hpp new file mode 100644 index 000000000..2ac50bdf0 --- /dev/null +++ b/apps/openmw/mwrender/screenshotmanager.hpp @@ -0,0 +1,40 @@ +#ifndef MWRENDER_SCREENSHOTMANAGER_H +#define MWRENDER_SCREENSHOTMANAGER_H + +#include + +#include +#include + +#include + +namespace Resource +{ + class ResourceSystem; +} + +namespace MWRender +{ + class Water; + + class ScreenshotManager + { + public: + ScreenshotManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, osg::ref_ptr sceneRoot, Resource::ResourceSystem* resourceSystem, Water* water); + + void screenshot(osg::Image* image, int w, int h); + bool screenshot360(osg::Image* image); + + private: + osg::ref_ptr mViewer; + osg::ref_ptr mRootNode; + osg::ref_ptr mSceneRoot; + Resource::ResourceSystem* mResourceSystem; + Water* mWater; + + void renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h); + void makeCubemapScreenshot(osg::Image* image, int w, int h, osg::Matrixd cameraTransform=osg::Matrixd()); + }; +} + +#endif diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 8b95ee122..bf5b6db73 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2536,12 +2536,12 @@ namespace MWWorld void World::screenshot(osg::Image* image, int w, int h) { - mRendering->screenshotFramebuffer(image, w, h); + mRendering->screenshot(image, w, h); } - bool World::screenshot360(osg::Image* image, std::string settingStr) + bool World::screenshot360(osg::Image* image) { - return mRendering->screenshot360(image,settingStr); + return mRendering->screenshot360(image); } void World::activateDoor(const MWWorld::Ptr& door) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 9f0501413..79c8a4980 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -597,7 +597,7 @@ namespace MWWorld /// \todo this does not belong here void screenshot (osg::Image* image, int w, int h) override; - bool screenshot360 (osg::Image* image, std::string settingStr) override; + bool screenshot360 (osg::Image* image) override; /// Find center of exterior cell above land surface /// \return false if exterior with given name not exists, true otherwise From c5a36ad440a55ebf999a2e9d452e5437b62180ec Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 13:19:41 +0400 Subject: [PATCH 022/171] Do not cast enums to booleans --- apps/launcher/advancedpage.cpp | 2 +- apps/launcher/graphicspage.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 6f59ade7a..54fbbdfe0 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -108,7 +108,7 @@ bool Launcher::AdvancedPage::loadSettings() loadSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); connect(animSourcesCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotAnimSourcesToggled(bool))); loadSettingBool(animSourcesCheckBox, "use additional anim sources", "Game"); - if (animSourcesCheckBox->checkState()) + if (animSourcesCheckBox->checkState() != Qt::Unchecked) { loadSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game"); loadSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game"); diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index d1cf3aa6f..01205043e 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -206,7 +206,7 @@ void Launcher::GraphicsPage::saveSettings() if (cScreen != mEngineSettings.getInt("screen", "Video")) mEngineSettings.setInt("screen", "Video", cScreen); - if (framerateLimitCheckBox->checkState()) + if (framerateLimitCheckBox->checkState() != Qt::Unchecked) { float cFpsLimit = framerateLimitSpinBox->value(); if (cFpsLimit != mEngineSettings.getFloat("framerate limit", "Video")) @@ -217,7 +217,7 @@ void Launcher::GraphicsPage::saveSettings() mEngineSettings.setFloat("framerate limit", "Video", 0); } - int cShadowDist = shadowDistanceCheckBox->checkState() ? shadowDistanceSpinBox->value() : 0; + int cShadowDist = shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0; if (mEngineSettings.getInt("maximum shadow map distance", "Shadows") != cShadowDist) mEngineSettings.setInt("maximum shadow map distance", "Shadows", cShadowDist); float cFadeStart = fadeStartSpinBox->value(); From 801e2d6ad0f66ce9646c06ff6b73793c54349ce4 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 13:36:40 +0400 Subject: [PATCH 023/171] Avoid to use uninitialized variables --- apps/opencs/model/world/refidadapterimp.cpp | 2 +- apps/opencs/model/world/refidadapterimp.hpp | 2 +- components/files/constrainedfilestream.cpp | 2 +- components/nif/data.hpp | 10 +++++----- components/sceneutil/mwshadowtechnique.cpp | 2 ++ 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index d85fcc068..d944adc23 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -10,7 +10,7 @@ #include "nestedtablewrapper.hpp" CSMWorld::PotionColumns::PotionColumns (const InventoryColumns& columns) -: InventoryColumns (columns) {} +: InventoryColumns (columns), mEffects(nullptr) {} CSMWorld::PotionRefIdAdapter::PotionRefIdAdapter (const PotionColumns& columns, const RefIdColumn *autoCalc) diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 7695e9ace..a73f76af9 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -115,7 +115,7 @@ namespace CSMWorld { const RefIdColumn *mModel; - ModelColumns (const BaseColumns& base) : BaseColumns (base) {} + ModelColumns (const BaseColumns& base) : BaseColumns (base), mModel(nullptr) {} }; /// \brief Adapter for IDs with models (all but levelled lists) diff --git a/components/files/constrainedfilestream.cpp b/components/files/constrainedfilestream.cpp index baab1b081..b9968038d 100644 --- a/components/files/constrainedfilestream.cpp +++ b/components/files/constrainedfilestream.cpp @@ -21,7 +21,7 @@ namespace Files LowLevelFile mFile; - char mBuffer[sBufferSize]; + char mBuffer[sBufferSize]{0}; public: ConstrainedFileStreamBuf(const std::string &fname, size_t start, size_t length) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 66a391afc..efbe138c5 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -71,7 +71,7 @@ struct NiParticlesData : public NiGeometryData { int numParticles{0}; - int activeCount; + int activeCount{0}; std::vector particleRadii, sizes, rotationAngles; std::vector rotations; @@ -119,14 +119,14 @@ struct NiPixelData : public Record NIPXFMT_DXT5, NIPXFMT_DXT5_ALT }; - Format fmt; + Format fmt{NIPXFMT_RGB8}; - unsigned int colorMask[4]; - unsigned int bpp, pixelTiling{0}; + unsigned int colorMask[4]{0}; + unsigned int bpp{0}, pixelTiling{0}; bool sRGB{false}; NiPalettePtr palette; - unsigned int numberOfMipmaps; + unsigned int numberOfMipmaps{0}; struct Mipmap { diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index c49a14777..294780cfd 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -785,6 +785,7 @@ MWShadowTechnique::MWShadowTechnique(): _debugHud(nullptr) { _shadowRecievingPlaceholderStateSet = new osg::StateSet; + mSetDummyStateWhenDisabled = false; } MWShadowTechnique::MWShadowTechnique(const MWShadowTechnique& vdsm, const osg::CopyOp& copyop): @@ -792,6 +793,7 @@ MWShadowTechnique::MWShadowTechnique(const MWShadowTechnique& vdsm, const osg::C { _shadowRecievingPlaceholderStateSet = new osg::StateSet; _enableShadows = vdsm._enableShadows; + mSetDummyStateWhenDisabled = vdsm.mSetDummyStateWhenDisabled; } MWShadowTechnique::~MWShadowTechnique() From 7fc4c9f3f69d81be16d2294a7c390859ae63e278 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 13:52:01 +0400 Subject: [PATCH 024/171] Avoid dead code --- apps/opencs/model/filter/parser.cpp | 6 ------ apps/opencs/view/render/terraintexturemode.cpp | 8 ++------ apps/openmw/mwgui/journalwindow.cpp | 2 +- apps/openmw/mwmechanics/character.cpp | 4 ++-- components/compiler/scanner.hpp | 11 ++++------- extern/oics/ICSInputControlSystem_joystick.cpp | 2 +- 6 files changed, 10 insertions(+), 23 deletions(-) diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp index d2a4f2a35..d363b4849 100644 --- a/apps/opencs/model/filter/parser.cpp +++ b/apps/opencs/model/filter/parser.cpp @@ -325,12 +325,6 @@ std::shared_ptr CSMFilter::Parser::parseNAry (const Token& keyw break; } - if (nodes.empty()) - { - error(); - return std::shared_ptr(); - } - switch (keyword.mType) { case Token::Type_Keyword_And: return std::shared_ptr (new AndNode (nodes)); diff --git a/apps/opencs/view/render/terraintexturemode.cpp b/apps/opencs/view/render/terraintexturemode.cpp index c8d63f32e..09d6b135a 100644 --- a/apps/opencs/view/render/terraintexturemode.cpp +++ b/apps/opencs/view/render/terraintexturemode.cpp @@ -429,12 +429,8 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe { int distanceX(0); int distanceY(0); - if (i_cell < cellX) distanceX = xHitInCell + landTextureSize * abs(i_cell-cellX) - i; - if (j_cell < cellY) distanceY = yHitInCell + landTextureSize * abs(j_cell-cellY) - j; - if (i_cell > cellX) distanceX = -xHitInCell + landTextureSize* abs(i_cell-cellX) + i; - if (j_cell > cellY) distanceY = -yHitInCell + landTextureSize * abs(j_cell-cellY) + j; - if (i_cell == cellX) distanceX = abs(i-xHitInCell); - if (j_cell == cellY) distanceY = abs(j-yHitInCell); + distanceX = abs(i-xHitInCell); + distanceY = abs(j-yHitInCell); float distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2))); float rf = static_cast(mBrushSize) / 2; if (distance < rf) newTerrain[j*landTextureSize+i] = brushInt; diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index 1474becf0..96c42549a 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -561,7 +561,7 @@ namespace if (mAllQuests) { SetNamesInactive setInactive(list); - mModel->visitQuestNames(!mAllQuests, setInactive); + mModel->visitQuestNames(false, setInactive); } MWBase::Environment::get().getWindowManager()->playSound("book page"); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index b9bfe6bc0..b9ef50bf7 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -446,9 +446,9 @@ std::string CharacterController::fallbackShortWeaponGroup(const std::string& bas const ESM::WeaponType* weapInfo = getWeaponType(mWeaponType); // For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones - if (isRealWeapon && weapInfo->mFlags & ESM::WeaponType::TwoHanded && weapInfo->mWeaponClass == ESM::WeaponType::Melee) + if (weapInfo->mFlags & ESM::WeaponType::TwoHanded && weapInfo->mWeaponClass == ESM::WeaponType::Melee) groupName += twoHandFallback; - else if (isRealWeapon) + else groupName += oneHandFallback; // Special case for crossbows - we shouls apply 1h animations a fallback only for lower body diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index 2139f04b2..9c7bd656e 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -140,15 +140,12 @@ namespace Compiler for (int i = 0; i <= length; i++) { - if (length >= i) - { - in.get (ch); + in.get (ch); - if (!in.good()) - return false; + if (!in.good()) + return false; - mData[i] = ch; - } + mData[i] = ch; } mLength = length; diff --git a/extern/oics/ICSInputControlSystem_joystick.cpp b/extern/oics/ICSInputControlSystem_joystick.cpp index 38199436b..697d0ed3d 100644 --- a/extern/oics/ICSInputControlSystem_joystick.cpp +++ b/extern/oics/ICSInputControlSystem_joystick.cpp @@ -286,7 +286,7 @@ namespace ICS //ControlAxisBinderItem joystickBinderItem = mControlsJoystickAxisBinderMap[ evt.which ][ axis ]; // joystic axis start at 0 index //Control* ctrl = joystickBinderItem.control; //if(ctrl && ctrl->isAxisBindable()) - if(mDetectingBindingControl && mDetectingBindingControl->isAxisBindable()) + if(mDetectingBindingControl->isAxisBindable()) { if( abs( evt.value ) > ICS_JOYSTICK_AXIS_BINDING_MARGIN) { From 8283ec6cadef7bcafdc4a830f2d60d00b26b6c9b Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 14:03:48 +0400 Subject: [PATCH 025/171] Do not use & for boolean arguments --- apps/opencs/view/doc/view.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index be3fe5142..6fe01dc27 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -438,8 +438,8 @@ void CSVDoc::View::updateActions() for (std::vector::iterator iter (mEditingActions.begin()); iter!=mEditingActions.end(); ++iter) (*iter)->setEnabled (editing); - mUndo->setEnabled (editing & mDocument->getUndoStack().canUndo()); - mRedo->setEnabled (editing & mDocument->getUndoStack().canRedo()); + mUndo->setEnabled (editing && mDocument->getUndoStack().canUndo()); + mRedo->setEnabled (editing && mDocument->getUndoStack().canRedo()); mSave->setEnabled (!(mDocument->getState() & CSMDoc::State_Saving) && !running); mVerify->setEnabled (!(mDocument->getState() & CSMDoc::State_Verifying)); From 56666c60d44fa7809f8e9ab811827c4c908ce6d9 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 14:17:59 +0400 Subject: [PATCH 026/171] Remove dead code --- apps/openmw/mwmechanics/weapontype.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/openmw/mwmechanics/weapontype.cpp b/apps/openmw/mwmechanics/weapontype.cpp index 07345557f..2f8e45f7f 100644 --- a/apps/openmw/mwmechanics/weapontype.cpp +++ b/apps/openmw/mwmechanics/weapontype.cpp @@ -4,8 +4,6 @@ namespace MWMechanics { - static const ESM::WeaponType *sWeaponTypeListEnd = &sWeaponTypeList[sizeof(sWeaponTypeList)/sizeof(sWeaponTypeList[0])]; - MWWorld::ContainerStoreIterator getActiveWeapon(MWWorld::Ptr actor, int *weaptype) { MWWorld::InventoryStore &inv = actor.getClass().getInventoryStore(actor); From c1512b8b6cc47a8615c8400d91ed913e0ae35a82 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 14:18:38 +0400 Subject: [PATCH 027/171] Convert loop to condition --- apps/openmw/mwworld/localscripts.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index 42914d4ac..1661d6b9f 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -76,7 +76,7 @@ void MWWorld::LocalScripts::startIteration() bool MWWorld::LocalScripts::getNext(std::pair& script) { - while (mIter!=mScripts.end()) + if (mIter!=mScripts.end()) { std::list >::iterator iter = mIter++; script = *iter; From 33648313a68efad40eebf4d218261b5d00845ea1 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 14:21:57 +0400 Subject: [PATCH 028/171] Initialize variables --- apps/openmw/mwphysics/mtphysics.cpp | 3 +++ components/nif/node.hpp | 4 ++-- components/nif/property.hpp | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 0d1e5962a..3e4ed9b47 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -152,6 +152,9 @@ namespace MWPhysics , mNextLOS(0) , mFrameNumber(0) , mTimer(osg::Timer::instance()) + , mTimeBegin(0) + , mTimeEnd(0) + , mFrameStart(0) { mNumThreads = Config::computeNumThreads(mThreadSafeBullet); diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 1d082b8f9..406a4d454 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -177,7 +177,7 @@ struct Node : public Named // NiNodes (or types derived from NiNodes) can be parents. NiNode *parent; - bool isBone; + bool isBone{false}; void setBone() { @@ -378,7 +378,7 @@ struct NiCamera : Node struct NiSwitchNode : public NiNode { unsigned int switchFlags{0}; - unsigned int initialIndex; + unsigned int initialIndex{0}; void read(NIFStream *nif) override { diff --git a/components/nif/property.hpp b/components/nif/property.hpp index eccb442f7..008e84515 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -61,7 +61,7 @@ struct NiTexturingProperty : public Property 3 - hilight // These two are for PS2 only? 4 - hilight2 */ - unsigned int apply; + unsigned int apply{0}; /* * The textures in this list are as follows: @@ -193,7 +193,7 @@ struct S_MaterialProperty // The vector components are R,G,B osg::Vec3f ambient{1.f,1.f,1.f}, diffuse{1.f,1.f,1.f}; osg::Vec3f specular, emissive; - float glossiness, alpha; + float glossiness{0.f}, alpha{0.f}; void read(NIFStream *nif); }; From c9b885ffd4e3f01943d08488722abd7fbb539e61 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 14:24:04 +0400 Subject: [PATCH 029/171] Avoid possible null dereferencing --- apps/opencs/view/world/dragdroputils.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/world/dragdroputils.cpp b/apps/opencs/view/world/dragdroputils.cpp index 789d4f33d..808125a60 100644 --- a/apps/opencs/view/world/dragdroputils.cpp +++ b/apps/opencs/view/world/dragdroputils.cpp @@ -20,7 +20,8 @@ CSMWorld::UniversalId CSVWorld::DragDropUtils::getAcceptedData(const QDropEvent { if (canAcceptData(event, type)) { - return getTableMimeData(event)->returnMatching(type); + if (const CSMWorld::TableMimeData *data = getTableMimeData(event)) + return data->returnMatching(type); } return CSMWorld::UniversalId::Type_None; } From 8e5f26c109da6e1b2b5d7126e3e66b6a61ce073d Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 14:41:10 +0400 Subject: [PATCH 030/171] Code cleanup --- apps/opencs/view/world/dragrecordtable.cpp | 12 ++++-------- apps/openmw/mwgui/hud.cpp | 2 +- apps/openmw/mwrender/globalmap.cpp | 2 +- components/terrain/terraindrawable.cpp | 2 +- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/apps/opencs/view/world/dragrecordtable.cpp b/apps/opencs/view/world/dragrecordtable.cpp index d795bd5de..f84bf639d 100644 --- a/apps/opencs/view/world/dragrecordtable.cpp +++ b/apps/opencs/view/world/dragrecordtable.cpp @@ -19,14 +19,10 @@ void CSVWorld::DragRecordTable::startDragFromTable (const CSVWorld::DragRecordTa } CSMWorld::TableMimeData* mime = new CSMWorld::TableMimeData (records, mDocument); - - if (mime) - { - QDrag* drag = new QDrag (this); - drag->setMimeData (mime); - drag->setPixmap (QString::fromUtf8 (mime->getIcon().c_str())); - drag->exec (Qt::CopyAction); - } + QDrag* drag = new QDrag (this); + drag->setMimeData (mime); + drag->setPixmap (QString::fromUtf8 (mime->getIcon().c_str())); + drag->exec (Qt::CopyAction); } CSVWorld::DragRecordTable::DragRecordTable (CSMDoc::Document& document, QWidget* parent) : diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index a4ab20fd6..45defe9a5 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -587,7 +587,7 @@ namespace MWGui // effect box can have variable width -> variable left coordinate int effectsDx = 0; if (!mMinimapBox->getVisible ()) - effectsDx = (viewSize.width - mMinimapBoxBaseRight) - (viewSize.width - mEffectBoxBaseRight); + effectsDx = mEffectBoxBaseRight - mMinimapBoxBaseRight; mMapVisible = mMinimapBox->getVisible (); if (!mMapVisible) diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index ba300accb..366da6439 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -583,7 +583,7 @@ namespace MWRender } mOverlayImage->copySubImage(imageDest.mX, imageDest.mY, 0, imageDest.mImage); - it = mPendingImageDest.erase(it); + mPendingImageDest.erase(it); return true; } } diff --git a/components/terrain/terraindrawable.cpp b/components/terrain/terraindrawable.cpp index 0d82be4ff..746534abb 100644 --- a/components/terrain/terraindrawable.cpp +++ b/components/terrain/terraindrawable.cpp @@ -78,7 +78,7 @@ void TerrainDrawable::cull(osgUtil::CullVisitor *cv) osg::RefMatrix& matrix = *cv->getModelViewMatrix(); - if (cv->getComputeNearFarMode() && bb.valid()) + if (cv->getComputeNearFarMode() != osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR && bb.valid()) { if (!cv->updateCalculatedNearFar(matrix, *this, false)) return; From a80ee7a76a6ad94bc17fbda46b53affeb96968a6 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 14:43:00 +0400 Subject: [PATCH 031/171] Avoid possible memory leak in the mInterMessageBoxe field --- apps/openmw/mwgui/messagebox.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index d64ec9c37..5bd8ceb5a 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -28,10 +28,7 @@ namespace MWGui MessageBoxManager::~MessageBoxManager () { - for (MessageBox* messageBox : mMessageBoxes) - { - delete messageBox; - } + MessageBoxManager::clear(); } int MessageBoxManager::getMessagesCount() From 24f8a2db27acf5ecb8c38b8022f4ba4006aee4d8 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Jan 2021 14:07:30 +0100 Subject: [PATCH 032/171] Use perfect forwarding in Sound_Buffer ctor --- apps/openmw/mwsound/sound_buffer.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwsound/sound_buffer.hpp b/apps/openmw/mwsound/sound_buffer.hpp index e62392300..02e3c499b 100644 --- a/apps/openmw/mwsound/sound_buffer.hpp +++ b/apps/openmw/mwsound/sound_buffer.hpp @@ -25,8 +25,9 @@ namespace MWSound class Sound_Buffer { public: - Sound_Buffer(std::string resname, float volume, float mindist, float maxdist) - : mResourceName(std::move(resname)), mVolume(volume), mMinDist(mindist), mMaxDist(maxdist) + template + Sound_Buffer(T&& resname, float volume, float mindist, float maxdist) + : mResourceName(std::forward(resname)), mVolume(volume), mMinDist(mindist), mMaxDist(maxdist) {} const std::string& getResourceName() const noexcept { return mResourceName; } From 90c8e77e2ce75860ed7d674e3e068e164cec7a39 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Jan 2021 14:08:54 +0100 Subject: [PATCH 033/171] Remove redundant default Sound_Buffer fields initialization --- apps/openmw/mwsound/sound_buffer.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwsound/sound_buffer.hpp b/apps/openmw/mwsound/sound_buffer.hpp index 02e3c499b..61d569e13 100644 --- a/apps/openmw/mwsound/sound_buffer.hpp +++ b/apps/openmw/mwsound/sound_buffer.hpp @@ -42,9 +42,9 @@ namespace MWSound private: std::string mResourceName; - float mVolume = 0; - float mMinDist = 0; - float mMaxDist = 0; + float mVolume; + float mMinDist; + float mMaxDist; Sound_Handle mHandle = nullptr; std::size_t mUses = 0; From e4cce88142f383b8ef9dd086c23a545db57ce121 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Jan 2021 14:09:27 +0100 Subject: [PATCH 034/171] Replace std::tie by structured binding --- apps/openmw/mwsound/sound_buffer.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/openmw/mwsound/sound_buffer.cpp b/apps/openmw/mwsound/sound_buffer.cpp index 0e25ff601..cb71cb56d 100644 --- a/apps/openmw/mwsound/sound_buffer.cpp +++ b/apps/openmw/mwsound/sound_buffer.cpp @@ -82,9 +82,7 @@ namespace MWSound if (sfx->getHandle() == nullptr) { - Sound_Handle handle; - size_t size; - std::tie(handle, size) = mOutput->loadSound(sfx->getResourceName()); + auto [handle, size] = mOutput->loadSound(sfx->getResourceName()); if (handle == nullptr) return {}; From a6aba83741218241db323590cb2042232b2e7f9d Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Jan 2021 14:11:49 +0100 Subject: [PATCH 035/171] Remove redundant SoundManager::lookupSound and loadSound --- apps/openmw/mwsound/sound_buffer.hpp | 4 ++++ apps/openmw/mwsound/soundmanagerimp.cpp | 28 +++++++------------------ apps/openmw/mwsound/soundmanagerimp.hpp | 3 --- 3 files changed, 11 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwsound/sound_buffer.hpp b/apps/openmw/mwsound/sound_buffer.hpp index 61d569e13..5c45ac08a 100644 --- a/apps/openmw/mwsound/sound_buffer.hpp +++ b/apps/openmw/mwsound/sound_buffer.hpp @@ -60,8 +60,12 @@ namespace MWSound ~SoundBufferPool(); + /// Lookup a soundId for its sound data (resource name, local volume, + /// minRange, and maxRange) Sound_Buffer* lookup(const std::string& soundId) const; + /// Lookup a soundId for its sound data (resource name, local volume, + /// minRange, and maxRange), and ensure it's ready for use. Sound_Buffer* load(const std::string& soundId); void use(Sound_Buffer& sfx) diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 03ad58088..44465e5a7 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -120,20 +120,6 @@ namespace MWSound return DecoderPtr(new DEFAULT_DECODER (mVFS)); } - // Lookup a soundId for its sound data (resource name, local volume, - // minRange, and maxRange) - Sound_Buffer *SoundManager::lookupSound(const std::string &soundId) const - { - return mSoundBuffers.lookup(soundId); - } - - // Lookup a soundId for its sound data (resource name, local volume, - // minRange, and maxRange), and ensure it's ready for use. - Sound_Buffer *SoundManager::loadSound(const std::string &soundId) - { - return mSoundBuffers.load(soundId); - } - DecoderPtr SoundManager::loadVoice(const std::string &voicefile) { try @@ -509,7 +495,7 @@ namespace MWSound if(!mOutput->isInitialized()) return nullptr; - Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); + Sound_Buffer *sfx = mSoundBuffers.load(Misc::StringUtils::lowerCase(soundId)); if(!sfx) return nullptr; // Only one copy of given sound can be played at time, so stop previous copy @@ -545,7 +531,7 @@ namespace MWSound return nullptr; // Look up the sound in the ESM data - Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); + Sound_Buffer *sfx = mSoundBuffers.load(Misc::StringUtils::lowerCase(soundId)); if(!sfx) return nullptr; // Only one copy of given sound can be played at time on ptr, so stop previous copy @@ -597,7 +583,7 @@ namespace MWSound return nullptr; // Look up the sound in the ESM data - Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); + Sound_Buffer *sfx = mSoundBuffers.load(Misc::StringUtils::lowerCase(soundId)); if(!sfx) return nullptr; SoundPtr sound = getSoundRef(); @@ -645,7 +631,7 @@ namespace MWSound if(!mOutput->isInitialized()) return; - Sound_Buffer *sfx = lookupSound(Misc::StringUtils::lowerCase(soundId)); + Sound_Buffer *sfx = mSoundBuffers.lookup(Misc::StringUtils::lowerCase(soundId)); if (!sfx) return; stopSound(sfx, ptr); @@ -697,7 +683,7 @@ namespace MWSound SoundMap::iterator snditer = mActiveSounds.find(ptr); if(snditer != mActiveSounds.end()) { - Sound_Buffer *sfx = lookupSound(Misc::StringUtils::lowerCase(soundId)); + Sound_Buffer *sfx = mSoundBuffers.lookup(Misc::StringUtils::lowerCase(soundId)); if (sfx == nullptr) return; for(SoundBufferRefPair &sndbuf : snditer->second) @@ -713,7 +699,7 @@ namespace MWSound SoundMap::const_iterator snditer = mActiveSounds.find(ptr); if(snditer != mActiveSounds.end()) { - Sound_Buffer *sfx = lookupSound(Misc::StringUtils::lowerCase(soundId)); + Sound_Buffer *sfx = mSoundBuffers.lookup(Misc::StringUtils::lowerCase(soundId)); return std::find_if(snditer->second.cbegin(), snditer->second.cend(), [this,sfx](const SoundBufferRefPair &snd) -> bool { return snd.second == sfx && mOutput->isSoundPlaying(snd.first.get()); } @@ -826,7 +812,7 @@ namespace MWSound bool soundIdChanged = false; - Sound_Buffer* sfx = lookupSound(update.mId); + Sound_Buffer* sfx = mSoundBuffers.lookup(update.mId); if (mLastCell != cell) { const auto snditer = mActiveSounds.find(MWWorld::ConstPtr()); diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 21004dc94..6b9de800f 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -99,9 +99,6 @@ namespace MWSound Sound_Buffer *insertSound(const std::string &soundId, const ESM::Sound *sound); - Sound_Buffer *lookupSound(const std::string &soundId) const; - Sound_Buffer *loadSound(const std::string &soundId); - // returns a decoder to start streaming, or nullptr if the sound was not found DecoderPtr loadVoice(const std::string &voicefile); From 33da0af1d1760f5cd6b79a853aa0a6fe3fe848ff Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 18:25:48 +0400 Subject: [PATCH 036/171] Use explicit calls for virtual methods in constructors --- apps/openmw/mwrender/water.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index b9018e0a2..e786ce937 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -135,7 +135,7 @@ public: mClipNodeTransform = new osg::Group; mClipNodeTransform->addCullCallback(new FlipCallback(&mPlane)); - addChild(mClipNodeTransform); + osg::Group::addChild(mClipNodeTransform); mClipNode = new osg::ClipNode; @@ -240,7 +240,7 @@ public: setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); setReferenceFrame(osg::Camera::RELATIVE_RF); setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); - setName("RefractionCamera"); + osg::Camera::setName("RefractionCamera"); setCullCallback(new InheritViewPointCallback); setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); @@ -261,7 +261,7 @@ public: getOrCreateStateSet()->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); mClipCullNode = new ClipCullNode; - addChild(mClipCullNode); + osg::Camera::addChild(mClipCullNode); mRefractionTexture = new osg::Texture2D; mRefractionTexture->setTextureSize(rttSize, rttSize); @@ -335,7 +335,7 @@ public: setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); setReferenceFrame(osg::Camera::RELATIVE_RF); setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); - setName("ReflectionCamera"); + osg::Camera::setName("ReflectionCamera"); setCullCallback(new InheritViewPointCallback); setInterior(isInterior); @@ -364,7 +364,7 @@ public: getOrCreateStateSet()->setAttributeAndModes(frontFace, osg::StateAttribute::ON); mClipCullNode = new ClipCullNode; - addChild(mClipCullNode); + osg::Camera::addChild(mClipCullNode); SceneUtil::ShadowManager::disableShadowsForStateSet(getOrCreateStateSet()); } From 80ee1b55ea636184ec38718fa2d7dd928e1c5306 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 18:28:26 +0400 Subject: [PATCH 037/171] Protect assignment operator from this == &src case --- apps/openmw/mwworld/inventorystore.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 38007c1cb..fd0595703 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -120,6 +120,9 @@ MWWorld::InventoryStore::InventoryStore (const InventoryStore& store) MWWorld::InventoryStore& MWWorld::InventoryStore::operator= (const InventoryStore& store) { + if (this == &store) + return *this; + mListener = store.mListener; mInventoryListener = store.mInventoryListener; mMagicEffects = store.mMagicEffects; From a2d8a0b61a18da0c85a55930d480fbec8fb202dd Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 9 Jan 2021 14:44:15 +0000 Subject: [PATCH 038/171] engine.cpp typos --- apps/openmw/engine.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 8f23f710d..6f501e5ad 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -589,8 +589,8 @@ void OMW::Engine::createWindow(Settings::Manager& settings) Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->green << " bit green channel."; if (traits->blue < 8) Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->blue << " bit blue channel."; - if (traits->depth < 8) - Log(Debug::Warning) << "Warning: Framebuffer only has " << traits->red << " bits of depth precision."; + if (traits->depth < 24) + Log(Debug::Warning) << "Warning: Framebuffer only has " << traits->depth << " bits of depth precision."; traits->alpha = 0; // set to 0 to stop ScreenCaptureHandler reading the alpha channel } From 874348fb4681d4608d403e984ed9363972a19f4f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 19:19:38 +0400 Subject: [PATCH 039/171] Remove redundant code --- apps/openmw/mwmechanics/aiwander.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 68bcddf22..8e718061e 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -99,7 +99,6 @@ namespace MWMechanics { AiPackage::Options options; options.mUseVariableSpeed = true; - options.mRepeat = false; return options; } From 50e4600b160174024654a8d4fe8f82b0bb1cc99b Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 20:00:51 +0400 Subject: [PATCH 040/171] Reduce code duplication --- apps/opencs/view/world/util.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index ba9f40847..58d3d49e4 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -261,16 +261,10 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO return dsb; } + /// \todo implement size limit. QPlainTextEdit does not support a size limit. case CSMWorld::ColumnBase::Display_LongString: - { - QPlainTextEdit *edit = new QPlainTextEdit(parent); - edit->setUndoRedoEnabled (false); - return edit; - } - case CSMWorld::ColumnBase::Display_LongString256: { - /// \todo implement size limit. QPlainTextEdit does not support a size limit. QPlainTextEdit *edit = new QPlainTextEdit(parent); edit->setUndoRedoEnabled (false); return edit; From 1930f8f37d45c246186d996fc9edc30c74d5fab3 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 20:03:12 +0400 Subject: [PATCH 041/171] Fix copy-paste error --- apps/opencs/model/world/refidadapterimp.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index a73f76af9..0a29afcad 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -1858,18 +1858,18 @@ namespace CSMWorld break; // always save case 16: if (content.mType == ESM::AI_Travel) - content.mTravel.mZ = value.toFloat(); + content.mTravel.mX = value.toFloat(); else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) - content.mTarget.mZ = value.toFloat(); + content.mTarget.mX = value.toFloat(); else return; // return without saving break; // always save case 17: if (content.mType == ESM::AI_Travel) - content.mTravel.mZ = value.toFloat(); + content.mTravel.mY = value.toFloat(); else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) - content.mTarget.mZ = value.toFloat(); + content.mTarget.mY = value.toFloat(); else return; // return without saving From 564a0d7d559622d4d8ff7baebdbf9e4c950eb160 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 9 Jan 2021 18:25:46 +0100 Subject: [PATCH 042/171] Don't nuke fog when bounds have changed --- CHANGELOG.md | 1 + apps/openmw/mwrender/localmap.cpp | 126 +++++++++++++++++------------- 2 files changed, 74 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa3572282..3dce266d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ Bug #5441: Enemies can't push a player character when in critical strike stance Bug #5451: Magic projectiles don't disappear with the caster Bug #5452: Autowalk is being included in savegames + Bug #5469: Local map is reset when re-entering certain cells Bug #5472: Mistify mod causes CTD in 0.46 on Mac Bug #5479: NPCs who should be walking around town are standing around without walking Bug #5484: Zero value items shouldn't be able to be bought or sold for 1 gold diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 401e21ae4..5fa1a0e29 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -65,6 +65,15 @@ namespace return val*val; } + std::pair divideIntoSegments(const osg::BoundingBox& bounds, float mapSize) + { + osg::Vec2f min(bounds.xMin(), bounds.yMin()); + osg::Vec2f max(bounds.xMax(), bounds.yMax()); + osg::Vec2f length = max - min; + const int segsX = static_cast(std::ceil(length.x() / mapSize)); + const int segsY = static_cast(std::ceil(length.y() / mapSize)); + return {segsX, segsY}; + } } namespace MWRender @@ -127,12 +136,7 @@ void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) } else { - // FIXME: segmenting code duplicated from requestMap - osg::Vec2f min(mBounds.xMin(), mBounds.yMin()); - osg::Vec2f max(mBounds.xMax(), mBounds.yMax()); - osg::Vec2f length = max-min; - const int segsX = static_cast(std::ceil(length.x() / mMapWorldSize)); - const int segsY = static_cast(std::ceil(length.y() / mMapWorldSize)); + auto segments = divideIntoSegments(mBounds, mMapWorldSize); std::unique_ptr fog (new ESM::FogState()); @@ -142,11 +146,11 @@ void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) fog->mBounds.mMaxY = mBounds.yMax(); fog->mNorthMarkerAngle = mAngle; - fog->mFogTextures.reserve(segsX*segsY); + fog->mFogTextures.reserve(segments.first * segments.second); - for (int x=0; x> segmentMappings; if (cell->getFog()) { ESM::FogState* fog = cell->getFog(); - osg::Vec3f newMin (fog->mBounds.mMinX, fog->mBounds.mMinY, zMin); - osg::Vec3f newMax (fog->mBounds.mMaxX, fog->mBounds.mMaxY, zMax); - - osg::Vec3f minDiff = newMin - mBounds._min; - osg::Vec3f maxDiff = newMax - mBounds._max; - - if (std::abs(minDiff.x()) > padding || std::abs(minDiff.y()) > padding - || std::abs(maxDiff.x()) > padding || std::abs(maxDiff.y()) > padding - || std::abs(mAngle - fog->mNorthMarkerAngle) > osg::DegreesToRadians(5.f)) + if (std::abs(mAngle - fog->mNorthMarkerAngle) < osg::DegreesToRadians(5.f)) { - // Nuke it - cellHasValidFog = false; - } - else - { - // Looks sane, use it - mBounds = osg::BoundingBox(newMin, newMax); + // Expand mBounds so the saved textures fit the same grid + int xOffset = 0; + int yOffset = 0; + if(fog->mBounds.mMinX < mBounds.xMin()) + { + mBounds.xMin() = fog->mBounds.mMinX; + } + else if(fog->mBounds.mMinX > mBounds.xMin()) + { + float diff = fog->mBounds.mMinX - mBounds.xMin(); + xOffset += diff / mMapWorldSize; + xOffset++; + mBounds.xMin() = fog->mBounds.mMinX - xOffset * mMapWorldSize; + } + if(fog->mBounds.mMinY < mBounds.yMin()) + { + mBounds.yMin() = fog->mBounds.mMinY; + } + else if(fog->mBounds.mMinY > mBounds.yMin()) + { + float diff = fog->mBounds.mMinY - mBounds.yMin(); + yOffset += diff / mMapWorldSize; + yOffset++; + mBounds.yMin() = fog->mBounds.mMinY - yOffset * mMapWorldSize; + } + mBounds.xMax() = std::max(mBounds.xMax(), fog->mBounds.mMaxX); + mBounds.yMax() = std::max(mBounds.yMax(), fog->mBounds.mMaxY); + + if(xOffset != 0 || yOffset != 0) + Log(Debug::Warning) << "Warning: expanding fog by " << xOffset << ", " << yOffset; + + const auto& textures = fog->mFogTextures; + segmentMappings.reserve(textures.size()); + osg::BoundingBox savedBounds{ + fog->mBounds.mMinX, fog->mBounds.mMinY, 0, + fog->mBounds.mMaxX, fog->mBounds.mMaxY, 0 + }; + auto segments = divideIntoSegments(savedBounds, mMapWorldSize); + for (int x = 0; x < segments.first; ++x) + for (int y = 0; y < segments.second; ++y) + segmentMappings.emplace_back(std::make_pair(x + xOffset, y + yOffset)); + mAngle = fog->mNorthMarkerAngle; - cellHasValidFog = true; } } osg::Vec2f min(mBounds.xMin(), mBounds.yMin()); - osg::Vec2f max(mBounds.xMax(), mBounds.yMax()); - - osg::Vec2f length = max-min; - osg::Vec2f center(bounds.center().x(), bounds.center().y()); + osg::Vec2f center(mBounds.center().x(), mBounds.center().y()); + osg::Quat cameraOrient (mAngle, osg::Vec3d(0,0,-1)); - // divide into segments - const int segsX = static_cast(std::ceil(length.x() / mMapWorldSize)); - const int segsY = static_cast(std::ceil(length.y() / mMapWorldSize)); - - int i = 0; - for (int x=0; xgetFog(); - - // We are using the same bounds and angle as we were using when the textures were originally made. Segments should come out the same. - if (i >= int(fog->mFogTextures.size())) + if(segmentMappings[index] == coords) { - Log(Debug::Warning) << "Warning: fog texture count mismatch"; + ESM::FogState* fog = cell->getFog(); + segment.loadFogOfWar(fog->mFogTextures[index]); + loaded = true; break; } - - segment.loadFogOfWar(fog->mFogTextures[i]); } + if(!loaded) + segment.initFogOfWar(); } - ++i; } } } From ad101de733b383c4f708079c31abe42fedc840ec Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 22:58:54 +0400 Subject: [PATCH 043/171] Merge declaration and initialization --- apps/opencs/view/render/terraintexturemode.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/render/terraintexturemode.cpp b/apps/opencs/view/render/terraintexturemode.cpp index 09d6b135a..f4a3f461c 100644 --- a/apps/opencs/view/render/terraintexturemode.cpp +++ b/apps/opencs/view/render/terraintexturemode.cpp @@ -427,10 +427,8 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe { if (i_cell == cellX && j_cell == cellY && abs(i-xHitInCell) < r && abs(j-yHitInCell) < r) { - int distanceX(0); - int distanceY(0); - distanceX = abs(i-xHitInCell); - distanceY = abs(j-yHitInCell); + int distanceX = abs(i-xHitInCell); + int distanceY = abs(j-yHitInCell); float distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2))); float rf = static_cast(mBrushSize) / 2; if (distance < rf) newTerrain[j*landTextureSize+i] = brushInt; From 9bc687e20992cf5b7941343d7bce379f2c66b252 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 9 Jan 2021 20:56:20 +0100 Subject: [PATCH 044/171] Avoid a rare but possible deadlock around mCollisionWorldMutex. What happened is that the last handle to an Actor shared_ptr was a promoted weak_ptr. When the shared_ptr goes out of scope, the Actor dtor is invoked. That involves removing the Actor collision object after exclusively locking mCollisionWorldMutex. In this case, the lock was already held in the outter scope of the promoted weak_ptr. Reduce the scope of the mCollisionWorldMutex to never encompass the lifetime of a promoted weak_ptr. --- apps/openmw/mwphysics/mtphysics.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 0d1e5962a..c77bc6acd 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -357,7 +357,6 @@ namespace MWPhysics { if (!mDeferAabbUpdate || immediate) { - std::unique_lock lock(mCollisionWorldMutex); updatePtrAabb(ptr); } else @@ -410,7 +409,7 @@ namespace MWPhysics void PhysicsTaskScheduler::updateAabbs() { - std::scoped_lock lock(mCollisionWorldMutex, mUpdateAabbMutex); + std::scoped_lock lock(mUpdateAabbMutex); std::for_each(mUpdateAabb.begin(), mUpdateAabb.end(), [this](const std::weak_ptr& ptr) { updatePtrAabb(ptr); }); mUpdateAabb.clear(); @@ -420,6 +419,7 @@ namespace MWPhysics { if (const auto p = ptr.lock()) { + std::scoped_lock lock(mCollisionWorldMutex); if (const auto actor = std::dynamic_pointer_cast(p)) { actor->updateCollisionObjectPosition(); @@ -451,9 +451,11 @@ namespace MWPhysics int job = 0; while (mRemainingSteps && (job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) { - MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet); if(const auto actor = mActorsFrameData[job].mActor.lock()) + { + MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet); MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData); + } } mPostStepBarrier->wait(); @@ -478,13 +480,13 @@ namespace MWPhysics void PhysicsTaskScheduler::updateActorsPositions() { - std::unique_lock lock(mCollisionWorldMutex); for (auto& actorData : mActorsFrameData) { if(const auto actor = actorData.mActor.lock()) { if (actor->setPosition(actorData.mPosition)) { + std::scoped_lock lock(mCollisionWorldMutex); actor->updateCollisionObjectPosition(); mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); } From 60f66f5e29a43f8ef566d3855b5abc699e24527a Mon Sep 17 00:00:00 2001 From: fredzio Date: Thu, 7 Jan 2021 11:00:57 +0100 Subject: [PATCH 045/171] Remove never used parameter from CharacterController:update() --- apps/openmw/mwmechanics/character.cpp | 13 ++++++------- apps/openmw/mwmechanics/character.hpp | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index b9bfe6bc0..5c50c547c 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1898,7 +1898,7 @@ void CharacterController::updateAnimQueue() mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1); } -void CharacterController::update(float duration, bool animationOnly) +void CharacterController::update(float duration) { MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Class &cls = mPtr.getClass(); @@ -2386,10 +2386,10 @@ void CharacterController::update(float duration, bool animationOnly) world->rotateObject(mPtr, rot.x(), rot.y(), 0.0f, true); } - if (!animationOnly && !mMovementAnimationControlled) + if (!mMovementAnimationControlled) world->queueMovement(mPtr, vec); } - else if (!animationOnly) + else // We must always queue movement, even if there is none, to apply gravity. world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); @@ -2414,8 +2414,7 @@ void CharacterController::update(float duration, bool animationOnly) playDeath(1.f, mDeathState); } // We must always queue movement, even if there is none, to apply gravity. - if (!animationOnly) - world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); + world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); } bool isPersist = isPersistentAnimPlaying(); @@ -2452,10 +2451,10 @@ void CharacterController::update(float duration, bool animationOnly) { moved.z() = 1.0; } - } + } // Update movement - if(!animationOnly && mMovementAnimationControlled && mPtr.getClass().isActor()) + if(mMovementAnimationControlled && mPtr.getClass().isActor()) world->queueMovement(mPtr, moved); mSkipAnim = false; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 2308ba971..0821b3225 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -248,7 +248,7 @@ public: void updatePtr(const MWWorld::Ptr &ptr); - void update(float duration, bool animationOnly=false); + void update(float duration); bool onOpen(); void onClose(); From 313e8959129c05145a16194c0893282cb27523ad Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sat, 9 Jan 2021 21:35:07 +0000 Subject: [PATCH 046/171] [OpenMW-CS] Cube and sphere instance selection --- CHANGELOG.md | 1 + CHANGELOG_PR.md | 2 +- apps/opencs/model/prefs/state.cpp | 15 + apps/opencs/view/render/cell.cpp | 47 +++ apps/opencs/view/render/cell.hpp | 7 + apps/opencs/view/render/instancedragmodes.hpp | 18 + apps/opencs/view/render/instancemode.cpp | 106 +++++- apps/opencs/view/render/instancemode.hpp | 16 +- .../view/render/instanceselectionmode.cpp | 347 +++++++++++++++++- .../view/render/instanceselectionmode.hpp | 32 +- .../view/render/pagedworldspacewidget.cpp | 16 + .../view/render/pagedworldspacewidget.hpp | 5 + apps/opencs/view/render/selectionmode.cpp | 21 +- .../view/render/unpagedworldspacewidget.cpp | 10 + .../view/render/unpagedworldspacewidget.hpp | 4 + apps/opencs/view/render/worldspacewidget.hpp | 5 + 16 files changed, 627 insertions(+), 25 deletions(-) create mode 100644 apps/opencs/view/render/instancedragmodes.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index ce0cce952..fa33b6647 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -94,6 +94,7 @@ Feature #2386: Distant Statics in the form of Object Paging Feature #2404: Levelled List can not be placed into a container Feature #2686: Timestamps in openmw.log + Feature #3171: OpenMW-CS: Instance drag selection Feature #4894: Consider actors as obstacles for pathfinding Feature #5043: Head Bobbing Feature #5199: Improve Scene Colors diff --git a/CHANGELOG_PR.md b/CHANGELOG_PR.md index 69ad0cc1b..fdf27dc93 100644 --- a/CHANGELOG_PR.md +++ b/CHANGELOG_PR.md @@ -21,7 +21,7 @@ New Features: - Basics of Collada animations are now supported via osgAnimation plugin (#5456) New Editor Features: -- ? +- Instance selection modes are now implemented (centred cube, corner-dragged cube, sphere) with four user-configurable actions (select only, add to selection, remove from selection, invert selection) (#3171) Bug Fixes: - NiParticleColorModifier in NIF files is now properly handled which solves issues regarding particle effects, e.g., smoke and fire (#1952, #3676) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 588be9ccb..32b1ee33f 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -247,6 +247,15 @@ void CSMPrefs::State::declare() EnumValues landeditOutsideVisibleCell; landeditOutsideVisibleCell.add (showAndLandEdit).add (dontLandEdit); + EnumValue SelectOnly ("Select only"); + EnumValue SelectAdd ("Add to selection"); + EnumValue SelectRemove ("Remove from selection"); + EnumValue selectInvert ("Invert selection"); + EnumValues primarySelectAction; + primarySelectAction.add (SelectOnly).add (SelectAdd).add (SelectRemove).add (selectInvert); + EnumValues secondarySelectAction; + secondarySelectAction.add (SelectOnly).add (SelectAdd).add (SelectRemove).add (selectInvert); + declareCategory ("3D Scene Editing"); declareInt ("distance", "Drop Distance", 50). setTooltip ("If an instance drop can not be placed against another object at the " @@ -276,6 +285,12 @@ void CSMPrefs::State::declare() declareBool ("open-list-view", "Open displays list view", false). setTooltip ("When opening a reference from the scene view, it will open the" " instance list view instead of the individual instance record view."); + declareEnum ("primary-select-action", "Action for primary select", SelectOnly). + setTooltip("Selection can be chosen between select only, add to selection, remove from selection and invert selection."). + addValues (primarySelectAction); + declareEnum ("secondary-select-action", "Action for secondary select", SelectAdd). + setTooltip("Selection can be chosen between select only, add to selection, remove from selection and invert selection."). + addValues (secondarySelectAction); declareCategory ("Key Bindings"); diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 75d83cf63..2502dc1fd 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -1,5 +1,7 @@ #include "cell.hpp" +#include + #include #include #include @@ -25,6 +27,7 @@ #include "pathgrid.hpp" #include "terrainstorage.hpp" #include "object.hpp" +#include "instancedragmodes.hpp" namespace CSVRender { @@ -496,6 +499,50 @@ void CSVRender::Cell::selectAllWithSameParentId (int elementMask) } } +void CSVRender::Cell::handleSelectDrag(Object* object, DragMode dragMode) +{ + if (dragMode == DragMode_Select_Only || dragMode == DragMode_Select_Add) + object->setSelected(true); + + else if (dragMode == DragMode_Select_Remove) + object->setSelected(false); + + else if (dragMode == DragMode_Select_Invert) + object->setSelected (!object->getSelected()); +} + +void CSVRender::Cell::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) +{ + for (auto& object : mObjects) + { + if (dragMode == DragMode_Select_Only) object.second->setSelected (false); + + if ( ( object.second->getPosition().pos[0] > pointA[0] && object.second->getPosition().pos[0] < pointB[0] ) || + ( object.second->getPosition().pos[0] > pointB[0] && object.second->getPosition().pos[0] < pointA[0] )) + { + if ( ( object.second->getPosition().pos[1] > pointA[1] && object.second->getPosition().pos[1] < pointB[1] ) || + ( object.second->getPosition().pos[1] > pointB[1] && object.second->getPosition().pos[1] < pointA[1] )) + { + if ( ( object.second->getPosition().pos[2] > pointA[2] && object.second->getPosition().pos[2] < pointB[2] ) || + ( object.second->getPosition().pos[2] > pointB[2] && object.second->getPosition().pos[2] < pointA[2] )) + handleSelectDrag(object.second, dragMode); + } + + } + } +} + +void CSVRender::Cell::selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) +{ + for (auto& object : mObjects) + { + if (dragMode == DragMode_Select_Only) object.second->setSelected (false); + + float distanceFromObject = (point - object.second->getPosition().asVec3()).length(); + if (distanceFromObject < distance) handleSelectDrag(object.second, dragMode); + } +} + void CSVRender::Cell::setCellArrows (int mask) { for (int i=0; i<4; ++i) diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 281ac6735..5998a4ee6 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -10,6 +10,7 @@ #include "../../model/world/cellcoordinates.hpp" #include "terrainstorage.hpp" +#include "instancedragmodes.hpp" class QModelIndex; @@ -152,6 +153,12 @@ namespace CSVRender // already selected void selectAllWithSameParentId (int elementMask); + void handleSelectDrag(Object* object, DragMode dragMode); + + void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode); + + void selectWithinDistance(const osg::Vec3d& pointA, float distance, DragMode dragMode); + void setCellArrows (int mask); /// \brief Set marker for this cell. diff --git a/apps/opencs/view/render/instancedragmodes.hpp b/apps/opencs/view/render/instancedragmodes.hpp new file mode 100644 index 000000000..01547545a --- /dev/null +++ b/apps/opencs/view/render/instancedragmodes.hpp @@ -0,0 +1,18 @@ +#ifndef CSV_WIDGET_INSTANCEDRAGMODES_H +#define CSV_WIDGET_INSTANCEDRAGMODES_H + +namespace CSVRender +{ + enum DragMode + { + DragMode_None, + DragMode_Move, + DragMode_Rotate, + DragMode_Scale, + DragMode_Select_Only, + DragMode_Select_Add, + DragMode_Select_Remove, + DragMode_Select_Invert + }; +} +#endif diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 19018fae6..4f6759cdb 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -96,6 +96,33 @@ osg::Vec3f CSVRender::InstanceMode::getScreenCoords(const osg::Vec3f& pos) return pos * combined; } +osg::Vec3f CSVRender::InstanceMode::getProjectionSpaceCoords(const osg::Vec3f& pos) +{ + osg::Matrix viewMatrix = getWorldspaceWidget().getCamera()->getViewMatrix(); + osg::Matrix projMatrix = getWorldspaceWidget().getCamera()->getProjectionMatrix(); + osg::Matrix combined = viewMatrix * projMatrix; + + return pos * combined; +} + +osg::Vec3f CSVRender::InstanceMode::getMousePlaneCoords(const QPoint& point, const osg::Vec3d& dragStart) +{ + osg::Matrix viewMatrix; + viewMatrix.invert(getWorldspaceWidget().getCamera()->getViewMatrix()); + osg::Matrix projMatrix; + projMatrix.invert(getWorldspaceWidget().getCamera()->getProjectionMatrix()); + osg::Matrix combined = projMatrix * viewMatrix; + + /* calculate viewport normalized coordinates + note: is there a reason to use getCamera()->getViewport()->computeWindowMatrix() instead? */ + float x = (point.x() * 2) / getWorldspaceWidget().getCamera()->getViewport()->width() - 1.0f; + float y = 1.0f - (point.y() * 2) / getWorldspaceWidget().getCamera()->getViewport()->height(); + + osg::Vec3f mousePlanePoint = osg::Vec3f(x, y, dragStart.z()) * combined; + + return mousePlanePoint; +} + CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, osg::ref_ptr parentNode, QWidget *parent) : EditMode (worldspaceWidget, QIcon (":scenetoolbar/editing-instance"), Mask_Reference | Mask_Terrain, "Instance editing", parent), mSubMode (nullptr), mSubModeId ("move"), mSelectionMode (nullptr), mDragMode (DragMode_None), @@ -146,7 +173,7 @@ void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar) } if (!mSelectionMode) - mSelectionMode = new InstanceSelectionMode (toolbar, getWorldspaceWidget()); + mSelectionMode = new InstanceSelectionMode (toolbar, getWorldspaceWidget(), mParentNode); mDragMode = DragMode_None; @@ -322,6 +349,42 @@ bool CSVRender::InstanceMode::secondaryEditStartDrag (const QPoint& pos) return false; } +bool CSVRender::InstanceMode::primarySelectStartDrag (const QPoint& pos) +{ + if (mDragMode!=DragMode_None || mLocked) + return false; + + std::string primarySelectAction = CSMPrefs::get()["3D Scene Editing"]["primary-select-action"].toString(); + + if ( primarySelectAction == "Select only" ) mDragMode = DragMode_Select_Only; + else if ( primarySelectAction == "Add to selection" ) mDragMode = DragMode_Select_Add; + else if ( primarySelectAction == "Remove from selection" ) mDragMode = DragMode_Select_Remove; + else if ( primarySelectAction == "Invert selection" ) mDragMode = DragMode_Select_Invert; + + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + mSelectionMode->setDragStart(hit.worldPos); + + return true; +} + +bool CSVRender::InstanceMode::secondarySelectStartDrag (const QPoint& pos) +{ + if (mDragMode!=DragMode_None || mLocked) + return false; + + std::string secondarySelectAction = CSMPrefs::get()["3D Scene Editing"]["secondary-select-action"].toString(); + + if ( secondarySelectAction == "Select only" ) mDragMode = DragMode_Select_Only; + else if ( secondarySelectAction == "Add to selection" ) mDragMode = DragMode_Select_Add; + else if ( secondarySelectAction == "Remove from selection" ) mDragMode = DragMode_Select_Remove; + else if ( secondarySelectAction == "Invert selection" ) mDragMode = DragMode_Select_Invert; + + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + mSelectionMode->setDragStart(hit.worldPos); + + return true; +} + void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor) { osg::Vec3f offset; @@ -432,6 +495,24 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou // Only uniform scaling is currently supported offset = osg::Vec3f(scale, scale, scale); } + else if (mSelectionMode->getCurrentId() == "cube-centre") + { + osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart())); + mSelectionMode->drawSelectionCubeCentre (mousePlanePoint); + return; + } + else if (mSelectionMode->getCurrentId() == "cube-corner") + { + osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart())); + mSelectionMode->drawSelectionCubeCorner (mousePlanePoint); + return; + } + else if (mSelectionMode->getCurrentId() == "sphere") + { + osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart())); + mSelectionMode->drawSelectionSphere (mousePlanePoint); + return; + } // Apply for (std::vector >::iterator iter (selection.begin()); iter!=selection.end(); ++iter) @@ -495,6 +576,22 @@ void CSVRender::InstanceMode::dragCompleted(const QPoint& pos) case DragMode_Move: description = "Move Instances"; break; case DragMode_Rotate: description = "Rotate Instances"; break; case DragMode_Scale: description = "Scale Instances"; break; + case DragMode_Select_Only : + handleSelectDrag(pos); + return; + break; + case DragMode_Select_Add : + handleSelectDrag(pos); + return; + break; + case DragMode_Select_Remove : + handleSelectDrag(pos); + return; + break; + case DragMode_Select_Invert : + handleSelectDrag(pos); + return; + break; case DragMode_None: break; } @@ -680,6 +777,13 @@ void CSVRender::InstanceMode::subModeChanged (const std::string& id) getWorldspaceWidget().setSubMode (getSubModeFromId (id), Mask_Reference); } +void CSVRender::InstanceMode::handleSelectDrag(const QPoint& pos) +{ + osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart())); + mSelectionMode->dragEnded (mousePlanePoint, mDragMode); + mDragMode = DragMode_None; +} + void CSVRender::InstanceMode::deleteSelectedInstances(bool active) { std::vector > selection = getWorldspaceWidget().getSelection (Mask_Reference); diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 32dd4ac67..0a4f2e478 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -9,6 +9,7 @@ #include #include "editmode.hpp" +#include "instancedragmodes.hpp" namespace CSVWidget { @@ -25,14 +26,6 @@ namespace CSVRender { Q_OBJECT - enum DragMode - { - DragMode_None, - DragMode_Move, - DragMode_Rotate, - DragMode_Scale - }; - enum DropMode { Collision, @@ -57,6 +50,9 @@ namespace CSVRender osg::Vec3f getSelectionCenter(const std::vector >& selection) const; osg::Vec3f getScreenCoords(const osg::Vec3f& pos); + osg::Vec3f getProjectionSpaceCoords(const osg::Vec3f& pos); + osg::Vec3f getMousePlaneCoords(const QPoint& point, const osg::Vec3d& dragStart); + void handleSelectDrag(const QPoint& pos); void dropInstance(DropMode dropMode, CSVRender::Object* object, float objectHeight); float getDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight); @@ -84,6 +80,10 @@ namespace CSVRender bool secondaryEditStartDrag (const QPoint& pos) override; + bool primarySelectStartDrag(const QPoint& pos) override; + + bool secondarySelectStartDrag(const QPoint& pos) override; + void drag (const QPoint& pos, int diffX, int diffY, double speedFactor) override; void dragCompleted(const QPoint& pos) override; diff --git a/apps/opencs/view/render/instanceselectionmode.cpp b/apps/opencs/view/render/instanceselectionmode.cpp index bf8ede0eb..9b5fb759c 100644 --- a/apps/opencs/view/render/instanceselectionmode.cpp +++ b/apps/opencs/view/render/instanceselectionmode.cpp @@ -2,17 +2,24 @@ #include #include +#include + +#include +#include +#include +#include #include "../../model/world/idtable.hpp" #include "../../model/world/commands.hpp" +#include "instancedragmodes.hpp" #include "worldspacewidget.hpp" #include "object.hpp" namespace CSVRender { - InstanceSelectionMode::InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget) - : SelectionMode(parent, worldspaceWidget, Mask_Reference) + InstanceSelectionMode::InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, osg::Group *cellNode) + : SelectionMode(parent, worldspaceWidget, Mask_Reference), mParentNode(cellNode) { mSelectSame = new QAction("Extend selection to instances with same object ID", this); mDeleteSelection = new QAction("Delete selected instances", this); @@ -21,6 +28,342 @@ namespace CSVRender connect(mDeleteSelection, SIGNAL(triggered()), this, SLOT(deleteSelection())); } + InstanceSelectionMode::~InstanceSelectionMode() + { + mParentNode->removeChild(mBaseNode); + } + + void InstanceSelectionMode::setDragStart(const osg::Vec3d& dragStart) + { + mDragStart = dragStart; + } + + const osg::Vec3d& InstanceSelectionMode::getDragStart() + { + return mDragStart; + } + + void InstanceSelectionMode::dragEnded(const osg::Vec3d& dragEndPoint, DragMode dragMode) + { + float dragDistance = (mDragStart - dragEndPoint).length(); + if (mBaseNode) mParentNode->removeChild (mBaseNode); + if (getCurrentId() == "cube-centre") + { + osg::Vec3d pointA(mDragStart[0] - dragDistance, mDragStart[1] - dragDistance, mDragStart[2] - dragDistance); + osg::Vec3d pointB(mDragStart[0] + dragDistance, mDragStart[1] + dragDistance, mDragStart[2] + dragDistance); + getWorldspaceWidget().selectInsideCube(pointA, pointB, dragMode); + } + else if (getCurrentId() == "cube-corner") + { + getWorldspaceWidget().selectInsideCube(mDragStart, dragEndPoint, dragMode); + } + else if (getCurrentId() == "sphere") + { + getWorldspaceWidget().selectWithinDistance(mDragStart, dragDistance, dragMode); + } + } + + void InstanceSelectionMode::drawSelectionCubeCentre(const osg::Vec3f& mousePlanePoint) + { + float dragDistance = (mDragStart - mousePlanePoint).length(); + drawSelectionCube(mDragStart, dragDistance); + } + + void InstanceSelectionMode::drawSelectionCubeCorner(const osg::Vec3f& mousePlanePoint) + { + drawSelectionBox(mDragStart, mousePlanePoint); + } + + void InstanceSelectionMode::drawSelectionBox(const osg::Vec3d& pointA, const osg::Vec3d& pointB) + { + if (mBaseNode) mParentNode->removeChild (mBaseNode); + mBaseNode = new osg::PositionAttitudeTransform; + mBaseNode->setPosition(pointA); + + osg::ref_ptr geometry (new osg::Geometry); + + osg::Vec3Array *vertices = new osg::Vec3Array; + vertices->push_back (osg::Vec3f (0.0f, 0.0f, 0.0f)); + vertices->push_back (osg::Vec3f (0.0f, 0.0f, pointB[2] - pointA[2])); + vertices->push_back (osg::Vec3f (0.0f, pointB[1] - pointA[1], 0.0f)); + vertices->push_back (osg::Vec3f (0.0f, pointB[1] - pointA[1], pointB[2] - pointA[2])); + + vertices->push_back (osg::Vec3f (pointB[0] - pointA[0], 0.0f, 0.0f)); + vertices->push_back (osg::Vec3f (pointB[0] - pointA[0], 0.0f, pointB[2] - pointA[2])); + vertices->push_back (osg::Vec3f (pointB[0] - pointA[0], pointB[1] - pointA[1], 0.0f)); + vertices->push_back (osg::Vec3f (pointB[0] - pointA[0], pointB[1] - pointA[1], pointB[2] - pointA[2])); + + geometry->setVertexArray (vertices); + + osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0); + + // top + primitives->push_back (2); + primitives->push_back (1); + primitives->push_back (0); + + primitives->push_back (3); + primitives->push_back (1); + primitives->push_back (2); + + // bottom + primitives->push_back (4); + primitives->push_back (5); + primitives->push_back (6); + + primitives->push_back (6); + primitives->push_back (5); + primitives->push_back (7); + + // sides + primitives->push_back (1); + primitives->push_back (4); + primitives->push_back (0); + + primitives->push_back (4); + primitives->push_back (1); + primitives->push_back (5); + + primitives->push_back (4); + primitives->push_back (2); + primitives->push_back (0); + + primitives->push_back (6); + primitives->push_back (2); + primitives->push_back (4); + + primitives->push_back (6); + primitives->push_back (3); + primitives->push_back (2); + + primitives->push_back (7); + primitives->push_back (3); + primitives->push_back (6); + + primitives->push_back (1); + primitives->push_back (3); + primitives->push_back (5); + + primitives->push_back (5); + primitives->push_back (3); + primitives->push_back (7); + + geometry->addPrimitiveSet (primitives); + + osg::Vec4Array *colours = new osg::Vec4Array; + + colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.5f, 0.2f)); + colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); + colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); + colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); + colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + + geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); + + geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setMode (GL_BLEND, osg::StateAttribute::ON); + geometry->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + + mBaseNode->addChild (geometry); + mParentNode->addChild(mBaseNode); + } + + void InstanceSelectionMode::drawSelectionCube(const osg::Vec3d& point, float radius) + { + if (mBaseNode) mParentNode->removeChild (mBaseNode); + mBaseNode = new osg::PositionAttitudeTransform; + mBaseNode->setPosition(point); + + osg::ref_ptr geometry (new osg::Geometry); + + osg::Vec3Array *vertices = new osg::Vec3Array; + for (int i = 0; i < 2; ++i) + { + float height = i ? -radius : radius; + vertices->push_back (osg::Vec3f (height, -radius, -radius)); + vertices->push_back (osg::Vec3f (height, -radius, radius)); + vertices->push_back (osg::Vec3f (height, radius, -radius)); + vertices->push_back (osg::Vec3f (height, radius, radius)); + } + + geometry->setVertexArray (vertices); + + osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0); + + // top + primitives->push_back (2); + primitives->push_back (1); + primitives->push_back (0); + + primitives->push_back (3); + primitives->push_back (1); + primitives->push_back (2); + + // bottom + primitives->push_back (4); + primitives->push_back (5); + primitives->push_back (6); + + primitives->push_back (6); + primitives->push_back (5); + primitives->push_back (7); + + // sides + primitives->push_back (1); + primitives->push_back (4); + primitives->push_back (0); + + primitives->push_back (4); + primitives->push_back (1); + primitives->push_back (5); + + primitives->push_back (4); + primitives->push_back (2); + primitives->push_back (0); + + primitives->push_back (6); + primitives->push_back (2); + primitives->push_back (4); + + primitives->push_back (6); + primitives->push_back (3); + primitives->push_back (2); + + primitives->push_back (7); + primitives->push_back (3); + primitives->push_back (6); + + primitives->push_back (1); + primitives->push_back (3); + primitives->push_back (5); + + primitives->push_back (5); + primitives->push_back (3); + primitives->push_back (7); + + geometry->addPrimitiveSet (primitives); + + osg::Vec4Array *colours = new osg::Vec4Array; + + colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.5f, 0.2f)); + colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); + colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); + colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); + colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + + geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); + + geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setMode (GL_BLEND, osg::StateAttribute::ON); + geometry->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + + mBaseNode->addChild (geometry); + mParentNode->addChild(mBaseNode); + } + + void InstanceSelectionMode::drawSelectionSphere(const osg::Vec3f& mousePlanePoint) + { + float dragDistance = (mDragStart - mousePlanePoint).length(); + drawSelectionSphere(mDragStart, dragDistance); + } + + void InstanceSelectionMode::drawSelectionSphere(const osg::Vec3d& point, float radius) + { + if (mBaseNode) mParentNode->removeChild (mBaseNode); + mBaseNode = new osg::PositionAttitudeTransform; + mBaseNode->setPosition(point); + + osg::ref_ptr geometry (new osg::Geometry); + + osg::Vec3Array *vertices = new osg::Vec3Array; + int resolution = 32; + float radiusPerResolution = radius / resolution; + float reciprocalResolution = 1.0f / resolution; + float doubleReciprocalRes = reciprocalResolution * 2; + + osg::Vec4Array *colours = new osg::Vec4Array; + + for (float i = 0.0; i <= resolution; i += 2) + { + float iShifted = (static_cast(i) - resolution / 2.0f); // i - 16 = -16 ... 16 + float xPercentile = iShifted * doubleReciprocalRes; + float x = xPercentile * radius; + float thisRadius = sqrt (radius * radius - x * x); + + //the next row + float iShifted2 = (static_cast(i + 1) - resolution / 2.0f); + float xPercentile2 = iShifted2 * doubleReciprocalRes; + float x2 = xPercentile2 * radius; + float thisRadius2 = sqrt (radius * radius - x2 * x2); + + for (int j = 0; j < resolution; ++j) + { + float vertexX = thisRadius * sin(j * reciprocalResolution * osg::PI * 2); + float vertexY = i * radiusPerResolution * 2 - radius; + float vertexZ = thisRadius * cos(j * reciprocalResolution * osg::PI * 2); + float heightPercentage = (vertexZ + radius) / (radius * 2); + vertices->push_back (osg::Vec3f (vertexX, vertexY, vertexZ)); + colours->push_back (osg::Vec4f (heightPercentage, heightPercentage, heightPercentage, 0.3f)); + + float vertexNextRowX = thisRadius2 * sin(j * reciprocalResolution * osg::PI * 2); + float vertexNextRowY = (i + 1) * radiusPerResolution * 2 - radius; + float vertexNextRowZ = thisRadius2 * cos(j * reciprocalResolution * osg::PI * 2); + float heightPercentageNextRow = (vertexZ + radius) / (radius * 2); + vertices->push_back (osg::Vec3f (vertexNextRowX, vertexNextRowY, vertexNextRowZ)); + colours->push_back (osg::Vec4f (heightPercentageNextRow, heightPercentageNextRow, heightPercentageNextRow, 0.3f)); + } + } + + geometry->setVertexArray (vertices); + + osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLE_STRIP, 0); + + for (int i = 0; i < resolution; ++i) + { + //Even + for (int j = 0; j < resolution * 2; ++j) + { + if (i * resolution * 2 + j > static_cast(vertices->size()) - 1) continue; + primitives->push_back (i * resolution * 2 + j); + } + if (i * resolution * 2 > static_cast(vertices->size()) - 1) continue; + primitives->push_back (i * resolution * 2); + primitives->push_back (i * resolution * 2 + 1); + + //Odd + for (int j = 1; j < resolution * 2 - 2; j += 2) + { + if ((i + 1) * resolution * 2 + j - 1 > static_cast(vertices->size()) - 1) continue; + primitives->push_back ((i + 1) * resolution * 2 + j - 1); + primitives->push_back (i * resolution * 2 + j + 2); + } + if ((i + 2) * resolution * 2 - 2 > static_cast(vertices->size()) - 1) continue; + primitives->push_back ((i + 2) * resolution * 2 - 2); + primitives->push_back (i * resolution * 2 + 1); + primitives->push_back ((i + 1) * resolution * 2); + } + + geometry->addPrimitiveSet (primitives); + + geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); + + geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setMode (GL_BLEND, osg::StateAttribute::ON); + geometry->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + + mBaseNode->addChild (geometry); + mParentNode->addChild(mBaseNode); + } + bool InstanceSelectionMode::createContextMenu(QMenu* menu) { if (menu) diff --git a/apps/opencs/view/render/instanceselectionmode.hpp b/apps/opencs/view/render/instanceselectionmode.hpp index a23811671..81795d5d3 100644 --- a/apps/opencs/view/render/instanceselectionmode.hpp +++ b/apps/opencs/view/render/instanceselectionmode.hpp @@ -1,7 +1,13 @@ #ifndef CSV_RENDER_INSTANCE_SELECTION_MODE_H #define CSV_RENDER_INSTANCE_SELECTION_MODE_H +#include + +#include +#include + #include "selectionmode.hpp" +#include "instancedragmodes.hpp" namespace CSVRender { @@ -11,8 +17,25 @@ namespace CSVRender public: - InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget); + InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, osg::Group *cellNode); + + ~InstanceSelectionMode(); + + /// Store the worldspace-coordinate when drag begins + void setDragStart(const osg::Vec3d& dragStart); + /// Store the worldspace-coordinate when drag begins + const osg::Vec3d& getDragStart(); + + /// Store the screen-coordinate when drag begins + void setScreenDragStart(const QPoint& dragStartPoint); + + /// Apply instance selection changes + void dragEnded(const osg::Vec3d& dragEndPoint, DragMode dragMode); + + void drawSelectionCubeCentre(const osg::Vec3f& mousePlanePoint ); + void drawSelectionCubeCorner(const osg::Vec3f& mousePlanePoint ); + void drawSelectionSphere(const osg::Vec3f& mousePlanePoint ); protected: /// Add context menu items to \a menu. @@ -25,8 +48,15 @@ namespace CSVRender private: + void drawSelectionBox(const osg::Vec3d& pointA, const osg::Vec3d& pointB); + void drawSelectionCube(const osg::Vec3d& point, float radius); + void drawSelectionSphere(const osg::Vec3d& point, float radius); + QAction* mDeleteSelection; QAction* mSelectSame; + osg::Vec3d mDragStart; + osg::Group* mParentNode; + osg::ref_ptr mBaseNode; private slots: diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index dca5549af..ed3558422 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -768,6 +768,22 @@ void CSVRender::PagedWorldspaceWidget::selectAllWithSameParentId (int elementMas flagAsModified(); } +void CSVRender::PagedWorldspaceWidget::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) +{ + for (auto& cell : mCells) + { + cell.second->selectInsideCube (pointA, pointB, dragMode); + } +} + +void CSVRender::PagedWorldspaceWidget::selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) +{ + for (auto& cell : mCells) + { + cell.second->selectWithinDistance (point, distance, dragMode); + } +} + std::string CSVRender::PagedWorldspaceWidget::getCellId (const osg::Vec3f& point) const { CSMWorld::CellCoordinates cellCoordinates ( diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index d17670cfa..beab0c575 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -7,6 +7,7 @@ #include "worldspacewidget.hpp" #include "cell.hpp" +#include "instancedragmodes.hpp" namespace CSVWidget { @@ -120,6 +121,10 @@ namespace CSVRender /// \param elementMask Elements to be affected by the select operation void selectAllWithSameParentId (int elementMask) override; + void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) override; + + void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) override; + std::string getCellId (const osg::Vec3f& point) const override; Cell* getCell(const osg::Vec3d& point) const override; diff --git a/apps/opencs/view/render/selectionmode.cpp b/apps/opencs/view/render/selectionmode.cpp index b5ccda5ad..e7e7d47b5 100644 --- a/apps/opencs/view/render/selectionmode.cpp +++ b/apps/opencs/view/render/selectionmode.cpp @@ -15,30 +15,27 @@ namespace CSVRender { addButton(":scenetoolbar/selection-mode-cube", "cube-centre", "Centred cube" - "

"); addButton(":scenetoolbar/selection-mode-cube-corner", "cube-corner", "Cube corner to corner" - ""); addButton(":scenetoolbar/selection-mode-cube-sphere", "sphere", "Centred sphere" - ""); mSelectAll = new QAction("Select all", this); mDeselectAll = new QAction("Clear selection", this); diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index b1088aa60..c4fef45dd 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -140,6 +140,16 @@ void CSVRender::UnpagedWorldspaceWidget::selectAllWithSameParentId (int elementM flagAsModified(); } +void CSVRender::UnpagedWorldspaceWidget::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) +{ + mCell->selectInsideCube (pointA, pointB, dragMode); +} + +void CSVRender::UnpagedWorldspaceWidget::selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) +{ + mCell->selectWithinDistance (point, distance, dragMode); +} + std::string CSVRender::UnpagedWorldspaceWidget::getCellId (const osg::Vec3f& point) const { return mCellId; diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index eec1b01f3..83233c327 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -60,6 +60,10 @@ namespace CSVRender /// \param elementMask Elements to be affected by the select operation void selectAllWithSameParentId (int elementMask) override; + void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) override; + + void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) override; + std::string getCellId (const osg::Vec3f& point) const override; Cell* getCell(const osg::Vec3d& point) const override; diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 3b8cf70c2..5e224b380 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -7,6 +7,7 @@ #include "../../model/doc/document.hpp" #include "../../model/world/tablemimedata.hpp" +#include "instancedragmodes.hpp" #include "scenewidget.hpp" #include "mask.hpp" @@ -160,6 +161,10 @@ namespace CSVRender /// \param elementMask Elements to be affected by the select operation virtual void selectAllWithSameParentId (int elementMask) = 0; + virtual void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) = 0; + + virtual void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) = 0; + /// Return the next intersection with scene elements matched by /// \a interactionMask based on \a localPos and the camera vector. /// If there is no such intersection, instead a point "in front" of \a localPos will be From 7b54415c40a184b2cd4c41e726f63d61ad3ab41a Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Jan 2021 21:38:56 +0100 Subject: [PATCH 047/171] Fix reference binding to null /usr/include/c++/10.2.0/bits/stl_vector.h:1046:34: runtime error: reference binding to null pointer of type 'value_type' #0 0x55e37f50008a in std::vector >::operator[](unsigned long) /usr/include/c++/10.2.0/bits/stl_vector.h:1046 #1 0x55e37f50008a in ESM::SavedGame::load(ESM::ESMReader&) /home/elsid/dev/openmw/components/esm/savedgame.cpp:28 #2 0x55e37e726139 in MWState::Character::addSlot(boost::filesystem::path const&, std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/apps/openmw/mwstate/character.cpp:31 #3 0x55e37e742b39 in MWState::Character::Character(boost::filesystem::path const&, std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/apps/openmw/mwstate/character.cpp:88 #4 0x55e37e7006e1 in MWState::CharacterManager::CharacterManager(boost::filesystem::path const&, std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/apps/openmw/mwstate/charactermanager.cpp:25 #5 0x55e37e6d4140 in MWState::StateManager::StateManager(boost::filesystem::path const&, std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/apps/openmw/mwstate/statemanagerimp.cpp:90 #6 0x55e37e82595a in OMW::Engine::prepareEngine(Settings::Manager&) /home/elsid/dev/openmw/apps/openmw/engine.cpp:641 #7 0x55e37e8439fd in OMW::Engine::go() /home/elsid/dev/openmw/apps/openmw/engine.cpp:867 #8 0x55e37e782760 in runApplication(int, char**) /home/elsid/dev/openmw/apps/openmw/main.cpp:289 #9 0x55e37f6483c3 in wrapApplication(int (*)(int, char**), int, char**, std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/components/debug/debugging.cpp:200 #10 0x55e37ba8e3fe in main /home/elsid/dev/openmw/apps/openmw/main.cpp:301 #11 0x7f013e845151 in __libc_start_main (/usr/lib/libc.so.6+0x28151) #12 0x55e37baa0e3d in _start (/home/elsid/dev/openmw/build/gcc/ubsan/openmw+0x6c11e3d) --- components/esm/savedgame.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index 9edcb1a67..7cb30f2dd 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -25,7 +25,7 @@ void ESM::SavedGame::load (ESMReader &esm) esm.getSubNameIs("SCRN"); esm.getSubHeader(); mScreenshot.resize(esm.getSubSize()); - esm.getExact(&mScreenshot[0], mScreenshot.size()); + esm.getExact(mScreenshot.data(), mScreenshot.size()); } void ESM::SavedGame::save (ESMWriter &esm) const From d2d8a7a940b47660f7bd51cc065fbf13119c0179 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Jan 2021 21:58:27 +0100 Subject: [PATCH 048/171] Fix passing null to memcpy /home/elsid/dev/openmw/components/detournavigator/navmeshtilescache.cpp:36:24: runtime error: null pointer passed as argument 2, which is declared to never be null #0 0x55e37ba4cda5 in makeNavMeshKey /home/elsid/dev/openmw/components/detournavigator/navmeshtilescache.cpp:36 #1 0x55e37ba4cda5 in DetourNavigator::NavMeshTilesCache::set(osg::Vec3f const&, osg::Vec2i const&, DetourNavigator::RecastMesh const&, std::vector > const&, DetourNavigator::NavMeshData&&) /home/elsid/dev/openmw/components/detournavigator/navmeshtilescache.cpp:81 #2 0x55e37fe3c861 in DetourNavigator::updateNavMesh(osg::Vec3f const&, DetourNavigator::RecastMesh const*, osg::Vec2i const&, osg::Vec2i const&, std::vector > const&, DetourNavigator::Settings const&, std::shared_ptr > const&, DetourNavigator::NavMeshTilesCache&) /home/elsid/dev/openmw/components/detournavigator/makenavmesh.cpp:582 #3 0x55e37fb796ce in DetourNavigator::AsyncNavMeshUpdater::processJob(DetourNavigator::AsyncNavMeshUpdater::Job const&) /home/elsid/dev/openmw/components/detournavigator/asyncnavmeshupdater.cpp:178 #4 0x55e37fb9a125 in DetourNavigator::AsyncNavMeshUpdater::process() /home/elsid/dev/openmw/components/detournavigator/asyncnavmeshupdater.cpp:144 #5 0x7f013f585c23 in execute_native_thread_routine /build/gcc/src/gcc/libstdc++-v3/src/c++11/thread.cc:80 #6 0x7f013f8c63e8 in start_thread (/usr/lib/libpthread.so.0+0x93e8) #7 0x7f013e91d292 in __GI___clone (/usr/lib/libc.so.6+0x100292) --- .../detournavigator/navmeshtilescache.cpp | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp index f554cd414..cff93ac0e 100644 --- a/components/detournavigator/navmeshtilescache.cpp +++ b/components/detournavigator/navmeshtilescache.cpp @@ -21,19 +21,32 @@ namespace DetourNavigator std::vector result(indicesSize + verticesSize + areaTypesSize + waterSize + offMeshConnectionsSize); unsigned char* dst = result.data(); - std::memcpy(dst, recastMesh.getIndices().data(), indicesSize); - dst += indicesSize; + if (indicesSize > 0) + { + std::memcpy(dst, recastMesh.getIndices().data(), indicesSize); + dst += indicesSize; + } - std::memcpy(dst, recastMesh.getVertices().data(), verticesSize); - dst += verticesSize; + if (verticesSize > 0) + { + std::memcpy(dst, recastMesh.getVertices().data(), verticesSize); + dst += verticesSize; + } - std::memcpy(dst, recastMesh.getAreaTypes().data(), areaTypesSize); - dst += areaTypesSize; + if (areaTypesSize > 0) + { + std::memcpy(dst, recastMesh.getAreaTypes().data(), areaTypesSize); + dst += areaTypesSize; + } - std::memcpy(dst, recastMesh.getWater().data(), waterSize); - dst += waterSize; + if (waterSize > 0) + { + std::memcpy(dst, recastMesh.getWater().data(), waterSize); + dst += waterSize; + } - std::memcpy(dst, offMeshConnections.data(), offMeshConnectionsSize); + if (offMeshConnectionsSize > 0) + std::memcpy(dst, offMeshConnections.data(), offMeshConnectionsSize); return result; } From 5f1d3e0e2fb2c209d7ad23c6c53d69d4258e6788 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sat, 9 Jan 2021 22:20:57 +0200 Subject: [PATCH 049/171] Use the Topic ID of the cloned target from topicinfos --- apps/opencs/model/world/collection.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 451ef9d0e..f657c67a1 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -15,6 +15,7 @@ #include "columnbase.hpp" #include "collectionbase.hpp" +#include "info.hpp" #include "land.hpp" #include "landtexture.hpp" #include "ref.hpp" @@ -264,6 +265,13 @@ namespace CSMWorld CSMWorld::CellRef* ptr = (CSMWorld::CellRef*) ©.mModified; ptr->mRefNum.mIndex = 0; } + if (type == UniversalId::Type_TopicInfo || type == UniversalId::Type_JournalInfo) + { + CSMWorld::Info* ptr = (CSMWorld::Info*) ©.mModified; + std::vector splitStringContainer; + Misc::StringUtils::split(destination, splitStringContainer, "#"); + ptr->mTopicId = splitStringContainer[0]; + } int index = getAppendIndex(destination, type); insertRecord(copy, getAppendIndex(destination, type)); From 29416269b27434f0e1bf7854f736fb2ada47c9eb Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sun, 10 Jan 2021 01:07:03 +0200 Subject: [PATCH 050/171] Update changelog --- CHANGELOG.md | 1 + CHANGELOG_PR.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee84ae6d7..123936d42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Bug #4055: Local scripts don't inherit variables from their base record Bug #4083: Door animation freezes when colliding with actors Bug #4247: Cannot walk up stairs in Ebonheart docks + Bug #4363: Editor: Defect in Clone Function for Dialogue Info records Bug #4447: Actor collision capsule shape allows looking through some walls Bug #4465: Collision shape overlapping causes twitching Bug #4476: Abot Gondoliers: player hangs in air during scenic travel diff --git a/CHANGELOG_PR.md b/CHANGELOG_PR.md index fdf27dc93..f70fbad57 100644 --- a/CHANGELOG_PR.md +++ b/CHANGELOG_PR.md @@ -35,6 +35,7 @@ Bug Fixes: Editor Bug Fixes: - Deleted and moved objects within a cell are now saved properly (#832) +- Topic and Journal Info records can now be cloned with a different parent Topic/Journal Id (#4363) - Verifier no longer checks for alleged 'race' entries in clothing body parts (#5400) - Loading mods now keeps the master index (#5675) - Flicker and crashing on XFCE4 fixed (#5703) From 3045d20a970afd0da6a63081186e545ca940bfe6 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sun, 10 Jan 2021 01:25:40 +0200 Subject: [PATCH 051/171] Make sure that vector isn't empty, just in case --- apps/opencs/model/world/collection.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index f657c67a1..16ebb3a61 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -270,7 +270,7 @@ namespace CSMWorld CSMWorld::Info* ptr = (CSMWorld::Info*) ©.mModified; std::vector splitStringContainer; Misc::StringUtils::split(destination, splitStringContainer, "#"); - ptr->mTopicId = splitStringContainer[0]; + if (!splitStringContainer.empty()) ptr->mTopicId = splitStringContainer[0]; } int index = getAppendIndex(destination, type); From a257567b8077a0c272eb987569947e4cdac3ad71 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 10 Jan 2021 08:04:06 +0000 Subject: [PATCH 052/171] Don't update magic effects when unequipping items to equip something else --- CHANGELOG.md | 1 + apps/openmw/mwworld/inventorystore.cpp | 9 +++++---- apps/openmw/mwworld/inventorystore.hpp | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa3572282..db1e4042c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,6 +81,7 @@ Bug #5661: Region sounds don't play at the right interval Bug #5675: OpenMW-cs. FRMR subrecords are saved with the wrong MastIdx Bug #5681: Player character can clip or pass through bridges instead of colliding against them + Bug #5687: Bound items covering the same inventory slot expiring at the same time freezes the game Bug #5688: Water shader broken indoors with enable indoor shadows = false Bug #5695: ExplodeSpell for actors doesn't target the ground Bug #5703: OpenMW-CS menu system crashing on XFCE diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 38007c1cb..c9b1d8bc1 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -762,7 +762,7 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor return retCount; } -MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor, bool fireEvent) +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor, bool applyUpdates) { if (slot<0 || slot>=static_cast (mSlots.size())) throw std::runtime_error ("slot number out of range"); @@ -794,10 +794,11 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c } } - if (fireEvent) + if (applyUpdates) + { fireEquipmentChangedEvent(actor); - - updateMagicEffects(actor); + updateMagicEffects(actor); + } return retval; } diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index df69a5709..bfe0a9992 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -173,7 +173,7 @@ namespace MWWorld /// /// @return the number of items actually removed - ContainerStoreIterator unequipSlot(int slot, const Ptr& actor, bool fireEvent=true); + ContainerStoreIterator unequipSlot(int slot, const Ptr& actor, bool applyUpdates = true); ///< Unequip \a slot. /// /// @return an iterator to the item that was previously in the slot From d015f17a6ce290a0a338db51745dc9a98329cba6 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 25 Dec 2020 18:20:42 +0100 Subject: [PATCH 053/171] Make all physics object manage their own resources Use smart pointer for heightfields and their members. Move collision object addition inside of Object's ctor, as for Actors and HeightFields --- apps/openmw/mwphysics/actor.cpp | 5 ++--- apps/openmw/mwphysics/heightfield.cpp | 25 +++++++++++++------------ apps/openmw/mwphysics/heightfield.hpp | 11 ++++++++--- apps/openmw/mwphysics/object.cpp | 12 ++++++------ apps/openmw/mwphysics/object.hpp | 2 +- apps/openmw/mwphysics/physicssystem.cpp | 24 ++++-------------------- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwphysics/projectile.cpp | 11 ++++------- 8 files changed, 39 insertions(+), 53 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 3b7b0d061..3b52ee934 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -58,7 +58,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic mConvexShape = static_cast(mShape.get()); - mCollisionObject.reset(new btCollisionObject); + mCollisionObject = std::make_unique(); mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); mCollisionObject->setActivationState(DISABLE_DEACTIVATION); mCollisionObject->setCollisionShape(mShape.get()); @@ -76,8 +76,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic Actor::~Actor() { - if (mCollisionObject) - mTaskScheduler->removeCollisionObject(mCollisionObject.get()); + mTaskScheduler->removeCollisionObject(mCollisionObject.get()); } void Actor::enableCollisionMode(bool collision) diff --git a/apps/openmw/mwphysics/heightfield.cpp b/apps/openmw/mwphysics/heightfield.cpp index e1448116b..436cdfe8f 100644 --- a/apps/openmw/mwphysics/heightfield.cpp +++ b/apps/openmw/mwphysics/heightfield.cpp @@ -1,4 +1,5 @@ #include "heightfield.hpp" +#include "mtphysics.hpp" #include @@ -42,10 +43,12 @@ namespace namespace MWPhysics { - HeightField::HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject) - : mHeights(makeHeights(heights, sqrtVerts)) + HeightField::HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject, PhysicsTaskScheduler* scheduler) + : mHoldObject(holdObject) + , mHeights(makeHeights(heights, sqrtVerts)) + , mTaskScheduler(scheduler) { - mShape = new btHeightfieldTerrainShape( + mShape = std::make_unique( sqrtVerts, sqrtVerts, getHeights(heights, mHeights), 1, @@ -60,31 +63,29 @@ namespace MWPhysics (y+0.5f) * triSize * (sqrtVerts-1), (maxH+minH)*0.5f)); - mCollisionObject = new btCollisionObject; - mCollisionObject->setCollisionShape(mShape); + mCollisionObject = std::make_unique(); + mCollisionObject->setCollisionShape(mShape.get()); mCollisionObject->setWorldTransform(transform); - - mHoldObject = holdObject; + mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_HeightMap, CollisionType_Actor|CollisionType_Projectile); } HeightField::~HeightField() { - delete mCollisionObject; - delete mShape; + mTaskScheduler->removeCollisionObject(mCollisionObject.get()); } btCollisionObject* HeightField::getCollisionObject() { - return mCollisionObject; + return mCollisionObject.get(); } const btCollisionObject* HeightField::getCollisionObject() const { - return mCollisionObject; + return mCollisionObject.get(); } const btHeightfieldTerrainShape* HeightField::getShape() const { - return mShape; + return mShape.get(); } } diff --git a/apps/openmw/mwphysics/heightfield.hpp b/apps/openmw/mwphysics/heightfield.hpp index 2ba58afff..c76f8b943 100644 --- a/apps/openmw/mwphysics/heightfield.hpp +++ b/apps/openmw/mwphysics/heightfield.hpp @@ -5,6 +5,7 @@ #include +#include #include class btCollisionObject; @@ -17,10 +18,12 @@ namespace osg namespace MWPhysics { + class PhysicsTaskScheduler; + class HeightField { public: - HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject); + HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject, PhysicsTaskScheduler* scheduler); ~HeightField(); btCollisionObject* getCollisionObject(); @@ -28,11 +31,13 @@ namespace MWPhysics const btHeightfieldTerrainShape* getShape() const; private: - btHeightfieldTerrainShape* mShape; - btCollisionObject* mCollisionObject; + std::unique_ptr mShape; + std::unique_ptr mCollisionObject; osg::ref_ptr mHoldObject; std::vector mHeights; + PhysicsTaskScheduler* mTaskScheduler; + void operator=(const HeightField&); HeightField(const HeightField&); }; diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 910f7bf15..e3615989d 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -14,29 +14,29 @@ namespace MWPhysics { - Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, PhysicsTaskScheduler* scheduler) + Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler) : mShapeInstance(shapeInstance) , mSolid(true) , mTaskScheduler(scheduler) { mPtr = ptr; - mCollisionObject.reset(new btCollisionObject); + mCollisionObject = std::make_unique(); mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape()); mCollisionObject->setUserPointer(this); setScale(ptr.getCellRef().getScale()); setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); - const float* pos = ptr.getRefData().getPosition().pos; - setOrigin(btVector3(pos[0], pos[1], pos[2])); + setOrigin(Misc::Convert::toBullet(ptr.getRefData().getPosition().asVec3())); commitPositionChange(); + + mTaskScheduler->addCollisionObject(mCollisionObject.get(), collisionType, CollisionType_Actor|CollisionType_HeightMap|CollisionType_Projectile); } Object::~Object() { - if (mCollisionObject) - mTaskScheduler->removeCollisionObject(mCollisionObject.get()); + mTaskScheduler->removeCollisionObject(mCollisionObject.get()); } const Resource::BulletShapeInstance* Object::getShapeInstance() const diff --git a/apps/openmw/mwphysics/object.hpp b/apps/openmw/mwphysics/object.hpp index 876e35651..cae877180 100644 --- a/apps/openmw/mwphysics/object.hpp +++ b/apps/openmw/mwphysics/object.hpp @@ -26,7 +26,7 @@ namespace MWPhysics class Object final : public PtrHolder { public: - Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, PhysicsTaskScheduler* scheduler); + Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler); ~Object() override; const Resource::BulletShapeInstance* getShapeInstance() const; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index b50366ade..95d1ec70a 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -107,12 +107,7 @@ namespace MWPhysics if (mWaterCollisionObject) mTaskScheduler->removeCollisionObject(mWaterCollisionObject.get()); - for (auto& heightField : mHeightFields) - { - mTaskScheduler->removeCollisionObject(heightField.second->getCollisionObject()); - delete heightField.second; - } - + mHeightFields.clear(); mObjects.clear(); mActors.clear(); mProjectiles.clear(); @@ -442,22 +437,14 @@ namespace MWPhysics void PhysicsSystem::addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject) { - HeightField *heightfield = new HeightField(heights, x, y, triSize, sqrtVerts, minH, maxH, holdObject); - mHeightFields[std::make_pair(x,y)] = heightfield; - - mTaskScheduler->addCollisionObject(heightfield->getCollisionObject(), CollisionType_HeightMap, - CollisionType_Actor|CollisionType_Projectile); + mHeightFields[std::make_pair(x,y)] = std::make_unique(heights, x, y, triSize, sqrtVerts, minH, maxH, holdObject, mTaskScheduler.get()); } void PhysicsSystem::removeHeightField (int x, int y) { HeightFieldMap::iterator heightfield = mHeightFields.find(std::make_pair(x,y)); if(heightfield != mHeightFields.end()) - { - mTaskScheduler->removeCollisionObject(heightfield->second->getCollisionObject()); - delete heightfield->second; mHeightFields.erase(heightfield); - } } const HeightField* PhysicsSystem::getHeightField(int x, int y) const @@ -465,7 +452,7 @@ namespace MWPhysics const auto heightField = mHeightFields.find(std::make_pair(x, y)); if (heightField == mHeightFields.end()) return nullptr; - return heightField->second; + return heightField->second.get(); } void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType) @@ -474,14 +461,11 @@ namespace MWPhysics if (!shapeInstance || !shapeInstance->getCollisionShape()) return; - auto obj = std::make_shared(ptr, shapeInstance, mTaskScheduler.get()); + auto obj = std::make_shared(ptr, shapeInstance, collisionType, mTaskScheduler.get()); mObjects.emplace(ptr, obj); if (obj->isAnimated()) mAnimatedObjects.insert(obj.get()); - - mTaskScheduler->addCollisionObject(obj->getCollisionObject(), collisionType, - CollisionType_Actor|CollisionType_HeightMap|CollisionType_Projectile); } void PhysicsSystem::remove(const MWWorld::Ptr &ptr) diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 2de8d153b..449209974 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -274,7 +274,7 @@ namespace MWPhysics using ProjectileMap = std::map>; ProjectileMap mProjectiles; - using HeightFieldMap = std::map, HeightField *>; + using HeightFieldMap = std::map, std::unique_ptr>; HeightFieldMap mHeightFields; bool mDebugDrawEnabled; diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index a8aaeb72a..1b9beca5f 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -28,7 +28,7 @@ Projectile::Projectile(int projectileId, const MWWorld::Ptr& caster, const osg:: mShape.reset(new btSphereShape(1.f)); mConvexShape = static_cast(mShape.get()); - mCollisionObject.reset(new btCollisionObject); + mCollisionObject = std::make_unique(); mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); mCollisionObject->setActivationState(DISABLE_DEACTIVATION); mCollisionObject->setCollisionShape(mShape.get()); @@ -45,12 +45,9 @@ Projectile::Projectile(int projectileId, const MWWorld::Ptr& caster, const osg:: Projectile::~Projectile() { - if (mCollisionObject) - { - if (!mActive) - mPhysics->reportCollision(mHitPosition, mHitNormal); - mTaskScheduler->removeCollisionObject(mCollisionObject.get()); - } + if (!mActive) + mPhysics->reportCollision(mHitPosition, mHitNormal); + mTaskScheduler->removeCollisionObject(mCollisionObject.get()); } void Projectile::commitPositionChange() From 1ab4683dcecb4947b81dcf6606407b6c46a1eacb Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 28 Dec 2020 19:19:07 +0100 Subject: [PATCH 054/171] Tweak follow distance to be more like the original --- apps/openmw/mwmechanics/aifollow.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index f31be9c10..ec2367997 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -14,6 +14,16 @@ #include "movement.hpp" #include "steering.hpp" +namespace +{ +osg::Vec3f::value_type getHalfExtents(const MWWorld::ConstPtr& actor) +{ + if(actor.getClass().isNpc()) + return 64; + return MWBase::Environment::get().getWorld()->getHalfExtents(actor).y(); +} +} + namespace MWMechanics { int AiFollow::mFollowIndexCounter = 0; @@ -121,13 +131,14 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte { for(auto& follower : followers) { - auto halfExtent = MWBase::Environment::get().getWorld()->getHalfExtents(follower.second).y(); + auto halfExtent = getHalfExtents(follower.second); if(halfExtent > floatingDistance) floatingDistance = halfExtent; } + floatingDistance += 128; } - floatingDistance += MWBase::Environment::get().getWorld()->getHalfExtents(target).y(); - floatingDistance += MWBase::Environment::get().getWorld()->getHalfExtents(actor).y() * 2; + floatingDistance += getHalfExtents(target) + 64; + floatingDistance += getHalfExtents(actor) * 2; short followDistance = static_cast(floatingDistance); if (!mAlwaysFollow) //Update if you only follow for a bit From 14dd11372f3e76a7289d929387ef38c06f0fbc8d Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 5 Jan 2021 22:20:09 +0100 Subject: [PATCH 055/171] Utility functions for little-endian <-> big-endian conversion. --- components/misc/endianness.hpp | 82 ++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 components/misc/endianness.hpp diff --git a/components/misc/endianness.hpp b/components/misc/endianness.hpp new file mode 100644 index 000000000..1b43e584e --- /dev/null +++ b/components/misc/endianness.hpp @@ -0,0 +1,82 @@ +#ifndef COMPONENTS_MISC_ENDIANNESS_H +#define COMPONENTS_MISC_ENDIANNESS_H + +#include + +namespace Misc +{ + + // Two-way conversion little-endian <-> big-endian + template + void swapEndiannessInplace(T& v) + { + static_assert(std::is_arithmetic_v); + static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8); + + if constexpr (sizeof(T) == 2) + { + uint16_t& v16 = *reinterpret_cast(&v); + v16 = (v16 >> 8) | (v16 << 8); + } + if constexpr (sizeof(T) == 4) + { + uint32_t& v32 = *reinterpret_cast(&v); + v32 = (v32 >> 24) | ((v32 >> 8) & 0xff00) | ((v32 & 0xff00) << 8) || v32 << 24; + } + if constexpr (sizeof(T) == 8) + { + uint64_t& v64 = *reinterpret_cast(&v); + v64 = (v64 >> 56) | ((v64 & 0x00ff'0000'0000'0000) >> 40) | ((v64 & 0x0000'ff00'0000'0000) >> 24) + | ((v64 & 0x0000'00ff'0000'0000) >> 8) | ((v64 & 0x0000'0000'ff00'0000) << 8) + | ((v64 & 0x0000'0000'00ff'0000) << 24) | ((v64 & 0x0000'0000'0000'ff00) << 40) | (v64 << 56); + } + } + + #ifdef _WIN32 + constexpr bool IS_LITTLE_ENDIAN = true; + constexpr bool IS_BIG_ENDIAN = false; + #else + constexpr bool IS_LITTLE_ENDIAN = __BYTE_ORDER__ != __ORDER_BIG_ENDIAN__; + constexpr bool IS_BIG_ENDIAN = __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__; + #endif + + // Usage: swapEndiannessInplaceIf(v) - native to little-endian or back + // swapEndiannessInplaceIf(v) - native to big-endian or back + template + void swapEndiannessInplaceIf(T& v) + { + static_assert(std::is_arithmetic_v); + static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8); + if constexpr (C) + swapEndiannessInplace(v); + } + + template + T toLittleEndian(T v) + { + swapEndiannessInplaceIf(v); + return v; + } + template + T fromLittleEndian(T v) + { + swapEndiannessInplaceIf(v); + return v; + } + + template + T toBigEndian(T v) + { + swapEndiannessInplaceIf(v); + return v; + } + template + T fromBigEndian(T v) + { + swapEndiannessInplaceIf(v); + return v; + } + +} + +#endif // COMPONENTS_MISC_ENDIANNESS_H From eaaa2f4a1c4daff1e035208eda662476449a4a1f Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 8 Jan 2021 23:02:19 +0100 Subject: [PATCH 056/171] Use misc/endianness.hpp in components/nif/nifstream --- components/nif/nifstream.cpp | 2 +- components/nif/nifstream.hpp | 98 +++++++++++++----------------------- 2 files changed, 35 insertions(+), 65 deletions(-) diff --git a/components/nif/nifstream.cpp b/components/nif/nifstream.cpp index 69f1a905b..07c9c917c 100644 --- a/components/nif/nifstream.cpp +++ b/components/nif/nifstream.cpp @@ -7,7 +7,7 @@ namespace Nif osg::Quat NIFStream::getQuaternion() { float f[4]; - readLittleEndianBufferOfType<4, float,uint32_t>(inp, (float*)&f); + readLittleEndianBufferOfType<4, float>(inp, (float*)&f); osg::Quat quat; quat.w() = f[0]; quat.x() = f[1]; diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index 97075c288..4d221b867 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -22,61 +23,30 @@ namespace Nif class NIFFile; /* - readLittleEndianBufferOfType: This template should only be used with non POD data types + readLittleEndianBufferOfType: This template should only be used with arithmetic types */ -template inline void readLittleEndianBufferOfType(Files::IStreamPtr &pIStream, T* dest) +template inline void readLittleEndianBufferOfType(Files::IStreamPtr &pIStream, T* dest) { -#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86) pIStream->read((char*)dest, numInstances * sizeof(T)); -#else - uint8_t* destByteBuffer = (uint8_t*)dest; - pIStream->read((char*)dest, numInstances * sizeof(T)); - /* - Due to the loop iterations being known at compile time, - this nested loop will most likely be unrolled - For example, for 2 instances of a 4 byte data type, you should get the below result - */ - union { - IntegerT i; - T t; - } u; - for (uint32_t i = 0; i < numInstances; i++) - { - u = { 0 }; - for (uint32_t byte = 0; byte < sizeof(T); byte++) - u.i |= (((IntegerT)destByteBuffer[i * sizeof(T) + byte]) << (byte * 8)); - dest[i] = u.t; - } -#endif + if constexpr (Misc::IS_BIG_ENDIAN) + for (uint32_t i = 0; i < numInstances; i++) + Misc::swapEndiannessInplace(dest[i]); } /* - readLittleEndianDynamicBufferOfType: This template should only be used with non POD data types + readLittleEndianDynamicBufferOfType: This template should only be used with arithmetic types */ -template inline void readLittleEndianDynamicBufferOfType(Files::IStreamPtr &pIStream, T* dest, uint32_t numInstances) +template inline void readLittleEndianDynamicBufferOfType(Files::IStreamPtr &pIStream, T* dest, uint32_t numInstances) { -#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86) pIStream->read((char*)dest, numInstances * sizeof(T)); -#else - uint8_t* destByteBuffer = (uint8_t*)dest; - pIStream->read((char*)dest, numInstances * sizeof(T)); - union { - IntegerT i; - T t; - } u; - for (uint32_t i = 0; i < numInstances; i++) - { - u.i = 0; - for (uint32_t byte = 0; byte < sizeof(T); byte++) - u.i |= ((IntegerT)destByteBuffer[i * sizeof(T) + byte]) << (byte * 8); - dest[i] = u.t; - } -#endif + if constexpr (Misc::IS_BIG_ENDIAN) + for (uint32_t i = 0; i < numInstances; i++) + Misc::swapEndiannessInplace(dest[i]); } -template type inline readLittleEndianType(Files::IStreamPtr &pIStream) +template type inline readLittleEndianType(Files::IStreamPtr &pIStream) { type val; - readLittleEndianBufferOfType<1,type,IntegerT>(pIStream, (type*)&val); + readLittleEndianBufferOfType<1, type>(pIStream, (type*)&val); return val; } @@ -95,59 +65,59 @@ public: char getChar() { - return readLittleEndianType(inp); + return readLittleEndianType(inp); } short getShort() { - return readLittleEndianType(inp); + return readLittleEndianType(inp); } unsigned short getUShort() { - return readLittleEndianType(inp); + return readLittleEndianType(inp); } int getInt() { - return readLittleEndianType(inp); + return readLittleEndianType(inp); } unsigned int getUInt() { - return readLittleEndianType(inp); + return readLittleEndianType(inp); } float getFloat() { - return readLittleEndianType(inp); + return readLittleEndianType(inp); } osg::Vec2f getVector2() { osg::Vec2f vec; - readLittleEndianBufferOfType<2,float,uint32_t>(inp, (float*)&vec._v[0]); + readLittleEndianBufferOfType<2,float>(inp, (float*)&vec._v[0]); return vec; } osg::Vec3f getVector3() { osg::Vec3f vec; - readLittleEndianBufferOfType<3, float,uint32_t>(inp, (float*)&vec._v[0]); + readLittleEndianBufferOfType<3, float>(inp, (float*)&vec._v[0]); return vec; } osg::Vec4f getVector4() { osg::Vec4f vec; - readLittleEndianBufferOfType<4, float,uint32_t>(inp, (float*)&vec._v[0]); + readLittleEndianBufferOfType<4, float>(inp, (float*)&vec._v[0]); return vec; } Matrix3 getMatrix3() { Matrix3 mat; - readLittleEndianBufferOfType<9, float,uint32_t>(inp, (float*)&mat.mValues); + readLittleEndianBufferOfType<9, float>(inp, (float*)&mat.mValues); return mat; } @@ -181,14 +151,14 @@ public: ///Read in a string of the length specified in the file std::string getSizedString() { - size_t size = readLittleEndianType(inp); + size_t size = readLittleEndianType(inp); return getSizedString(size); } ///Specific to Bethesda headers, uses a byte for length std::string getExportString() { - size_t size = static_cast(readLittleEndianType(inp)); + size_t size = static_cast(readLittleEndianType(inp)); return getSizedString(size); } @@ -203,58 +173,58 @@ public: void getChars(std::vector &vec, size_t size) { vec.resize(size); - readLittleEndianDynamicBufferOfType(inp, vec.data(), size); + readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getUChars(std::vector &vec, size_t size) { vec.resize(size); - readLittleEndianDynamicBufferOfType(inp, vec.data(), size); + readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getUShorts(std::vector &vec, size_t size) { vec.resize(size); - readLittleEndianDynamicBufferOfType(inp, vec.data(), size); + readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getFloats(std::vector &vec, size_t size) { vec.resize(size); - readLittleEndianDynamicBufferOfType(inp, vec.data(), size); + readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getInts(std::vector &vec, size_t size) { vec.resize(size); - readLittleEndianDynamicBufferOfType(inp, vec.data(), size); + readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getUInts(std::vector &vec, size_t size) { vec.resize(size); - readLittleEndianDynamicBufferOfType(inp, vec.data(), size); + readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getVector2s(std::vector &vec, size_t size) { vec.resize(size); /* The packed storage of each Vec2f is 2 floats exactly */ - readLittleEndianDynamicBufferOfType(inp,(float*)vec.data(), size*2); + readLittleEndianDynamicBufferOfType(inp,(float*)vec.data(), size*2); } void getVector3s(std::vector &vec, size_t size) { vec.resize(size); /* The packed storage of each Vec3f is 3 floats exactly */ - readLittleEndianDynamicBufferOfType(inp, (float*)vec.data(), size*3); + readLittleEndianDynamicBufferOfType(inp, (float*)vec.data(), size*3); } void getVector4s(std::vector &vec, size_t size) { vec.resize(size); /* The packed storage of each Vec4f is 4 floats exactly */ - readLittleEndianDynamicBufferOfType(inp, (float*)vec.data(), size*4); + readLittleEndianDynamicBufferOfType(inp, (float*)vec.data(), size*4); } void getQuaternions(std::vector &quat, size_t size) From 7196ad74557d8c608a9401273a51220fd786f8db Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sun, 10 Jan 2021 21:23:52 +0200 Subject: [PATCH 057/171] Implement an override-value when cloning, use when cloning info records --- apps/opencs/model/world/collection.hpp | 8 -------- apps/opencs/model/world/commands.cpp | 9 +++++++++ apps/opencs/model/world/commands.hpp | 3 +++ apps/opencs/view/world/infocreator.cpp | 4 ++++ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 16ebb3a61..451ef9d0e 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -15,7 +15,6 @@ #include "columnbase.hpp" #include "collectionbase.hpp" -#include "info.hpp" #include "land.hpp" #include "landtexture.hpp" #include "ref.hpp" @@ -265,13 +264,6 @@ namespace CSMWorld CSMWorld::CellRef* ptr = (CSMWorld::CellRef*) ©.mModified; ptr->mRefNum.mIndex = 0; } - if (type == UniversalId::Type_TopicInfo || type == UniversalId::Type_JournalInfo) - { - CSMWorld::Info* ptr = (CSMWorld::Info*) ©.mModified; - std::vector splitStringContainer; - Misc::StringUtils::split(destination, splitStringContainer, "#"); - if (!splitStringContainer.empty()) ptr->mTopicId = splitStringContainer[0]; - } int index = getAppendIndex(destination, type); insertRecord(copy, getAppendIndex(destination, type)); diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index e33be1139..34485a46d 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -397,6 +397,10 @@ void CSMWorld::CloneCommand::redo() { mModel.cloneRecord (mIdOrigin, mId, mType); applyModifications(); + for (auto& value : mOverrideValues) + { + mModel.setData(mModel.getModelIndex (mId, value.first), value.second); + } } void CSMWorld::CloneCommand::undo() @@ -404,6 +408,11 @@ void CSMWorld::CloneCommand::undo() mModel.removeRow (mModel.getModelIndex (mId, 0).row()); } +void CSMWorld::CloneCommand::setOverrideValue(int column, QVariant value) +{ + mOverrideValues.emplace_back(std::make_pair(column, value)); +} + CSMWorld::CreatePathgridCommand::CreatePathgridCommand(IdTable& model, const std::string& id, QUndoCommand *parent) : CreateCommand(model, id, parent) { diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index 5776cae36..33608304f 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -183,6 +183,7 @@ namespace CSMWorld class CloneCommand : public CreateCommand { std::string mIdOrigin; + std::vector> mOverrideValues; public: @@ -194,6 +195,8 @@ namespace CSMWorld void redo() override; void undo() override; + + void setOverrideValue(int column, QVariant value); }; class RevertCommand : public QUndoCommand diff --git a/apps/opencs/view/world/infocreator.cpp b/apps/opencs/view/world/infocreator.cpp index 2f1615c87..505de753c 100644 --- a/apps/opencs/view/world/infocreator.cpp +++ b/apps/opencs/view/world/infocreator.cpp @@ -40,10 +40,14 @@ void CSVWorld::InfoCreator::configureCreateCommand (CSMWorld::CreateCommand& com command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Rank), -1); command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Gender), -1); command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_PcRank), -1); + CSMWorld::CloneCommand* cloneCommand = dynamic_cast (&command); + if (cloneCommand) cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text()); } else { command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); + CSMWorld::CloneCommand* cloneCommand = dynamic_cast (&command); + if (cloneCommand) cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); } } From 7be7af13d753d7cdbf7a8fa3b6ef9331a79b063b Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Mon, 11 Jan 2021 06:53:23 +0300 Subject: [PATCH 058/171] Downgrade FOV-dependent view distance factor to a recommendation --- apps/launcher/advancedpage.cpp | 4 +-- apps/openmw/mwrender/fogmanager.cpp | 4 +-- .../reference/modding/settings/camera.rst | 33 +++++++------------ 3 files changed, 15 insertions(+), 26 deletions(-) diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 54fbbdfe0..d82dd1be2 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -62,12 +62,12 @@ namespace double convertToCells(double unitRadius) { - return std::round((unitRadius / 0.93 + 1024) / CellSizeInUnits); + return std::round((unitRadius + 1024) / CellSizeInUnits); } double convertToUnits(double CellGridRadius) { - return (CellSizeInUnits * CellGridRadius - 1024) * 0.93; + return CellSizeInUnits * CellGridRadius - 1024; } } diff --git a/apps/openmw/mwrender/fogmanager.cpp b/apps/openmw/mwrender/fogmanager.cpp index 837e6ad04..b15188292 100644 --- a/apps/openmw/mwrender/fogmanager.cpp +++ b/apps/openmw/mwrender/fogmanager.cpp @@ -76,8 +76,8 @@ namespace MWRender mLandFogStart = viewDistance * (1 - fogDepth); mLandFogEnd = viewDistance; } - mUnderwaterFogStart = std::min(viewDistance, 6666.f) * (1 - underwaterFog); - mUnderwaterFogEnd = std::min(viewDistance, 6666.f); + mUnderwaterFogStart = std::min(viewDistance, 7168.f) * (1 - underwaterFog); + mUnderwaterFogEnd = std::min(viewDistance, 7168.f); } mFogColor = color; } diff --git a/docs/source/reference/modding/settings/camera.rst b/docs/source/reference/modding/settings/camera.rst index c30dcb3f3..e15b06e74 100644 --- a/docs/source/reference/modding/settings/camera.rst +++ b/docs/source/reference/modding/settings/camera.rst @@ -51,39 +51,28 @@ viewing distance This value controls the maximum visible distance (also called the far clipping plane). Larger values significantly improve rendering in exterior spaces, but also increase the amount of rendered geometry and significantly reduce the frame rate. -Note that when cells are visible before loading (when not using a Distant Land), the geometry will "pop-in" suddenly, -creating a jarring visual effect. To prevent this effect, this value must be less than:: +Note that when cells are visible before loading, the geometry will "pop-in" suddenly, +creating a jarring visual effect. To prevent this effect, this value should not be greater than: - (CellSizeInUnits * CellGridRadius - 1024) * 0.93 + CellSizeInUnits * CellGridRadius - 1024 The CellSizeInUnits is the size of a game cell in units (8192 by default), CellGridRadius determines how many neighboring cells to current one to load (1 by default - 3x3 grid), and 1024 is the threshold distance for loading a new cell. -Additionally, the field of view setting also interacts with this setting because the view frustum end is a plane, +The field of view setting also interacts with this setting because the view frustum end is a plane, so you can see further at the edges of the screen than you should be able to. This can be observed in game by looking at distant objects and rotating the camera so the objects are near the edge of the screen. -As a result, this setting should further be reduced by a factor that depends on the field of view setting. -In the default configuration this reduction is 7%, hence the factor of 0.93 above. -Using this factor, approximate values recommended for other CellGridRadius values are: - -======= ======== -Cells Viewing - Distance -======= ======== -2 14285 -3 21903 -4 29522 -5 35924 -======= ======== - -Reductions of up to 25% or more can be required to completely eliminate pop-in for wide fields of view -and long viewing distances near the edges of the screen. +As a result, this distance is recommended to further be reduced to avoid pop-in for wide fields of view +and long viewing distances near the edges of the screen if distant terrain and object paging are not used. + +Reductions of up to 25% or more can be required to completely eliminate this pop-in. Such situations are unusual and probably not worth the performance penalty introduced by loading geometry obscured by fog in the center of the screen. See RenderingManager::configureFog for the relevant source code. -This setting can be adjusted in game from the ridiculously low value of 2048.0 to a maximum of 81920.0 -using the View Distance slider in the Detail tab of the Video panel of the Options menu. +This setting can be adjusted in game from the ridiculously low value of 2500 units to a maximum of 7168 units +using the View Distance slider in the Detail tab of the Video panel of the Options menu, unless distant terrain is enabled, +in which case the maximum is increased to 81920 units. field of view ------------- From 93b1b444f2a3abd13d5105b0bb35c41fc5498cd3 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Mon, 11 Jan 2021 12:53:34 +0200 Subject: [PATCH 059/171] Optimize CreateCommand and CloneCommand configuration --- apps/opencs/view/world/infocreator.cpp | 27 +++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/apps/opencs/view/world/infocreator.cpp b/apps/opencs/view/world/infocreator.cpp index 505de753c..cf1b48a19 100644 --- a/apps/opencs/view/world/infocreator.cpp +++ b/apps/opencs/view/world/infocreator.cpp @@ -34,20 +34,29 @@ void CSVWorld::InfoCreator::configureCreateCommand (CSMWorld::CreateCommand& com { CSMWorld::IdTable& table = dynamic_cast (*getData().getTableModel (getCollectionId())); + CSMWorld::CloneCommand* cloneCommand = dynamic_cast (&command); if (getCollectionId() == CSMWorld::UniversalId::Type_TopicInfos) { - command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text()); - command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Rank), -1); - command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Gender), -1); - command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_PcRank), -1); - CSMWorld::CloneCommand* cloneCommand = dynamic_cast (&command); - if (cloneCommand) cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text()); + if (!cloneCommand) + { + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text()); + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Rank), -1); + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Gender), -1); + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_PcRank), -1); + } + else + { + cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text()); + } } else { - command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); - CSMWorld::CloneCommand* cloneCommand = dynamic_cast (&command); - if (cloneCommand) cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); + if (!cloneCommand) + { + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); + } + else + cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); } } From 3087ce9c7039bb072c7587b723d0a58623e81d75 Mon Sep 17 00:00:00 2001 From: fredzio Date: Mon, 11 Jan 2021 17:56:34 +0100 Subject: [PATCH 060/171] Use saved fallheight to determine a character's jump state. The jump state initial state is "none", and it is set after physics simulation. If a save is done just above the ground, the character may land before the first run of the simulation, effectively cancelling the effect of falling. --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/character.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa3572282..5b2e1b260 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,7 @@ Bug #5703: OpenMW-CS menu system crashing on XFCE Bug #5706: AI sequences stop looping after the saved game is reloaded Bug #5731: Editor: skirts are invisible on characters + Bug #5739: Saving and loading the save a second or two before hitting the ground doesn't count fall damage Bug #5758: Paralyzed actors behavior is inconsistent with vanilla Bug #5762: Movement solver is insufficiently robust Feature #390: 3rd person look "over the shoulder" diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index b9bfe6bc0..53c8e4a8b 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -883,7 +883,11 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim } if(!cls.getCreatureStats(mPtr).isDead()) + { mIdleState = CharState_Idle; + if (cls.getCreatureStats(mPtr).getFallHeight() > 0) + mJumpState = JumpState_InAir; + } else { const MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr); From 70087e16feaeda3bdd8789b209d0f7cdbca75044 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Mon, 11 Jan 2021 13:43:44 +0200 Subject: [PATCH 061/171] Disable dialogue info table sorting --- apps/opencs/view/world/subviews.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 3e72f9a9e..169bc2e94 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -75,10 +75,10 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) new CSVDoc::SubViewFactoryWithCreator); manager.add (CSMWorld::UniversalId::Type_TopicInfos, - new CSVDoc::SubViewFactoryWithCreator); + new CSVDoc::SubViewFactoryWithCreator(false)); manager.add (CSMWorld::UniversalId::Type_JournalInfos, - new CSVDoc::SubViewFactoryWithCreator); + new CSVDoc::SubViewFactoryWithCreator(false)); manager.add (CSMWorld::UniversalId::Type_Pathgrids, new CSVDoc::SubViewFactoryWithCreator); From 390314215225099ef09ef6c10b7365b587d3767b Mon Sep 17 00:00:00 2001 From: psi29a Date: Tue, 12 Jan 2021 12:05:17 +0000 Subject: [PATCH 062/171] Update apps/openmw/mwbase/environment.cpp --- apps/openmw/mwbase/environment.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index edb10d945..d639dd4c6 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -19,7 +19,7 @@ MWBase::Environment *MWBase::Environment::sThis = nullptr; MWBase::Environment::Environment() -: mWorld (nullptr), mSoundManager (nullptr), mScriptManager (nullptr), mWindowManager (nullptr), +: mWorld (nullptr), mSoundManager (nullptr), mScriptManager (nullptr), mWindowManager (nullptr), mResourceSystem (nullptr), mMechanicsManager (nullptr), mDialogueManager (nullptr), mJournal (nullptr), mInputManager (nullptr), mStateManager (nullptr), mFrameDuration (0), mFrameRateLimit(0.f) { From 4638fc36b46c8a0961f84ae5c3a026c59e72a7e5 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Tue, 12 Jan 2021 15:46:19 +0300 Subject: [PATCH 063/171] Allow all creatures to float to the water surface --- apps/openmw/mwmechanics/character.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index abff30bb9..172ddb25c 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2448,7 +2448,7 @@ void CharacterController::update(float duration) } } - if (mFloatToSurface && cls.isActor() && cls.canSwim(mPtr)) + if (mFloatToSurface && cls.isActor()) { if (cls.getCreatureStats(mPtr).isDead() || (!godmode && cls.getCreatureStats(mPtr).isParalyzed())) From 73740013a30cece6cb99f0ea9312bb95dd96d94f Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 12 Jan 2021 19:58:46 +0100 Subject: [PATCH 064/171] mResourceSystem initialization reorder --- apps/openmw/mwbase/environment.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index d639dd4c6..9014fafff 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -19,9 +19,9 @@ MWBase::Environment *MWBase::Environment::sThis = nullptr; MWBase::Environment::Environment() -: mWorld (nullptr), mSoundManager (nullptr), mScriptManager (nullptr), mWindowManager (nullptr), mResourceSystem (nullptr), - mMechanicsManager (nullptr), mDialogueManager (nullptr), mJournal (nullptr), mInputManager (nullptr), mStateManager (nullptr), - mFrameDuration (0), mFrameRateLimit(0.f) +: mWorld (nullptr), mSoundManager (nullptr), mScriptManager (nullptr), mWindowManager (nullptr), + mMechanicsManager (nullptr), mDialogueManager (nullptr), mJournal (nullptr), mInputManager (nullptr), + mStateManager (nullptr), mResourceSystem (nullptr), mFrameDuration (0), mFrameRateLimit(0.f) { assert (!sThis); sThis = this; From 823e7bea382ef028643b114dfb85d49a147b2e6e Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Wed, 13 Jan 2021 02:48:54 +0000 Subject: [PATCH 065/171] Fix MyGUI detection `libfind_pkg_detect` used `pkg_check_modules`, which requires all the given modules to be found. This means it always failed for MyGUI, which passes `MyGUI${MYGUI_STATIC_SUFFIX} MYGUI${MYGUI_STATIC_SUFFIX}` to it. Replaces `pkg_check_modules` with `pkg_search_module`, which finds the first match instead. --- cmake/LibFindMacros.cmake | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmake/LibFindMacros.cmake b/cmake/LibFindMacros.cmake index 2be27c5fc..3044601f6 100644 --- a/cmake/LibFindMacros.cmake +++ b/cmake/LibFindMacros.cmake @@ -19,11 +19,11 @@ macro (libfind_package PREFIX PKG) endmacro() # A simple wrapper to make pkg-config searches a bit easier. -# Works the same as CMake's internal pkg_check_modules but is always quiet. -macro (libfind_pkg_check_modules) +# Works the same as CMake's internal pkg_search_module but is always quiet. +macro (libfind_pkg_search_module) find_package(PkgConfig QUIET) if (PKG_CONFIG_FOUND) - pkg_check_modules(${ARGN} QUIET) + pkg_search_module(${ARGN} QUIET) endif() endmacro() @@ -47,7 +47,7 @@ function (libfind_pkg_detect PREFIX) message(FATAL_ERROR "libfind_pkg_detect requires at least a pkg_config package name to be passed.") endif() # Find library - libfind_pkg_check_modules(${PREFIX}_PKGCONF ${pkgargs}) + libfind_pkg_search_module(${PREFIX}_PKGCONF ${pkgargs}) if (pathargs) find_path(${PREFIX}_INCLUDE_DIR NAMES ${pathargs} HINTS ${${PREFIX}_PKGCONF_INCLUDE_DIRS}) endif() From f175beb3047d97dffabc3caa9ffc57746760060a Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Jan 2021 13:33:46 +0400 Subject: [PATCH 066/171] Define template ref classes in components --- apps/openmw/mwrender/objectpaging.cpp | 13 ++----------- components/resource/scenemanager.cpp | 18 ++++-------------- components/resource/scenemanager.hpp | 25 +++++++++++++++++++++++++ components/sceneutil/serialize.cpp | 1 + 4 files changed, 32 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index a5015f377..d8e856e76 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -249,15 +249,6 @@ namespace MWRender } }; - class TemplateRef : public osg::Object - { - public: - TemplateRef() {} - TemplateRef(const TemplateRef& copy, const osg::CopyOp&) : mObjects(copy.mObjects) {} - META_Object(MWRender, TemplateRef) - std::vector> mObjects; - }; - class RefnumSet : public osg::Object { public: @@ -530,7 +521,7 @@ namespace MWRender osg::ref_ptr group = new osg::Group; osg::ref_ptr mergeGroup = new osg::Group; - osg::ref_ptr templateRefs = new TemplateRef; + osg::ref_ptr templateRefs = new Resource::TemplateMultiRef; osgUtil::StateToCompile stateToCompile(0, nullptr); CopyOp copyop; for (const auto& pair : nodes) @@ -596,7 +587,7 @@ namespace MWRender if (numinstances > 0) { // add a ref to the original template, to hint to the cache that it's still being used and should be kept in cache - templateRefs->mObjects.emplace_back(cnode); + templateRefs->addRef(cnode); if (pair.second.mNeedCompile) { diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 2630cd453..e75fa4f74 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -110,6 +110,10 @@ namespace namespace Resource { + void TemplateMultiRef::addRef(const osg::Node* node) + { + mObjects.emplace_back(node); + } class SharedStateManager : public osgDB::SharedStateManager { @@ -554,20 +558,6 @@ namespace Resource return node; } - class TemplateRef : public osg::Object - { - public: - TemplateRef(const Object* object) - : mObject(object) {} - TemplateRef() {} - TemplateRef(const TemplateRef& copy, const osg::CopyOp&) : mObject(copy.mObject) {} - - META_Object(Resource, TemplateRef) - - private: - osg::ref_ptr mObject; - }; - osg::ref_ptr SceneManager::createInstance(const std::string& name) { osg::ref_ptr scene = getTemplate(name); diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index fd75070a1..e897566a8 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -37,6 +37,31 @@ namespace Shader namespace Resource { + class TemplateRef : public osg::Object + { + public: + TemplateRef(const Object* object) : mObject(object) {} + TemplateRef() {} + TemplateRef(const TemplateRef& copy, const osg::CopyOp&) : mObject(copy.mObject) {} + + META_Object(Resource, TemplateRef) + + private: + osg::ref_ptr mObject; + }; + + class TemplateMultiRef : public osg::Object + { + public: + TemplateMultiRef() {} + TemplateMultiRef(const TemplateMultiRef& copy, const osg::CopyOp&) : mObjects(copy.mObjects) {} + void addRef(const osg::Node* node); + + META_Object(Resource, TemplateMultiRef) + + private: + std::vector> mObjects; + }; class MultiObjectCache; diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index d03612fc1..7e176be3d 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -121,6 +121,7 @@ void registerSerializers() const char* ignore[] = { "MWRender::PtrHolder", "Resource::TemplateRef", + "Resource::TemplateMultiRef", "SceneUtil::CompositeStateSetUpdater", "SceneUtil::LightListCallback", "SceneUtil::LightManagerUpdateCallback", From 0418e8e7a6eccef17a6995470d4f9ce2e95d1106 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Jan 2021 13:41:02 +0400 Subject: [PATCH 067/171] Add an API to get base wind speed (which is from openmw.cfg) --- apps/openmw/mwrender/sky.cpp | 9 +++++++++ apps/openmw/mwrender/sky.hpp | 4 ++++ apps/openmw/mwworld/weather.cpp | 2 ++ 3 files changed, 15 insertions(+) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 2920e07dd..93061022c 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -1134,6 +1134,7 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana , mRainEntranceSpeed(1) , mRainMaxRaindrops(0) , mWindSpeed(0.f) + , mBaseWindSpeed(0.f) , mEnabled(true) , mSunEnabled(true) , mWeatherAlpha(0.f) @@ -1685,6 +1686,7 @@ void SkyManager::setWeather(const WeatherResult& weather) mRainMaxHeight = weather.mRainMaxHeight; mRainSpeed = weather.mRainSpeed; mWindSpeed = weather.mWindSpeed; + mBaseWindSpeed = weather.mBaseWindSpeed; if (mRainEffect != weather.mRainEffect) { @@ -1853,6 +1855,13 @@ void SkyManager::setWeather(const WeatherResult& weather) fader->setAlpha(weather.mEffectFade); } +float SkyManager::getBaseWindSpeed() const +{ + if (!mCreated) return 0.f; + + return mBaseWindSpeed; +} + void SkyManager::sunEnable() { if (!mCreated) return; diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index cf697bd44..2ec134d09 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -70,6 +70,7 @@ namespace MWRender float mDLFogOffset; float mWindSpeed; + float mBaseWindSpeed; float mCurrentWindSpeed; float mNextWindSpeed; @@ -181,6 +182,8 @@ namespace MWRender void setRainIntensityUniform(osg::Uniform *uniform); + float getBaseWindSpeed() const; + private: void create(); ///< no need to call this, automatically done on first enable() @@ -265,6 +268,7 @@ namespace MWRender float mRainEntranceSpeed; int mRainMaxRaindrops; float mWindSpeed; + float mBaseWindSpeed; bool mEnabled; bool mSunEnabled; diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 6a4a227a4..415e69d20 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -1123,6 +1123,7 @@ inline void WeatherManager::calculateResult(const int weatherID, const float gam mResult.mCloudBlendFactor = 0; mResult.mNextWindSpeed = 0; mResult.mWindSpeed = mResult.mCurrentWindSpeed = calculateWindSpeed(weatherID, mWindSpeed); + mResult.mBaseWindSpeed = mWeatherSettings[weatherID].mWindSpeed; mResult.mCloudSpeed = current.mCloudSpeed; mResult.mGlareView = current.mGlareView; @@ -1214,6 +1215,7 @@ inline void WeatherManager::calculateTransitionResult(const float factor, const mResult.mCurrentWindSpeed = calculateWindSpeed(mCurrentWeather, mCurrentWindSpeed); mResult.mNextWindSpeed = calculateWindSpeed(mNextWeather, mNextWindSpeed); + mResult.mBaseWindSpeed = lerp(current.mBaseWindSpeed, other.mBaseWindSpeed, factor); mResult.mWindSpeed = lerp(mResult.mCurrentWindSpeed, mResult.mNextWindSpeed, factor); mResult.mCloudSpeed = lerp(current.mCloudSpeed, other.mCloudSpeed, factor); From 89f3f860ed99ce1b19c4ee3ec07373ad2f4cdcf1 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Jan 2021 13:57:05 +0400 Subject: [PATCH 068/171] Allow to get a rotation vector from ESM::Position --- components/esm/defs.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index 0f9cefab1..9bf9b01f3 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -54,6 +54,11 @@ struct Position { return osg::Vec3f(pos[0], pos[1], pos[2]); } + + osg::Vec3f asRotationVec3() const + { + return osg::Vec3f(rot[0], rot[1], rot[2]); + } }; #pragma pack(pop) From 16e03c151a783fc9902bce6cde73026c58cb6ddc Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 13 Jan 2021 15:38:29 +0200 Subject: [PATCH 069/171] Implement basic move algorithm, connect it to drag&drop --- apps/opencs/view/world/dragdroputils.cpp | 10 +++- apps/opencs/view/world/dragdroputils.hpp | 3 + apps/opencs/view/world/dragrecordtable.cpp | 7 ++- apps/opencs/view/world/dragrecordtable.hpp | 5 ++ apps/opencs/view/world/table.cpp | 70 ++++++++++++++++++++++ apps/opencs/view/world/table.hpp | 2 + 6 files changed, 95 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/world/dragdroputils.cpp b/apps/opencs/view/world/dragdroputils.cpp index 808125a60..bb4d97273 100644 --- a/apps/opencs/view/world/dragdroputils.cpp +++ b/apps/opencs/view/world/dragdroputils.cpp @@ -15,7 +15,15 @@ bool CSVWorld::DragDropUtils::canAcceptData(const QDropEvent &event, CSMWorld::C return data != nullptr && data->holdsType(type); } -CSMWorld::UniversalId CSVWorld::DragDropUtils::getAcceptedData(const QDropEvent &event, +bool CSVWorld::DragDropUtils::isInfo(const QDropEvent &event, CSMWorld::ColumnBase::Display type) +{ + const CSMWorld::TableMimeData *data = getTableMimeData(event); + return data != nullptr && ( + data->holdsType(CSMWorld::UniversalId::Type_TopicInfo) || + data->holdsType(CSMWorld::UniversalId::Type_JournalInfo) ); +} + +CSMWorld::UniversalId CSVWorld::DragDropUtils::getAcceptedData(const QDropEvent &event, CSMWorld::ColumnBase::Display type) { if (canAcceptData(event, type)) diff --git a/apps/opencs/view/world/dragdroputils.hpp b/apps/opencs/view/world/dragdroputils.hpp index d1d780708..2181e7606 100644 --- a/apps/opencs/view/world/dragdroputils.hpp +++ b/apps/opencs/view/world/dragdroputils.hpp @@ -20,6 +20,9 @@ namespace CSVWorld bool canAcceptData(const QDropEvent &event, CSMWorld::ColumnBase::Display type); ///< Checks whether the \a event contains a valid CSMWorld::TableMimeData that holds the \a type + bool isInfo(const QDropEvent &event, CSMWorld::ColumnBase::Display type); + ///< Info types can be dragged to sort the info table + CSMWorld::UniversalId getAcceptedData(const QDropEvent &event, CSMWorld::ColumnBase::Display type); ///< Gets the accepted data from the \a event using the \a type ///< \return Type_None if the \a event data doesn't holds the \a type diff --git a/apps/opencs/view/world/dragrecordtable.cpp b/apps/opencs/view/world/dragrecordtable.cpp index f84bf639d..58041af9f 100644 --- a/apps/opencs/view/world/dragrecordtable.cpp +++ b/apps/opencs/view/world/dragrecordtable.cpp @@ -46,7 +46,8 @@ void CSVWorld::DragRecordTable::dragEnterEvent(QDragEnterEvent *event) void CSVWorld::DragRecordTable::dragMoveEvent(QDragMoveEvent *event) { QModelIndex index = indexAt(event->pos()); - if (CSVWorld::DragDropUtils::canAcceptData(*event, getIndexDisplayType(index))) + if (CSVWorld::DragDropUtils::canAcceptData(*event, getIndexDisplayType(index)) || + CSVWorld::DragDropUtils::isInfo(*event, getIndexDisplayType(index)) ) { if (index.flags() & Qt::ItemIsEditable) { @@ -75,6 +76,10 @@ void CSVWorld::DragRecordTable::dropEvent(QDropEvent *event) } } } + else if (CSVWorld::DragDropUtils::isInfo(*event, display) && event->source() == this) + { + emit moveRecordsFromSameTable(event); + } } CSMWorld::ColumnBase::Display CSVWorld::DragRecordTable::getIndexDisplayType(const QModelIndex &index) const diff --git a/apps/opencs/view/world/dragrecordtable.hpp b/apps/opencs/view/world/dragrecordtable.hpp index a6b6756aa..f6c3fa890 100644 --- a/apps/opencs/view/world/dragrecordtable.hpp +++ b/apps/opencs/view/world/dragrecordtable.hpp @@ -23,6 +23,8 @@ namespace CSVWorld { class DragRecordTable : public QTableView { + Q_OBJECT + protected: CSMDoc::Document& mDocument; bool mEditLock; @@ -45,6 +47,9 @@ namespace CSVWorld private: CSMWorld::ColumnBase::Display getIndexDisplayType(const QModelIndex &index) const; + + signals: + void moveRecordsFromSameTable(QDropEvent *event); }; } diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index e5f4e36c5..03d7dbbfa 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -248,6 +249,7 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, if (isInfoTable) { mProxyModel = new CSMWorld::InfoTableProxyModel(id.getType(), this); + connect (this, &CSVWorld::DragRecordTable::moveRecordsFromSameTable, this, &CSVWorld::Table::moveRecords); } else if (isLtexTable) { @@ -563,6 +565,74 @@ void CSVWorld::Table::moveDownRecord() } } +void CSVWorld::Table::moveRecords(QDropEvent *event) +{ + if (mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) + return; + + QModelIndex targedIndex = indexAt(event->pos()); + + QModelIndexList selectedRows = selectionModel()->selectedRows(); + int targetRow = targedIndex.row(); + int baseRow = targedIndex.row() - 1; + int highestDifference = 0; + + for (const auto& thisRowData : selectedRows) + { + if (std::abs(targetRow - thisRowData.row()) > highestDifference) highestDifference = std::abs(targetRow - thisRowData.row()); + if (thisRowData.row() - 1 < baseRow) baseRow = thisRowData.row() - 1; + } + + std::vector newOrder (highestDifference + 1); + + for (long unsigned int i = 0; i < newOrder.size(); ++i) + { + newOrder[i] = i; + } + + if (selectedRows.size() > 1) + { + Log(Debug::Warning) << "Move operation failed: Moving multiple selections isn't implemented."; + return; + } + + for (const auto& thisRowData : selectedRows) + { + /* + Moving algorithm description + a) Remove the (ORIGIN + 1)th list member. + b) Add (ORIGIN+1)th list member with value TARGET + c) If ORIGIN > TARGET,d_INC; ELSE d_DEC + d_INC) increase all members after (and including) the TARGET by one, stop before hitting ORIGINth address + d_DEC) decrease all members after the ORIGIN by one, stop after hitting address TARGET + */ + + int originRow = thisRowData.row(); + //int sourceMappedOriginRow = mProxyModel->mapToSource (mProxyModel->index (originRow, 0)).row(); + + newOrder.erase(newOrder.begin() + originRow - baseRow - 1); + newOrder.emplace(newOrder.begin() + originRow - baseRow - 1, targetRow - baseRow - 1); + + if (originRow > targetRow) + { + for (int i = targetRow - baseRow - 1; i < originRow - baseRow - 1; ++i) + { + ++newOrder[i]; + } + } + else + { + for (int i = originRow - baseRow; i <= targetRow - baseRow - 1; ++i) + { + --newOrder[i]; + } + } + + } + mDocument.getUndoStack().push (new CSMWorld::ReorderRowsCommand ( + dynamic_cast (*mModel), baseRow + 1, newOrder)); +} + void CSVWorld::Table::editCell() { emit editRequest(mEditIdAction->getCurrentId(), ""); diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 61dd57c06..9c4b9b5e5 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -141,6 +141,8 @@ namespace CSVWorld void moveDownRecord(); + void moveRecords(QDropEvent *event); + void viewRecord(); void previewRecord(); From f2fc02cdff69cabafa00a33de95bd942511d2eb7 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 13 Jan 2021 15:55:16 +0200 Subject: [PATCH 070/171] Support filtered tables (mapToSource for indexes) --- apps/opencs/view/world/table.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 03d7dbbfa..c676a5ecc 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -573,14 +573,17 @@ void CSVWorld::Table::moveRecords(QDropEvent *event) QModelIndex targedIndex = indexAt(event->pos()); QModelIndexList selectedRows = selectionModel()->selectedRows(); - int targetRow = targedIndex.row(); - int baseRow = targedIndex.row() - 1; + int targetRowRaw = targedIndex.row(); + int targetRow = mProxyModel->mapToSource (mProxyModel->index (targetRowRaw, 0)).row(); + int baseRowRaw = targedIndex.row() - 1; + int baseRow = mProxyModel->mapToSource (mProxyModel->index (baseRowRaw, 0)).row(); int highestDifference = 0; for (const auto& thisRowData : selectedRows) { - if (std::abs(targetRow - thisRowData.row()) > highestDifference) highestDifference = std::abs(targetRow - thisRowData.row()); - if (thisRowData.row() - 1 < baseRow) baseRow = thisRowData.row() - 1; + int thisRow = mProxyModel->mapToSource (mProxyModel->index (thisRowData.row(), 0)).row(); + if (std::abs(targetRow - thisRow) > highestDifference) highestDifference = std::abs(targetRow - thisRow); + if (thisRow - 1 < baseRow) baseRow = thisRow - 1; } std::vector newOrder (highestDifference + 1); @@ -607,8 +610,8 @@ void CSVWorld::Table::moveRecords(QDropEvent *event) d_DEC) decrease all members after the ORIGIN by one, stop after hitting address TARGET */ - int originRow = thisRowData.row(); - //int sourceMappedOriginRow = mProxyModel->mapToSource (mProxyModel->index (originRow, 0)).row(); + int originRowRaw = thisRowData.row(); + int originRow = mProxyModel->mapToSource (mProxyModel->index (originRowRaw, 0)).row(); newOrder.erase(newOrder.begin() + originRow - baseRow - 1); newOrder.emplace(newOrder.begin() + originRow - baseRow - 1, targetRow - baseRow - 1); From 5740258d3b006977a5a8fa78f71b4cbb5d11f837 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 13 Jan 2021 16:23:59 +0200 Subject: [PATCH 071/171] Add changelog entry --- CHANGELOG.md | 1 + CHANGELOG_PR.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 943dcbe89..b628a847a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Bug #4055: Local scripts don't inherit variables from their base record Bug #4083: Door animation freezes when colliding with actors Bug #4247: Cannot walk up stairs in Ebonheart docks + Bug #4357: OpenMW-CS: TopicInfos index sorting and rearranging isn't fully functional Bug #4363: Editor: Defect in Clone Function for Dialogue Info records Bug #4447: Actor collision capsule shape allows looking through some walls Bug #4465: Collision shape overlapping causes twitching diff --git a/CHANGELOG_PR.md b/CHANGELOG_PR.md index f70fbad57..c7abb6fec 100644 --- a/CHANGELOG_PR.md +++ b/CHANGELOG_PR.md @@ -35,6 +35,7 @@ Bug Fixes: Editor Bug Fixes: - Deleted and moved objects within a cell are now saved properly (#832) +- Disabled record sorting in Topic and Journal Info tables, implemented drag-move for records (#4357) - Topic and Journal Info records can now be cloned with a different parent Topic/Journal Id (#4363) - Verifier no longer checks for alleged 'race' entries in clothing body parts (#5400) - Loading mods now keeps the master index (#5675) From d24a5f7b89a775baa09febcfd960138ae36eaf3e Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Wed, 13 Jan 2021 20:45:34 +0000 Subject: [PATCH 072/171] Fix OSG USE_GRAPHICSWINDOW check When OSG is built with `-DOSG_WINDOWING_SYSTEM=None`, this macro does not exist. Replaces the Android-specific check with a general one. --- components/sdlutil/sdlcursormanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sdlutil/sdlcursormanager.cpp b/components/sdlutil/sdlcursormanager.cpp index 56225868e..964762ec7 100644 --- a/components/sdlutil/sdlcursormanager.cpp +++ b/components/sdlutil/sdlcursormanager.cpp @@ -20,7 +20,7 @@ #include "imagetosurface.hpp" -#if defined(OSG_LIBRARY_STATIC) && !defined(ANDROID) +#if defined(OSG_LIBRARY_STATIC) && defined(USE_GRAPHICSWINDOW) // Sets the default windowing system interface according to the OS. // Necessary for OpenSceneGraph to do some things, like decompression. USE_GRAPHICSWINDOW() From 24d8412c0cab3b1eb146389168ec601acf222537 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Thu, 14 Jan 2021 02:52:59 +0000 Subject: [PATCH 073/171] cmake: Fix missing OPENGL_INCLUDE_DIR OPENGL_INCLUDE_DIR wasn't being included. It is usually just /usr/include but if it was custom the build failed. --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a615e843..226511b52 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -323,6 +323,7 @@ include_directories("." ${Boost_INCLUDE_DIR} ${MyGUI_INCLUDE_DIRS} ${OPENAL_INCLUDE_DIR} + ${OPENGL_INCLUDE_DIR} ${BULLET_INCLUDE_DIRS} ) From 11dfb9daff116ae9e1ccda971ebb8852831bfc6e Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Fri, 15 Jan 2021 15:11:50 +0000 Subject: [PATCH 074/171] cmake/FindOSGPlugins: Support lib-prefixed plugins With a regular OSG build, the plugin names are prefixed with `lib`, e.g. `libosgdb_jpeg.a` for a static build. However, on Debian on Ubuntu they are not. With this commit we now try both options. Fixes #5972 Signed-off-by: Gleb Mazovetskiy --- cmake/FindOSGPlugins.cmake | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cmake/FindOSGPlugins.cmake b/cmake/FindOSGPlugins.cmake index c210466c0..457abf665 100644 --- a/cmake/FindOSGPlugins.cmake +++ b/cmake/FindOSGPlugins.cmake @@ -27,9 +27,12 @@ foreach(_library ${OSGPlugins_FIND_COMPONENTS}) string(TOUPPER ${_library} _library_uc) set(_component OSGPlugins_${_library}) - set(${_library_uc}_DIR ${OSGPlugins_LIB_DIR}) # to help function osg_find_library + # On some systems, notably Debian and Ubuntu, the OSG plugins do not have + # the usual "lib" prefix. We temporarily add the empty string to the list + # of prefixes CMake searches for (via osg_find_library) to support these systems. set(_saved_lib_prefix ${CMAKE_FIND_LIBRARY_PREFIXES}) # save CMAKE_FIND_LIBRARY_PREFIXES - set(CMAKE_FIND_LIBRARY_PREFIXES "") # search libraries with no prefix + list(APPEND CMAKE_FIND_LIBRARY_PREFIXES "") # search libraries with no prefix + set(${_library_uc}_DIR ${OSGPlugins_LIB_DIR}) # to help function osg_find_library osg_find_library(${_library_uc} ${_library}) # find it into ${_library_uc}_LIBRARIES set(CMAKE_FIND_LIBRARY_PREFIXES ${_saved_lib_prefix}) # restore prefix From 4974b64cbf4f54d446386a0303962d08cd518026 Mon Sep 17 00:00:00 2001 From: psi29a Date: Sun, 17 Jan 2021 15:58:43 +0000 Subject: [PATCH 075/171] Update CI/before_install.osx.sh --- CI/before_install.osx.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index 85434fa06..ba0ff00ca 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -1,5 +1,9 @@ #!/bin/sh -e +# workaround python issue on travis +HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.8 || true +HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.9 || true + # Some of these tools can come from places other than brew, so check before installing command -v ccache >/dev/null 2>&1 || brew install ccache command -v cmake >/dev/null 2>&1 || brew install cmake From e37e5d4d16fd3240bd57355df40abc889e3ce8db Mon Sep 17 00:00:00 2001 From: fredzio Date: Mon, 18 Jan 2021 17:42:03 +0100 Subject: [PATCH 076/171] Don't run unstuck if there is no simulation running in async case. In this case, the actor mPreviousPosition is not updated, so the actor position is interpolated between an old (stucked) position and the new (unstucked) position. The new position is most likely "stucked", so the unstuck code strikes again, making the actor "vibrates". That's exactly what the sync code path does, and it doesn't exhibit this behavior. --- apps/openmw/mwphysics/mtphysics.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 2781a5b1b..6c7c573a4 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -173,6 +173,8 @@ namespace MWPhysics { if (mDeferAabbUpdate) updateAabbs(); + if (!mRemainingSteps) + return; for (auto& data : mActorsFrameData) if (data.mActor.lock()) { From 54ea8eb5c79d4a6bb88159cd6a67ce0dd4e17235 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Mon, 18 Jan 2021 19:21:02 +0200 Subject: [PATCH 077/171] Fix string corruption with Qt on linux-systems --- apps/opencs/main.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index 5287c8b19..c7d57a256 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -63,6 +63,9 @@ int runApplication(int argc, char *argv[]) application.setWindowIcon (QIcon (":./openmw-cs.png")); CS::Editor editor(argc, argv); +#ifdef __linux__ + setlocale(LC_NUMERIC,"C"); +#endif if(!editor.makeIPCServer()) { From 9f0f3eaeb2224a725fee002b986ebd56eb02b57c Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Mon, 18 Jan 2021 19:22:01 +0200 Subject: [PATCH 078/171] Add collada to supported formats --- apps/opencs/model/world/resourcesmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/resourcesmanager.cpp b/apps/opencs/model/world/resourcesmanager.cpp index 1af9c5e9b..378ba7c6b 100644 --- a/apps/opencs/model/world/resourcesmanager.cpp +++ b/apps/opencs/model/world/resourcesmanager.cpp @@ -17,7 +17,7 @@ void CSMWorld::ResourcesManager::addResources (const Resources& resources) const char * const * CSMWorld::ResourcesManager::getMeshExtensions() { // maybe we could go over the osgDB::Registry to list all supported node formats - static const char * const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2", 0 }; + static const char * const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2", "dae", 0 }; return sMeshTypes; } From 0acae08e526846838f8e5f5f0673e3af7cca4d83 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Mon, 18 Jan 2021 19:24:38 +0200 Subject: [PATCH 079/171] Add changelog entry --- CHANGELOG.md | 1 + CHANGELOG_PR.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75fd4cbe3..47cfd0031 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,7 @@ Bug #5695: ExplodeSpell for actors doesn't target the ground Bug #5703: OpenMW-CS menu system crashing on XFCE Bug #5706: AI sequences stop looping after the saved game is reloaded + Bug #5713: OpenMW-CS: Collada models are corrupted in Qt-based scene view Bug #5731: Editor: skirts are invisible on characters Bug #5739: Saving and loading the save a second or two before hitting the ground doesn't count fall damage Bug #5758: Paralyzed actors behavior is inconsistent with vanilla diff --git a/CHANGELOG_PR.md b/CHANGELOG_PR.md index f70fbad57..944b26044 100644 --- a/CHANGELOG_PR.md +++ b/CHANGELOG_PR.md @@ -39,6 +39,7 @@ Editor Bug Fixes: - Verifier no longer checks for alleged 'race' entries in clothing body parts (#5400) - Loading mods now keeps the master index (#5675) - Flicker and crashing on XFCE4 fixed (#5703) +- Collada models render properly in the Editor (#5713) Miscellaneous: - Prevent save-game bloating by using an appropriate fog texture format (#5108) From 8af8ad38406c35182dcb23a3c511be007a87dd9c Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 20 Jan 2021 01:17:16 +0000 Subject: [PATCH 080/171] Always write opaque fragments instead of relying on blending being off for translucent RTT --- apps/openmw/mwrender/characterpreview.cpp | 26 +++++++++++++++++++++-- apps/openmw/mwrender/renderingmanager.cpp | 1 + files/shaders/objects_fragment.glsl | 4 ++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 89db3e5f4..f21e667eb 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -85,7 +86,7 @@ namespace MWRender class SetUpBlendVisitor : public osg::NodeVisitor { public: - SetUpBlendVisitor(): osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + SetUpBlendVisitor(): osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mNoAlphaUniform(new osg::Uniform("noAlpha", false)) { } @@ -102,10 +103,17 @@ namespace MWRender newStateSet->setAttribute(newBlendFunc, osg::StateAttribute::ON); node.setStateSet(newStateSet); } - + if (stateset->getMode(GL_BLEND) & osg::StateAttribute::ON) + { + // Disable noBlendAlphaEnv + stateset->setTextureMode(7, GL_TEXTURE_2D, osg::StateAttribute::OFF); + stateset->addUniform(mNoAlphaUniform); + } } traverse(node); } + private: + osg::ref_ptr mNoAlphaUniform; }; CharacterPreview::CharacterPreview(osg::Group* parent, Resource::ResourceSystem* resourceSystem, @@ -164,6 +172,20 @@ namespace MWRender fog->setEnd(10000000); stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); + // Opaque stuff must have 1 as its fragment alpha as the FBO is translucent, so having blending off isn't enough + osg::ref_ptr noBlendAlphaEnv = new osg::TexEnvCombine(); + noBlendAlphaEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); + noBlendAlphaEnv->setSource0_Alpha(osg::TexEnvCombine::CONSTANT); + noBlendAlphaEnv->setConstantColor(osg::Vec4(0.0, 0.0, 0.0, 1.0)); + noBlendAlphaEnv->setCombine_RGB(osg::TexEnvCombine::REPLACE); + noBlendAlphaEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + osg::ref_ptr dummyTexture = new osg::Texture2D(); + dummyTexture->setInternalFormat(GL_RED); + dummyTexture->setTextureSize(1, 1); + stateset->setTextureAttributeAndModes(7, dummyTexture, osg::StateAttribute::ON); + stateset->setTextureAttribute(7, noBlendAlphaEnv, osg::StateAttribute::ON); + stateset->addUniform(new osg::Uniform("noAlpha", true)); + osg::ref_ptr lightmodel = new osg::LightModel; lightmodel->setAmbientIntensity(osg::Vec4(0.0, 0.0, 0.0, 1.0)); stateset->setAttributeAndModes(lightmodel, osg::StateAttribute::ON); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6ce431d2e..e1f6f0dd9 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -370,6 +370,7 @@ namespace MWRender mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("simpleWater", false)); + mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("noAlpha", false)); mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near"); mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far"); diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 78660685f..bd2bd5909 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -50,6 +50,7 @@ uniform mat2 bumpMapMatrix; #endif uniform bool simpleWater; +uniform bool noAlpha; varying float euclideanDepth; varying float linearDepth; @@ -208,5 +209,8 @@ void main() #endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); + if (noAlpha) + gl_FragData[0].a = 1.0; + applyShadowDebugOverlay(); } From 35fab974783e6a3f3b384ec09ce31f22432c89eb Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 20 Jan 2021 01:24:05 +0000 Subject: [PATCH 081/171] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75fd4cbe3..ce1fb07e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ Bug #5370: Opening an unlocked but trapped door uses the key Bug #5384: openmw-cs: deleting an instance requires reload of scene window to show in editor Bug #5387: Move/MoveWorld don't update the object's cell properly + Bug #5391: Races Redone 1.2 bodies don't show on the inventory Bug #5397: NPC greeting does not reset if you leave + reenter area Bug #5400: Editor: Verifier checks race of non-skin bodyparts Bug #5403: Enchantment effect doesn't show on an enemy during death animation From b6e92c9c6deedfb9c4a4acfd106d57eb3e1628e8 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 20 Jan 2021 23:37:19 +0000 Subject: [PATCH 082/171] Use ShaderVisitor to skip translucent framebuffer specific stuff --- apps/openmw/mwrender/characterpreview.cpp | 3 +++ apps/openmw/mwrender/renderingmanager.cpp | 1 - components/resource/scenemanager.cpp | 7 ++++--- components/resource/scenemanager.hpp | 4 ++-- components/shader/shadervisitor.cpp | 10 +++++++++- components/shader/shadervisitor.hpp | 4 ++++ files/shaders/objects_fragment.glsl | 3 +++ 7 files changed, 25 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index f21e667eb..14735050c 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -16,6 +16,8 @@ #include #include +#include +#include #include #include @@ -249,6 +251,7 @@ namespace MWRender void CharacterPreview::setBlendMode() { + mResourceSystem->getSceneManager()->recreateShaders(mNode, "objects", true); SetUpBlendVisitor visitor; mNode->accept(visitor); } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index e1f6f0dd9..6ce431d2e 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -370,7 +370,6 @@ namespace MWRender mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("simpleWater", false)); - mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("noAlpha", false)); mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near"); mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far"); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index e75fa4f74..d937b992b 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -247,9 +247,9 @@ namespace Resource return mForceShaders; } - void SceneManager::recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix) + void SceneManager::recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix, bool translucentFramebuffer) { - osg::ref_ptr shaderVisitor(createShaderVisitor(shaderPrefix)); + osg::ref_ptr shaderVisitor(createShaderVisitor(shaderPrefix, translucentFramebuffer)); shaderVisitor->setAllowedToModifyStateSets(false); node->accept(*shaderVisitor); } @@ -749,7 +749,7 @@ namespace Resource stats->setAttribute(frameNumber, "Node Instance", mInstanceCache->getCacheSize()); } - Shader::ShaderVisitor *SceneManager::createShaderVisitor(const std::string& shaderPrefix) + Shader::ShaderVisitor *SceneManager::createShaderVisitor(const std::string& shaderPrefix, bool translucentFramebuffer) { Shader::ShaderVisitor* shaderVisitor = new Shader::ShaderVisitor(*mShaderManager.get(), *mImageManager, shaderPrefix+"_vertex.glsl", shaderPrefix+"_fragment.glsl"); shaderVisitor->setForceShaders(mForceShaders); @@ -759,6 +759,7 @@ namespace Resource shaderVisitor->setAutoUseSpecularMaps(mAutoUseSpecularMaps); shaderVisitor->setSpecularMapPattern(mSpecularMapPattern); shaderVisitor->setApplyLightingToEnvMaps(mApplyLightingToEnvMaps); + shaderVisitor->setTranslucentFramebuffer(translucentFramebuffer); return shaderVisitor; } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index e897566a8..a815a324f 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -76,7 +76,7 @@ namespace Resource Shader::ShaderManager& getShaderManager(); /// Re-create shaders for this node, need to call this if texture stages or vertex color mode have changed. - void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects"); + void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false); /// @see ShaderVisitor::setForceShaders void setForceShaders(bool force); @@ -173,7 +173,7 @@ namespace Resource private: - Shader::ShaderVisitor* createShaderVisitor(const std::string& shaderPrefix = "objects"); + Shader::ShaderVisitor* createShaderVisitor(const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false); std::unique_ptr mShaderManager; bool mForceShaders; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index e908b6aaa..9dec5522c 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -40,6 +40,7 @@ namespace Shader , mAutoUseNormalMaps(false) , mAutoUseSpecularMaps(false) , mApplyLightingToEnvMaps(false) + , mTranslucentFramebuffer(false) , mShaderManager(shaderManager) , mImageManager(imageManager) , mDefaultVsTemplate(defaultVsTemplate) @@ -146,7 +147,7 @@ namespace Shader mRequirements.back().mShaderRequired = true; } } - else + else if (!mTranslucentFramebuffer) Log(Debug::Error) << "ShaderVisitor encountered unknown texture " << texture; } } @@ -322,6 +323,8 @@ namespace Shader writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); + defineMap["translucentFramebuffer"] = mTranslucentFramebuffer ? "1" : "0"; + osg::ref_ptr vertexShader (mShaderManager.getShader(mDefaultVsTemplate, defineMap, osg::Shader::VERTEX)); osg::ref_ptr fragmentShader (mShaderManager.getShader(mDefaultFsTemplate, defineMap, osg::Shader::FRAGMENT)); @@ -474,4 +477,9 @@ namespace Shader mApplyLightingToEnvMaps = apply; } + void ShaderVisitor::setTranslucentFramebuffer(bool translucent) + { + mTranslucentFramebuffer = translucent; + } + } diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 6031dbfe6..11b37c923 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -40,6 +40,8 @@ namespace Shader void setApplyLightingToEnvMaps(bool apply); + void setTranslucentFramebuffer(bool translucent); + void apply(osg::Node& node) override; void apply(osg::Drawable& drawable) override; @@ -63,6 +65,8 @@ namespace Shader bool mApplyLightingToEnvMaps; + bool mTranslucentFramebuffer; + ShaderManager& mShaderManager; Resource::ImageManager& mImageManager; diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index bd2bd5909..50bb77145 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -209,8 +209,11 @@ void main() #endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); +#if @translucentFramebuffer + // having testing & blending isn't enough - we need to write an opaque pixel to be opaque if (noAlpha) gl_FragData[0].a = 1.0; +#endif applyShadowDebugOverlay(); } From cc24f13b391b3c3db25d8a1d77cb95b65fcfafee Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 21 Jan 2021 13:08:50 +0100 Subject: [PATCH 083/171] Remove duplicated sound_buffer entry --- apps/openmw/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 56875a8c3..f96ddb27f 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -58,7 +58,6 @@ add_openmw_dir (mwscript add_openmw_dir (mwsound soundmanagerimp openal_output ffmpeg_decoder sound sound_buffer sound_decoder sound_output loudness movieaudiofactory alext efx efx-presets regionsoundselector watersoundupdater volumesettings - sound_buffer ) add_openmw_dir (mwworld From 1f4c85520f33a1f340765597e0b1dbf837670b64 Mon Sep 17 00:00:00 2001 From: fredzio Date: Thu, 7 Jan 2021 11:02:53 +0100 Subject: [PATCH 084/171] Use convexSweepTest for projectile movement to solve any imprecision issue with projectile collision detection. Simplify the mechanics: manage hits in one spot. Give magic projectiles a collision shape similar in size to their visible model. Rename the 2 convex result callback to clearly state their purpose. --- CHANGELOG.md | 1 + apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwmechanics/spellcasting.cpp | 35 ++--- ...ltcallback.cpp => actorconvexcallback.cpp} | 6 +- ...ltcallback.hpp => actorconvexcallback.hpp} | 8 +- .../closestnotmerayresultcallback.cpp | 33 +--- .../closestnotmerayresultcallback.hpp | 4 +- apps/openmw/mwphysics/physicssystem.cpp | 54 +++++-- apps/openmw/mwphysics/physicssystem.hpp | 6 +- apps/openmw/mwphysics/projectile.cpp | 61 +++++--- apps/openmw/mwphysics/projectile.hpp | 26 ++-- .../mwphysics/projectileconvexcallback.cpp | 67 +++++++++ .../mwphysics/projectileconvexcallback.hpp | 27 ++++ apps/openmw/mwphysics/raycasting.hpp | 2 +- apps/openmw/mwphysics/trace.cpp | 6 +- apps/openmw/mwworld/projectilemanager.cpp | 142 ++++-------------- apps/openmw/mwworld/projectilemanager.hpp | 4 +- 17 files changed, 259 insertions(+), 225 deletions(-) rename apps/openmw/mwphysics/{closestnotmeconvexresultcallback.cpp => actorconvexcallback.cpp} (92%) rename apps/openmw/mwphysics/{closestnotmeconvexresultcallback.hpp => actorconvexcallback.hpp} (54%) create mode 100644 apps/openmw/mwphysics/projectileconvexcallback.cpp create mode 100644 apps/openmw/mwphysics/projectileconvexcallback.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 75fd4cbe3..a2eaa3259 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Bug #4039: Multiple followers should have the same following distance Bug #4055: Local scripts don't inherit variables from their base record Bug #4083: Door animation freezes when colliding with actors + Bug #4201: Projectile-projectile collision Bug #4247: Cannot walk up stairs in Ebonheart docks Bug #4363: Editor: Defect in Clone Function for Dialogue Info records Bug #4447: Actor collision capsule shape allows looking through some walls diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 56875a8c3..412041f74 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -74,7 +74,7 @@ add_openmw_dir (mwworld add_openmw_dir (mwphysics physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver projectile - closestnotmeconvexresultcallback raycasting mtphysics contacttestwrapper + actorconvexcallback raycasting mtphysics contacttestwrapper projectileconvexcallback ) add_openmw_dir (mwclass diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 81b3a353d..142914c35 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -60,7 +60,10 @@ namespace MWMechanics void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded) { - if (!target.isEmpty() && target.getClass().isActor()) + if (target.isEmpty()) + return; + + if (target.getClass().isActor()) { // Early-out for characters that have departed. const auto& stats = target.getClass().getCreatureStats(target); @@ -82,7 +85,7 @@ namespace MWMechanics return; const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search (mId); - if (spell && !target.isEmpty() && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight)) + if (spell && target.getClass().isActor() && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight)) { int requiredResistance = (spell->mData.mType == ESM::Spell::ST_Disease) ? ESM::MagicEffect::ResistCommonDisease @@ -105,13 +108,13 @@ namespace MWMechanics // This is required for Weakness effects in a spell to apply to any subsequent effects in the spell. // Otherwise, they'd only apply after the whole spell was added. MagicEffects targetEffects; - if (!target.isEmpty() && target.getClass().isActor()) + if (target.getClass().isActor()) targetEffects += target.getClass().getCreatureStats(target).getMagicEffects(); bool castByPlayer = (!caster.isEmpty() && caster == getPlayer()); ActiveSpells targetSpells; - if (!target.isEmpty() && target.getClass().isActor()) + if (target.getClass().isActor()) targetSpells = target.getClass().getCreatureStats(target).getActiveSpells(); bool canCastAnEffect = false; // For bound equipment.If this remains false @@ -123,7 +126,7 @@ namespace MWMechanics int currentEffectIndex = 0; for (std::vector::const_iterator effectIt (effects.mList.begin()); - !target.isEmpty() && effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex) + effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex) { if (effectIt->mRange != range) continue; @@ -267,7 +270,7 @@ namespace MWMechanics } // Re-casting a summon effect will remove the creature from previous castings of that effect. - if (isSummoningEffect(effectIt->mEffectID) && !target.isEmpty() && target.getClass().isActor()) + if (isSummoningEffect(effectIt->mEffectID) && target.getClass().isActor()) { CreatureStats& targetStats = target.getClass().getCreatureStats(target); ESM::SummonKey key(effectIt->mEffectID, mId, currentEffectIndex); @@ -310,18 +313,16 @@ namespace MWMechanics if (!exploded) MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName, mFromProjectile); - if (!target.isEmpty()) { - if (!reflectedEffects.mList.empty()) - inflict(caster, target, reflectedEffects, range, true, exploded); + if (!reflectedEffects.mList.empty()) + inflict(caster, target, reflectedEffects, range, true, exploded); - if (!appliedLastingEffects.empty()) - { - int casterActorId = -1; - if (!caster.isEmpty() && caster.getClass().isActor()) - casterActorId = caster.getClass().getCreatureStats(caster).getActorId(); - target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, - mSourceName, casterActorId); - } + if (!appliedLastingEffects.empty()) + { + int casterActorId = -1; + if (!caster.isEmpty() && caster.getClass().isActor()) + casterActorId = caster.getClass().getCreatureStats(caster).getActorId(); + target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, + mSourceName, casterActorId); } } diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp b/apps/openmw/mwphysics/actorconvexcallback.cpp similarity index 92% rename from apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp rename to apps/openmw/mwphysics/actorconvexcallback.cpp index 27e7a390c..f99b706d7 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp +++ b/apps/openmw/mwphysics/actorconvexcallback.cpp @@ -1,6 +1,6 @@ #include -#include "closestnotmeconvexresultcallback.hpp" +#include "actorconvexcallback.hpp" #include "collisiontype.hpp" #include "contacttestwrapper.h" @@ -31,13 +31,13 @@ namespace MWPhysics } }; - ClosestNotMeConvexResultCallback::ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world) + ActorConvexCallback::ActorConvexCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world) : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)), mMe(me), mMotion(motion), mMinCollisionDot(minCollisionDot), mWorld(world) { } - btScalar ClosestNotMeConvexResultCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) + btScalar ActorConvexCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) { if (convexResult.m_hitCollisionObject == mMe) return btScalar(1); diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp b/apps/openmw/mwphysics/actorconvexcallback.hpp similarity index 54% rename from apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp rename to apps/openmw/mwphysics/actorconvexcallback.hpp index 538721ad8..1c28ee6cc 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp +++ b/apps/openmw/mwphysics/actorconvexcallback.hpp @@ -1,5 +1,5 @@ -#ifndef OPENMW_MWPHYSICS_CLOSESTNOTMECONVEXRESULTCALLBACK_H -#define OPENMW_MWPHYSICS_CLOSESTNOTMECONVEXRESULTCALLBACK_H +#ifndef OPENMW_MWPHYSICS_ACTORCONVEXCALLBACK_H +#define OPENMW_MWPHYSICS_ACTORCONVEXCALLBACK_H #include @@ -7,10 +7,10 @@ class btCollisionObject; namespace MWPhysics { - class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback + class ActorConvexCallback : public btCollisionWorld::ClosestConvexResultCallback { public: - ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world); + ActorConvexCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world); btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) override; diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp index c3104f860..32d97d6c7 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp @@ -7,16 +7,13 @@ #include "../mwworld/class.hpp" -#include "actor.hpp" -#include "collisiontype.hpp" -#include "projectile.hpp" #include "ptrholder.hpp" namespace MWPhysics { - ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector targets, const btVector3& from, const btVector3& to, Projectile* proj) + ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector targets, const btVector3& from, const btVector3& to) : btCollisionWorld::ClosestRayResultCallback(from, to) - , mMe(me), mTargets(std::move(targets)), mProjectile(proj) + , mMe(me), mTargets(std::move(targets)) { } @@ -25,9 +22,6 @@ namespace MWPhysics if (rayResult.m_collisionObject == mMe) return 1.f; - if (mProjectile && rayResult.m_collisionObject == mProjectile->getCollisionObject()) - return 1.f; - if (!mTargets.empty()) { if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end())) @@ -38,27 +32,6 @@ namespace MWPhysics } } - btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); - if (mProjectile) - { - switch (rayResult.m_collisionObject->getBroadphaseHandle()->m_collisionFilterGroup) - { - case CollisionType_Actor: - { - auto* target = static_cast(rayResult.m_collisionObject->getUserPointer()); - mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); - break; - } - case CollisionType_Projectile: - { - auto* target = static_cast(rayResult.m_collisionObject->getUserPointer()); - target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld); - mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); - break; - } - } - } - - return rayResult.m_hitFraction; + return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); } } diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp index b86427165..1fa32ef68 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp @@ -14,13 +14,13 @@ namespace MWPhysics class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback { public: - ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector targets, const btVector3& from, const btVector3& to, Projectile* proj=nullptr); + ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector targets, const btVector3& from, const btVector3& to); btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override; + private: const btCollisionObject* mMe; const std::vector mTargets; - Projectile* mProjectile; }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 95d1ec70a..ed0e0c915 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -55,6 +55,7 @@ #include "deepestnotmecontacttestresultcallback.hpp" #include "closestnotmerayresultcallback.hpp" #include "contacttestresultcallback.hpp" +#include "projectileconvexcallback.hpp" #include "constants.hpp" #include "movementsolver.hpp" #include "mtphysics.hpp" @@ -246,7 +247,7 @@ namespace MWPhysics return 0.f; } - RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector targets, int mask, int group, int projId) const + RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector targets, int mask, int group) const { if (from == to) { @@ -283,7 +284,7 @@ namespace MWPhysics } } - ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo, getProjectile(projId)); + ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo); resultCallback.m_collisionFilterGroup = group; resultCallback.m_collisionFilterMask = mask; @@ -580,15 +581,44 @@ namespace MWPhysics } } - void PhysicsSystem::updateProjectile(const int projectileId, const osg::Vec3f &position) + void PhysicsSystem::updateProjectile(const int projectileId, const osg::Vec3f &position) const { - ProjectileMap::iterator foundProjectile = mProjectiles.find(projectileId); - if (foundProjectile != mProjectiles.end()) - { - foundProjectile->second->setPosition(position); - mTaskScheduler->updateSingleAabb(foundProjectile->second); + const auto foundProjectile = mProjectiles.find(projectileId); + assert(foundProjectile != mProjectiles.end()); + auto* projectile = foundProjectile->second.get(); + + btVector3 btFrom = Misc::Convert::toBullet(projectile->getPosition()); + btVector3 btTo = Misc::Convert::toBullet(position); + + if (btFrom == btTo) return; - } + + const auto casterPtr = projectile->getCaster(); + const auto* caster = [this,&casterPtr]() -> const btCollisionObject* + { + const Actor* actor = getActor(casterPtr); + if (actor) + return actor->getCollisionObject(); + const Object* object = getObject(casterPtr); + if (object) + return object->getCollisionObject(); + return nullptr; + }(); + assert(caster); + + ProjectileConvexCallback resultCallback(caster, btFrom, btTo, projectile); + resultCallback.m_collisionFilterMask = 0xff; + resultCallback.m_collisionFilterGroup = CollisionType_Projectile; + + const btQuaternion btrot = btQuaternion::getIdentity(); + btTransform from_ (btrot, btFrom); + btTransform to_ (btrot, btTo); + + mTaskScheduler->convexSweepTest(projectile->getConvexShape(), from_, to_, resultCallback); + + const auto newpos = projectile->isActive() ? position : Misc::Convert::toOsg(resultCallback.m_hitPointWorld); + projectile->setPosition(newpos); + mTaskScheduler->updateSingleAabb(foundProjectile->second); } void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr) @@ -651,10 +681,10 @@ namespace MWPhysics mActors.emplace(ptr, std::move(actor)); } - int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position) + int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canTraverseWater) { mProjectileId++; - auto projectile = std::make_shared(mProjectileId, caster, position, mTaskScheduler.get(), this); + auto projectile = std::make_shared(caster, position, radius, canTraverseWater, mTaskScheduler.get(), this); mProjectiles.emplace(mProjectileId, std::move(projectile)); return mProjectileId; @@ -863,7 +893,7 @@ namespace MWPhysics mWaterCollisionShape.reset(new btStaticPlaneShape(btVector3(0,0,1), mWaterHeight)); mWaterCollisionObject->setCollisionShape(mWaterCollisionShape.get()); mTaskScheduler->addCollisionObject(mWaterCollisionObject.get(), CollisionType_Water, - CollisionType_Actor); + CollisionType_Actor|CollisionType_Projectile); } bool PhysicsSystem::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 449209974..6f901067a 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -124,8 +124,8 @@ namespace MWPhysics void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); - int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position); - void updateProjectile(const int projectileId, const osg::Vec3f &position); + int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canTraverseWater); + void updateProjectile(const int projectileId, const osg::Vec3f &position) const; void removeProjectile(const int projectileId); void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated); @@ -174,7 +174,7 @@ namespace MWPhysics /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors. RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), std::vector targets = std::vector(), - int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff, int projId=-1) const override; + int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const override; RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const override; diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index 1b9beca5f..a93121997 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -13,19 +13,22 @@ #include "../mwworld/class.hpp" #include "collisiontype.hpp" +#include "memory" #include "mtphysics.hpp" #include "projectile.hpp" namespace MWPhysics { -Projectile::Projectile(int projectileId, const MWWorld::Ptr& caster, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem) - : mActive(true) +Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canCrossWaterSurface, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem) + : mCanCrossWaterSurface(canCrossWaterSurface) + , mCrossedWaterSurface(false) + , mActive(true) , mCaster(caster) + , mWaterHitPosition(std::nullopt) , mPhysics(physicssystem) , mTaskScheduler(scheduler) - , mProjectileId(projectileId) { - mShape.reset(new btSphereShape(1.f)); + mShape = std::make_unique(radius); mConvexShape = static_cast(mShape.get()); mCollisionObject = std::make_unique(); @@ -67,6 +70,17 @@ void Projectile::setPosition(const osg::Vec3f &position) mTransformUpdatePending = true; } +osg::Vec3f Projectile::getPosition() const +{ + std::scoped_lock lock(mMutex); + return Misc::Convert::toOsg(mLocalTransform.getOrigin()); +} + +bool Projectile::canTraverseWater() const +{ + return mCanCrossWaterSurface; +} + void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal) { if (!mActive.load(std::memory_order_acquire)) @@ -78,12 +92,6 @@ void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal) mActive.store(false, std::memory_order_release); } -void Projectile::activate() -{ - assert(!mActive); - mActive.store(true, std::memory_order_release); -} - MWWorld::Ptr Projectile::getCaster() const { std::scoped_lock lock(mMutex); @@ -108,21 +116,32 @@ bool Projectile::isValidTarget(const MWWorld::Ptr& target) const if (mCaster == target) return false; - if (!mValidTargets.empty()) + if (target.isEmpty() || mValidTargets.empty()) + return true; + + bool validTarget = false; + for (const auto& targetActor : mValidTargets) { - bool validTarget = false; - for (const auto& targetActor : mValidTargets) + if (targetActor == target) { - if (targetActor == target) - { - validTarget = true; - break; - } + validTarget = true; + break; } - - return validTarget; } - return true; + return validTarget; +} + +std::optional Projectile::getWaterHitPosition() +{ + return std::exchange(mWaterHitPosition, std::nullopt); +} + +void Projectile::setWaterHitPosition(btVector3 pos) +{ + if (mCrossedWaterSurface) + return; + mCrossedWaterSurface = true; + mWaterHitPosition = pos; } } diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp index 5ae963b9a..fb50eebde 100644 --- a/apps/openmw/mwphysics/projectile.hpp +++ b/apps/openmw/mwphysics/projectile.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "components/misc/convert.hpp" @@ -32,7 +33,7 @@ namespace MWPhysics class Projectile final : public PtrHolder { public: - Projectile(const int projectileId, const MWWorld::Ptr& caster, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem); + Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canCrossWaterSurface, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem); ~Projectile() override; btConvexShape* getConvexShape() const { return mConvexShape; } @@ -40,17 +41,13 @@ namespace MWPhysics void commitPositionChange(); void setPosition(const osg::Vec3f& position); + osg::Vec3f getPosition() const; btCollisionObject* getCollisionObject() const { return mCollisionObject.get(); } - int getProjectileId() const - { - return mProjectileId; - } - bool isActive() const { return mActive.load(std::memory_order_acquire); @@ -65,18 +62,16 @@ namespace MWPhysics MWWorld::Ptr getCaster() const; void setCaster(MWWorld::Ptr caster); - osg::Vec3f getHitPos() const - { - assert(!mActive); - return Misc::Convert::toOsg(mHitPosition); - } + bool canTraverseWater() const; void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal); - void activate(); void setValidTargets(const std::vector& targets); bool isValidTarget(const MWWorld::Ptr& target) const; + std::optional getWaterHitPosition(); + void setWaterHitPosition(btVector3 pos); + private: std::unique_ptr mShape; @@ -85,9 +80,12 @@ namespace MWPhysics std::unique_ptr mCollisionObject; btTransform mLocalTransform; bool mTransformUpdatePending; + bool mCanCrossWaterSurface; + bool mCrossedWaterSurface; std::atomic mActive; MWWorld::Ptr mCaster; MWWorld::Ptr mHitTarget; + std::optional mWaterHitPosition; btVector3 mHitPosition; btVector3 mHitNormal; @@ -95,15 +93,11 @@ namespace MWPhysics mutable std::mutex mMutex; - osg::Vec3f mPosition; - PhysicsSystem *mPhysics; PhysicsTaskScheduler *mTaskScheduler; Projectile(const Projectile&); Projectile& operator=(const Projectile&); - - int mProjectileId; }; } diff --git a/apps/openmw/mwphysics/projectileconvexcallback.cpp b/apps/openmw/mwphysics/projectileconvexcallback.cpp new file mode 100644 index 000000000..0d0ac8720 --- /dev/null +++ b/apps/openmw/mwphysics/projectileconvexcallback.cpp @@ -0,0 +1,67 @@ +#include "../mwworld/class.hpp" + +#include "actor.hpp" +#include "collisiontype.hpp" +#include "projectile.hpp" +#include "projectileconvexcallback.hpp" +#include "ptrholder.hpp" + +namespace MWPhysics +{ + ProjectileConvexCallback::ProjectileConvexCallback(const btCollisionObject* me, const btVector3& from, const btVector3& to, Projectile* proj) + : btCollisionWorld::ClosestConvexResultCallback(from, to) + , mMe(me), mProjectile(proj) + { + assert(mProjectile); + } + + btScalar ProjectileConvexCallback::addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) + { + // don't hit the caster + if (result.m_hitCollisionObject == mMe) + return 1.f; + + // don't hit the projectile + if (result.m_hitCollisionObject == mProjectile->getCollisionObject()) + return 1.f; + + btCollisionWorld::ClosestConvexResultCallback::addSingleResult(result, normalInWorldSpace); + switch (result.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup) + { + case CollisionType_Actor: + { + auto* target = static_cast(result.m_hitCollisionObject->getUserPointer()); + if (!mProjectile->isValidTarget(target->getPtr())) + return 1.f; + mProjectile->hit(target->getPtr(), result.m_hitPointLocal, result.m_hitNormalLocal); + break; + } + case CollisionType_Projectile: + { + auto* target = static_cast(result.m_hitCollisionObject->getUserPointer()); + if (!mProjectile->isValidTarget(target->getCaster())) + return 1.f; + target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld); + mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); + break; + } + case CollisionType_Water: + { + mProjectile->setWaterHitPosition(m_hitPointWorld); + if (mProjectile->canTraverseWater()) + return 1.f; + mProjectile->hit(MWWorld::Ptr(), m_hitPointWorld, m_hitNormalWorld); + break; + } + default: + { + mProjectile->hit(MWWorld::Ptr(), m_hitPointWorld, m_hitNormalWorld); + break; + } + } + + return result.m_hitFraction; + } + +} + diff --git a/apps/openmw/mwphysics/projectileconvexcallback.hpp b/apps/openmw/mwphysics/projectileconvexcallback.hpp new file mode 100644 index 000000000..c687de36c --- /dev/null +++ b/apps/openmw/mwphysics/projectileconvexcallback.hpp @@ -0,0 +1,27 @@ +#ifndef OPENMW_MWPHYSICS_PROJECTILECONVEXCALLBACK_H +#define OPENMW_MWPHYSICS_PROJECTILECONVEXCALLBACK_H + +#include + +#include + +class btCollisionObject; + +namespace MWPhysics +{ + class Projectile; + + class ProjectileConvexCallback : public btCollisionWorld::ClosestConvexResultCallback + { + public: + ProjectileConvexCallback(const btCollisionObject* me, const btVector3& from, const btVector3& to, Projectile* proj); + + btScalar addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) override; + + private: + const btCollisionObject* mMe; + Projectile* mProjectile; + }; +} + +#endif diff --git a/apps/openmw/mwphysics/raycasting.hpp b/apps/openmw/mwphysics/raycasting.hpp index b176e8330..7c8375cb5 100644 --- a/apps/openmw/mwphysics/raycasting.hpp +++ b/apps/openmw/mwphysics/raycasting.hpp @@ -29,7 +29,7 @@ namespace MWPhysics /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors. virtual RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), std::vector targets = std::vector(), - int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff, int projId=-1) const = 0; + int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const = 0; virtual RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const = 0; diff --git a/apps/openmw/mwphysics/trace.cpp b/apps/openmw/mwphysics/trace.cpp index f50b6100a..049d026e8 100644 --- a/apps/openmw/mwphysics/trace.cpp +++ b/apps/openmw/mwphysics/trace.cpp @@ -7,7 +7,7 @@ #include "collisiontype.hpp" #include "actor.hpp" -#include "closestnotmeconvexresultcallback.hpp" +#include "actorconvexcallback.hpp" namespace MWPhysics { @@ -24,7 +24,7 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star to.setOrigin(btend); const btVector3 motion = btstart-btend; - ClosestNotMeConvexResultCallback newTraceCallback(actor, motion, btScalar(0.0), world); + ActorConvexCallback newTraceCallback(actor, motion, btScalar(0.0), world); // Inherit the actor's collision group and mask newTraceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup; newTraceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask; @@ -62,7 +62,7 @@ void ActorTracer::findGround(const Actor* actor, const osg::Vec3f& start, const btTransform to(trans.getBasis(), btend); const btVector3 motion = btstart-btend; - ClosestNotMeConvexResultCallback newTraceCallback(actor->getCollisionObject(), motion, btScalar(0.0), world); + ActorConvexCallback newTraceCallback(actor->getCollisionObject(), motion, btScalar(0.0), world); // Inherit the actor's collision group and mask newTraceCallback.m_collisionFilterGroup = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterGroup; newTraceCallback.m_collisionFilterMask = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterMask; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 08c9e4662..c87f62505 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -43,6 +44,7 @@ #include "../mwsound/sound.hpp" +#include "../mwphysics/collisiontype.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwphysics/projectile.hpp" @@ -187,7 +189,7 @@ namespace MWWorld }; - void ProjectileManager::createModel(State &state, const std::string &model, const osg::Vec3f& pos, const osg::Quat& orient, + float ProjectileManager::createModel(State &state, const std::string &model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture) { state.mNode = new osg::PositionAttitudeTransform; @@ -251,6 +253,7 @@ namespace MWWorld state.mNode->accept(assignVisitor); MWRender::overrideFirstRootTexture(texture, mResourceSystem, projectile); + return projectile->getBound().radius(); } void ProjectileManager::update(State& state, float duration) @@ -305,7 +308,7 @@ namespace MWWorld osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); - createModel(state, ptr.getClass().getModel(ptr), pos, orient, true, true, lightDiffuseColor, texture); + const auto radius = createModel(state, ptr.getClass().getModel(ptr), pos, orient, true, true, lightDiffuseColor, texture); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); for (const std::string &soundid : state.mSoundIds) @@ -316,7 +319,7 @@ namespace MWWorld state.mSounds.push_back(sound); } - state.mProjectileId = mPhysics->addProjectile(caster, pos); + state.mProjectileId = mPhysics->addProjectile(caster, pos, radius, false); state.mToDelete = false; mMagicBolts.push_back(state); } @@ -340,7 +343,7 @@ namespace MWWorld if (!ptr.getClass().getEnchantment(ptr).empty()) SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); - state.mProjectileId = mPhysics->addProjectile(actor, pos); + state.mProjectileId = mPhysics->addProjectile(actor, pos, 1.f, true); state.mToDelete = false; mProjectiles.push_back(state); } @@ -407,15 +410,7 @@ namespace MWWorld float speed = fTargetSpellMaxSpeed * magicBoltState.mSpeed; osg::Vec3f direction = orient * osg::Vec3f(0,1,0); direction.normalize(); - osg::Vec3f pos(magicBoltState.mNode->getPosition()); - osg::Vec3f newPos = pos + direction * duration * speed; - - for (const auto& sound : magicBoltState.mSounds) - sound->setPosition(newPos); - - magicBoltState.mNode->setPosition(newPos); - - mPhysics->updateProjectile(magicBoltState.mProjectileId, newPos); + osg::Vec3f newPos = projectile->getPosition() + direction * duration * speed; update(magicBoltState, duration); @@ -425,41 +420,7 @@ namespace MWWorld caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); projectile->setValidTargets(targetActors); - // Check for impact - // TODO: use a proper btRigidBody / btGhostObject? - const auto result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, magicBoltState.mProjectileId); - - bool hit = false; - if (result.mHit) - { - hit = true; - if (result.mHitObject.isEmpty()) - { - // terrain or projectile - } - else - { - MWMechanics::CastSpell cast(caster, result.mHitObject); - cast.mHitPosition = pos; - cast.mId = magicBoltState.mSpellId; - cast.mSourceName = magicBoltState.mSourceName; - cast.mStack = false; - cast.inflict(result.mHitObject, caster, magicBoltState.mEffects, ESM::RT_Target, false, true); - mPhysics->reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal)); - } - } - - // Explodes when hitting water - if (MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), newPos)) - hit = true; - - if (hit) - { - MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, result.mHitObject, - ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName); - - cleanupMagicBolt(magicBoltState); - } + mPhysics->updateProjectile(magicBoltState.mProjectileId, newPos); } } @@ -467,9 +428,6 @@ namespace MWWorld { for (auto& projectileState : mProjectiles) { - if (projectileState.mToDelete) - continue; - auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); if (!projectile->isActive()) continue; @@ -477,8 +435,7 @@ namespace MWWorld // simulating aerodynamics at all projectileState.mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration; - osg::Vec3f pos(projectileState.mNode->getPosition()); - osg::Vec3f newPos = pos + projectileState.mVelocity * duration; + osg::Vec3f newPos = projectile->getPosition() + projectileState.mVelocity * duration; // rotation does not work well for throwing projectiles - their roll angle will depend on shooting direction. if (!projectileState.mThrown) @@ -488,10 +445,6 @@ namespace MWWorld projectileState.mNode->setAttitude(orient); } - projectileState.mNode->setPosition(newPos); - - mPhysics->updateProjectile(projectileState.mProjectileId, newPos); - update(projectileState, duration); MWWorld::Ptr caster = projectileState.getCaster(); @@ -502,36 +455,7 @@ namespace MWWorld caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); projectile->setValidTargets(targetActors); - // Check for impact - // TODO: use a proper btRigidBody / btGhostObject? - const auto result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, projectileState.mProjectileId); - - bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), newPos); - - if (result.mHit || underwater) - { - // Try to get a Ptr to the bow that was used. It might no longer exist. - MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), projectileState.mIdArrow); - MWWorld::Ptr bow = projectileRef.getPtr(); - if (!caster.isEmpty() && projectileState.mIdArrow != projectileState.mBowId) - { - MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); - MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId)) - bow = *invIt; - } - - if (caster.isEmpty()) - caster = result.mHitObject; - - MWMechanics::projectileHit(caster, result.mHitObject, bow, projectileRef.getPtr(), result.mHit ? result.mHitPos : newPos, projectileState.mAttackStrength); - mPhysics->reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal)); - - if (underwater) - mRendering->emitWaterRipple(newPos); - - cleanupProjectile(projectileState); - } + mPhysics->updateProjectile(projectileState.mProjectileId, newPos); } } @@ -543,17 +467,19 @@ namespace MWWorld continue; auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); + + if (const auto hitWaterPos = projectile->getWaterHitPosition()) + mRendering->emitWaterRipple(Misc::Convert::toOsg(*hitWaterPos)); + + const auto pos = projectile->getPosition(); + projectileState.mNode->setPosition(pos); + if (projectile->isActive()) continue; + const auto target = projectile->getTarget(); - const auto pos = projectile->getHitPos(); - MWWorld::Ptr caster = projectileState.getCaster(); + auto caster = projectileState.getCaster(); assert(target != caster); - if (!projectile->isValidTarget(target)) - { - projectile->activate(); - continue; - } if (caster.isEmpty()) caster = target; @@ -569,9 +495,8 @@ namespace MWWorld bow = *invIt; } - projectileState.mHitPosition = pos; - cleanupProjectile(projectileState); MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength); + cleanupProjectile(projectileState); } for (auto& magicBoltState : mMagicBolts) { @@ -579,20 +504,18 @@ namespace MWWorld continue; auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId); + + const auto pos = projectile->getPosition(); + magicBoltState.mNode->setPosition(pos); + for (const auto& sound : magicBoltState.mSounds) + sound->setPosition(pos); + if (projectile->isActive()) continue; + const auto target = projectile->getTarget(); - const auto pos = projectile->getHitPos(); - MWWorld::Ptr caster = magicBoltState.getCaster(); + const auto caster = magicBoltState.getCaster(); assert(target != caster); - if (!projectile->isValidTarget(target)) - { - projectile->activate(); - continue; - } - - magicBoltState.mHitPosition = pos; - cleanupMagicBolt(magicBoltState); MWMechanics::CastSpell cast(caster, target); cast.mHitPosition = pos; @@ -602,6 +525,7 @@ namespace MWWorld cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, false, true); MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName); + cleanupMagicBolt(magicBoltState); } mProjectiles.erase(std::remove_if(mProjectiles.begin(), mProjectiles.end(), [](const State& state) { return state.mToDelete; }), mProjectiles.end()); @@ -702,7 +626,7 @@ namespace MWWorld int weaponType = ptr.get()->mBase->mData.mType; state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown; - state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition)); + state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), 1.f, true); } catch(...) { @@ -747,7 +671,6 @@ namespace MWWorld MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0)); MWWorld::Ptr ptr = ref.getPtr(); model = ptr.getClass().getModel(ptr); - state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition)); } catch(...) { @@ -755,7 +678,8 @@ namespace MWWorld } osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); - createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, true, lightDiffuseColor, texture); + const auto radius = createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, true, lightDiffuseColor, texture); + state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), radius, false); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); for (const std::string &soundid : state.mSoundIds) diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index 2cd570847..e088dd701 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -80,8 +80,6 @@ namespace MWWorld int mActorId; int mProjectileId; - osg::Vec3f mHitPosition; - // TODO: this will break when the game is saved and reloaded, since there is currently // no way to write identifiers for non-actors to a savegame. MWWorld::Ptr mCasterHandle; @@ -132,7 +130,7 @@ namespace MWWorld void moveProjectiles(float dt); void moveMagicBolts(float dt); - void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient, + float createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture = ""); void update (State& state, float duration); From cd5e31dc4ba2cdbbc80de93bf59ccc83c3bbda70 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Fri, 22 Jan 2021 19:04:07 +0000 Subject: [PATCH 085/171] Revert "Merge branch 'windowing-system' into 'master'" This reverts merge request !541 --- components/sdlutil/sdlcursormanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sdlutil/sdlcursormanager.cpp b/components/sdlutil/sdlcursormanager.cpp index 964762ec7..56225868e 100644 --- a/components/sdlutil/sdlcursormanager.cpp +++ b/components/sdlutil/sdlcursormanager.cpp @@ -20,7 +20,7 @@ #include "imagetosurface.hpp" -#if defined(OSG_LIBRARY_STATIC) && defined(USE_GRAPHICSWINDOW) +#if defined(OSG_LIBRARY_STATIC) && !defined(ANDROID) // Sets the default windowing system interface according to the OS. // Necessary for OpenSceneGraph to do some things, like decompression. USE_GRAPHICSWINDOW() From eb80c997b8255e86b86096bc4ac9218929eaeddd Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 22 Jan 2021 19:44:22 +0000 Subject: [PATCH 086/171] Avoid OSG setting array binding from multiple threads --- components/terrain/buffercache.cpp | 2 +- components/terrain/chunkmanager.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/components/terrain/buffercache.cpp b/components/terrain/buffercache.cpp index 5871b96bc..f9eb7ae63 100644 --- a/components/terrain/buffercache.cpp +++ b/components/terrain/buffercache.cpp @@ -186,7 +186,7 @@ namespace Terrain int vertexCount = numVerts * numVerts; - osg::ref_ptr uvs (new osg::Vec2Array); + osg::ref_ptr uvs (new osg::Vec2Array(osg::Array::BIND_PER_VERTEX)); uvs->reserve(vertexCount); for (unsigned int col = 0; col < numVerts; ++col) diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 041414a87..50724628d 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -197,8 +197,7 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve bool useCompositeMap = chunkSize >= mCompositeMapLevel; unsigned int numUvSets = useCompositeMap ? 1 : 2; - for (unsigned int i=0; isetTexCoordArray(i, mBufferCache.getUVBuffer(numVerts)); + geometry->setTexCoordArrayList(osg::Geometry::ArrayList(numUvSets, mBufferCache.getUVBuffer(numVerts))); geometry->createClusterCullingCallback(); From a401c517bfa000347443c2837a0b55718000a53a Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 23 Jan 2021 00:56:46 +0100 Subject: [PATCH 087/171] Always unload height fields loadCell always adds a height field, but unloadCell only removed it for cells with height data. Reloading a cell overwrote the height field added earlier (leading to its destruction) while the navigator retained a reference to the now deleted collision shape, leading to a crash. --- apps/openmw/mwworld/scene.cpp | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 47b62862f..427ee3aa8 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -340,17 +340,9 @@ namespace MWWorld if ((*iter)->getCell()->isExterior()) { - const ESM::Land* land = - MWBase::Environment::get().getWorld()->getStore().get().search( - (*iter)->getCell()->getGridX(), - (*iter)->getCell()->getGridY() - ); - if (land && land->mDataTypes&ESM::Land::DATA_VHGT) - { - if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) - navigator->removeObject(DetourNavigator::ObjectId(heightField)); - mPhysics->removeHeightField(cellX, cellY); - } + if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) + navigator->removeObject(DetourNavigator::ObjectId(heightField)); + mPhysics->removeHeightField(cellX, cellY); } if ((*iter)->getCell()->hasWater()) From bd7d5a8f92640ceadaa70b22e650a5644f1b030f Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sat, 23 Jan 2021 04:08:39 +0000 Subject: [PATCH 088/171] Fix memory leak in FontLoader::loadFontFromXml Tried building with -DCMAKE_CXX_FLAGS='-fsanitize=address -fsanitize-recover=address' and this was one of the reported leaks. --- components/fontloader/fontloader.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 605d55243..09bf0b190 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -630,6 +630,7 @@ namespace Gui } MyGUI::ResourceManager::getInstance().loadFromXmlNode(copy, _file, _version); + delete copy; } } From 3300e26c86ecd4c84cc6b099e2abe918a6a5ef3c Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 23 Jan 2021 15:25:05 +0000 Subject: [PATCH 089/171] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b3a3438a..cede01f63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ Bug #4623: Corprus implementation is incorrect Bug #4631: Setting MSAA level too high doesn't fall back to highest supported level Bug #4764: Data race in osg ParticleSystem + Bug #4765: Data race in ChunkManager -> Array::setBinding Bug #4774: Guards are ignorant of an invisible player that tries to attack them Bug #5101: Hostile followers travel with the player Bug #5108: Savegame bloating due to inefficient fog textures format From 1e113710efb68183300c228ffa05136072789659 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sat, 23 Jan 2021 17:14:56 +0000 Subject: [PATCH 090/171] Clean-up FontLoader::loadFontFromXml fix Follow-up to !559 --- components/fontloader/fontloader.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 09bf0b190..2bed079e1 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -593,7 +593,7 @@ namespace Gui if (createCopy) { - MyGUI::xml::ElementPtr copy = _node->createCopy(); + std::unique_ptr copy{_node->createCopy()}; MyGUI::xml::ElementEnumerator copyFont = copy->getElementEnumerator(); while (copyFont.next("Resource")) @@ -629,8 +629,7 @@ namespace Gui } } - MyGUI::ResourceManager::getInstance().loadFromXmlNode(copy, _file, _version); - delete copy; + MyGUI::ResourceManager::getInstance().loadFromXmlNode(copy.get(), _file, _version); } } From 165c7314928dc281a364fa1a0143c45fd6d2adfd Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 23 Jan 2021 10:43:32 +0100 Subject: [PATCH 091/171] Remove physics dependency on basenode Necessary to be able to load physics objects from inactive cells. --- apps/openmw/mwclass/activator.cpp | 4 +-- apps/openmw/mwclass/activator.hpp | 2 +- apps/openmw/mwclass/actor.cpp | 6 +---- apps/openmw/mwclass/actor.hpp | 10 +++---- apps/openmw/mwclass/apparatus.cpp | 5 ---- apps/openmw/mwclass/apparatus.hpp | 2 -- apps/openmw/mwclass/armor.cpp | 5 ---- apps/openmw/mwclass/armor.hpp | 2 -- apps/openmw/mwclass/bodypart.cpp | 4 --- apps/openmw/mwclass/bodypart.hpp | 2 -- apps/openmw/mwclass/book.cpp | 5 ---- apps/openmw/mwclass/book.hpp | 2 -- apps/openmw/mwclass/clothing.cpp | 5 ---- apps/openmw/mwclass/clothing.hpp | 2 -- apps/openmw/mwclass/container.cpp | 4 +-- apps/openmw/mwclass/container.hpp | 2 +- apps/openmw/mwclass/door.cpp | 4 +-- apps/openmw/mwclass/door.hpp | 2 +- apps/openmw/mwclass/ingredient.cpp | 5 ---- apps/openmw/mwclass/ingredient.hpp | 2 -- apps/openmw/mwclass/light.cpp | 4 +-- apps/openmw/mwclass/light.hpp | 2 +- apps/openmw/mwclass/lockpick.cpp | 5 ---- apps/openmw/mwclass/lockpick.hpp | 2 -- apps/openmw/mwclass/misc.cpp | 5 ---- apps/openmw/mwclass/misc.hpp | 2 -- apps/openmw/mwclass/potion.cpp | 5 ---- apps/openmw/mwclass/potion.hpp | 2 -- apps/openmw/mwclass/probe.cpp | 5 ---- apps/openmw/mwclass/probe.hpp | 2 -- apps/openmw/mwclass/repair.cpp | 5 ---- apps/openmw/mwclass/repair.hpp | 2 -- apps/openmw/mwclass/static.cpp | 4 +-- apps/openmw/mwclass/static.hpp | 2 +- apps/openmw/mwclass/weapon.cpp | 5 ---- apps/openmw/mwclass/weapon.hpp | 2 -- apps/openmw/mwphysics/actor.cpp | 6 ++--- apps/openmw/mwphysics/actor.hpp | 2 +- apps/openmw/mwphysics/object.cpp | 11 +++++--- apps/openmw/mwphysics/object.hpp | 4 +-- apps/openmw/mwphysics/physicssystem.cpp | 10 +++---- apps/openmw/mwphysics/physicssystem.hpp | 4 +-- apps/openmw/mwworld/class.cpp | 6 +---- apps/openmw/mwworld/class.hpp | 13 +++++---- apps/openmw/mwworld/scene.cpp | 36 +++++++++++++------------ apps/openmw/mwworld/worldimp.cpp | 2 +- 46 files changed, 68 insertions(+), 155 deletions(-) diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index c54b1c369..716e548e1 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -38,10 +38,10 @@ namespace MWClass } } - void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model); + physics.addObject(ptr, model, rotation); } std::string Activator::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index 10ace6f74..c7b65ef67 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -17,7 +17,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp index 33aeb26bb..1789f1b19 100644 --- a/apps/openmw/mwclass/actor.cpp +++ b/apps/openmw/mwclass/actor.cpp @@ -17,16 +17,12 @@ namespace MWClass { - Actor::Actor() {} - - Actor::~Actor() {} - void Actor::adjustPosition(const MWWorld::Ptr& ptr, bool force) const { MWBase::Environment::get().getWorld()->adjustPosition(ptr, force); } - void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const { if (!model.empty()) { diff --git a/apps/openmw/mwclass/actor.hpp b/apps/openmw/mwclass/actor.hpp index 3d509b276..98998b4eb 100644 --- a/apps/openmw/mwclass/actor.hpp +++ b/apps/openmw/mwclass/actor.hpp @@ -15,16 +15,16 @@ namespace MWClass { protected: - Actor(); + Actor() = default; public: - virtual ~Actor(); + ~Actor() override = default; void adjustPosition(const MWWorld::Ptr& ptr, bool force) const override; ///< Adjust position to stand on ground. Must be called post model load /// @param force do this even if the ptr is flying - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; bool useAnim() const override; @@ -46,8 +46,8 @@ namespace MWClass float getCurrentSpeed(const MWWorld::Ptr& ptr) const override; // not implemented - Actor(const Actor&); - Actor& operator= (const Actor&); + Actor(const Actor&) = delete; + Actor& operator= (const Actor&) = delete; }; } diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 518695fab..e09e4804c 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -26,11 +26,6 @@ namespace MWClass } } - void Apparatus::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Apparatus::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/apparatus.hpp b/apps/openmw/mwclass/apparatus.hpp index 8087c57ba..828abef25 100644 --- a/apps/openmw/mwclass/apparatus.hpp +++ b/apps/openmw/mwclass/apparatus.hpp @@ -17,8 +17,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 3f9bfb859..817adbc1f 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -34,11 +34,6 @@ namespace MWClass } } - void Armor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Armor::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index 4f04e0824..f64f138a2 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -16,8 +16,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/bodypart.cpp b/apps/openmw/mwclass/bodypart.cpp index 0315d3ddb..7fe798e27 100644 --- a/apps/openmw/mwclass/bodypart.cpp +++ b/apps/openmw/mwclass/bodypart.cpp @@ -22,10 +22,6 @@ namespace MWClass } } - void BodyPart::insertObject(const MWWorld::Ptr &ptr, const std::string &model, MWPhysics::PhysicsSystem &physics) const - { - } - std::string BodyPart::getName(const MWWorld::ConstPtr &ptr) const { return std::string(); diff --git a/apps/openmw/mwclass/bodypart.hpp b/apps/openmw/mwclass/bodypart.hpp index 13d914138..0e372b884 100644 --- a/apps/openmw/mwclass/bodypart.hpp +++ b/apps/openmw/mwclass/bodypart.hpp @@ -15,8 +15,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 4ea71e3ac..51b9e39d7 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -31,11 +31,6 @@ namespace MWClass } } - void Book::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Book::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index c58e68ad8..f3d34c516 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -14,8 +14,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 6d7960aac..400cd97e4 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -29,11 +29,6 @@ namespace MWClass } } - void Clothing::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Clothing::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index a87e0cbe0..3d5c162aa 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -14,8 +14,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 28305c394..3023466f0 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -111,10 +111,10 @@ namespace MWClass } } - void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model); + physics.addObject(ptr, model, rotation); } std::string Container::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 2dc0c06ca..74d9ac0da 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -44,7 +44,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index ba51d9c2b..983953c20 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -62,10 +62,10 @@ namespace MWClass } } - void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, MWPhysics::CollisionType_Door); + physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_Door); // Resume the door's opening/closing animation if it wasn't finished if (ptr.getRefData().getCustomData()) diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index 6c2fa26b8..6d40a840b 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -18,7 +18,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; bool isDoor() const override; diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index a007ad115..20f9576df 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -28,11 +28,6 @@ namespace MWClass } } - void Ingredient::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Ingredient::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/ingredient.hpp b/apps/openmw/mwclass/ingredient.hpp index 5219cf39c..2aa831f86 100644 --- a/apps/openmw/mwclass/ingredient.hpp +++ b/apps/openmw/mwclass/ingredient.hpp @@ -14,8 +14,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 3bdf10f47..d4d196bc4 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -33,7 +33,7 @@ namespace MWClass renderingInterface.getObjects().insertModel(ptr, model, true, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)); } - void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const { MWWorld::LiveCellRef *ref = ptr.get(); @@ -41,7 +41,7 @@ namespace MWClass // TODO: add option somewhere to enable collision for placeable objects if (!model.empty() && (ref->mBase->mData.mFlags & ESM::Light::Carry) == 0) - physics.addObject(ptr, model); + physics.addObject(ptr, model, rotation); if (!ref->mBase->mSound.empty() && !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)) MWBase::Environment::get().getSoundManager()->playSound3D(ptr, ref->mBase->mSound, 1.0, 1.0, diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index e37dddc25..1b1794d4a 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -14,7 +14,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; bool useAnim() const override; diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 9b8abc8f2..985b08771 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -28,11 +28,6 @@ namespace MWClass } } - void Lockpick::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Lockpick::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index fabae3343..d4b265e39 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -14,8 +14,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 8d3cda6fe..facab9d51 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -37,11 +37,6 @@ namespace MWClass } } - void Miscellaneous::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Miscellaneous::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index 9bff85ca5..18788c7ed 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -14,8 +14,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 4af97e634..56d9dff27 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -30,11 +30,6 @@ namespace MWClass } } - void Potion::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Potion::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp index 75d923f0b..75b962164 100644 --- a/apps/openmw/mwclass/potion.hpp +++ b/apps/openmw/mwclass/potion.hpp @@ -14,8 +14,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index dba4e8c06..51273337a 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -28,11 +28,6 @@ namespace MWClass } } - void Probe::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Probe::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index a0a41dcfb..ef9273a37 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -14,8 +14,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 8907c8212..f1b88e422 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -25,11 +25,6 @@ namespace MWClass } } - void Repair::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Repair::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/repair.hpp b/apps/openmw/mwclass/repair.hpp index b9791e9cf..c403449e1 100644 --- a/apps/openmw/mwclass/repair.hpp +++ b/apps/openmw/mwclass/repair.hpp @@ -14,8 +14,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 5551b3d73..108c4eaa2 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -23,10 +23,10 @@ namespace MWClass } } - void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model); + physics.addObject(ptr, model, rotation); } std::string Static::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index 6bc783dad..f856e9fd9 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -14,7 +14,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 0d6a27cf6..6246c8fb0 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -34,11 +34,6 @@ namespace MWClass } } - void Weapon::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Weapon::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index f1824b7d1..db17e6b70 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -15,8 +15,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 3b52ee934..06600fd6a 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -67,7 +67,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic updateScale(); if(!mRotationallyInvariant) - updateRotation(); + setRotation(mPtr.getRefData().getBaseNode()->getAttitude()); updatePosition(); addCollisionMask(getCollisionMask()); @@ -197,10 +197,10 @@ osg::Vec3f Actor::getPreviousPosition() const return mPreviousPosition; } -void Actor::updateRotation () +void Actor::setRotation(osg::Quat quat) { std::scoped_lock lock(mPositionMutex); - mRotation = mPtr.getRefData().getBaseNode()->getAttitude(); + mRotation = quat; } bool Actor::isRotationallyInvariant() const diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 9d129f2ba..4039f4481 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -49,7 +49,7 @@ namespace MWPhysics void enableCollisionBody(bool collision); void updateScale(); - void updateRotation(); + void setRotation(osg::Quat quat); /** * Return true if the collision shape looks the same no matter how its Z rotated. diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index e3615989d..0a7b9540c 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -14,7 +14,7 @@ namespace MWPhysics { - Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler) + Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, osg::Quat rotation, int collisionType, PhysicsTaskScheduler* scheduler) : mShapeInstance(shapeInstance) , mSolid(true) , mTaskScheduler(scheduler) @@ -27,7 +27,7 @@ namespace MWPhysics mCollisionObject->setUserPointer(this); setScale(ptr.getCellRef().getScale()); - setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); + setRotation(rotation); setOrigin(Misc::Convert::toBullet(ptr.getRefData().getPosition().asVec3())); commitPositionChange(); @@ -51,10 +51,10 @@ namespace MWPhysics mScaleUpdatePending = true; } - void Object::setRotation(const btQuaternion& quat) + void Object::setRotation(osg::Quat quat) { std::unique_lock lock(mPositionMutex); - mLocalTransform.setRotation(quat); + mLocalTransform.setRotation(Misc::Convert::toBullet(quat)); mTransformUpdatePending = true; } @@ -116,6 +116,9 @@ namespace MWPhysics if (mShapeInstance->mAnimatedShapes.empty()) return false; + if (mPtr.getRefData().getBaseNode() == nullptr) + return true; + assert (mShapeInstance->getCollisionShape()->isCompound()); btCompoundShape* compound = static_cast(mShapeInstance->getCollisionShape()); diff --git a/apps/openmw/mwphysics/object.hpp b/apps/openmw/mwphysics/object.hpp index cae877180..c2273831e 100644 --- a/apps/openmw/mwphysics/object.hpp +++ b/apps/openmw/mwphysics/object.hpp @@ -26,12 +26,12 @@ namespace MWPhysics class Object final : public PtrHolder { public: - Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler); + Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, osg::Quat rotation, int collisionType, PhysicsTaskScheduler* scheduler); ~Object() override; const Resource::BulletShapeInstance* getShapeInstance() const; void setScale(float scale); - void setRotation(const btQuaternion& quat); + void setRotation(osg::Quat quat); void setOrigin(const btVector3& vec); void commitPositionChange(); btCollisionObject* getCollisionObject(); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index ed0e0c915..68cec48bc 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -456,13 +456,13 @@ namespace MWPhysics return heightField->second.get(); } - void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType) + void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType) { osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); if (!shapeInstance || !shapeInstance->getCollisionShape()) return; - auto obj = std::make_shared(ptr, shapeInstance, collisionType, mTaskScheduler.get()); + auto obj = std::make_shared(ptr, shapeInstance, rotation, collisionType, mTaskScheduler.get()); mObjects.emplace(ptr, obj); if (obj->isAnimated()) @@ -621,12 +621,12 @@ namespace MWPhysics mTaskScheduler->updateSingleAabb(foundProjectile->second); } - void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr) + void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr, osg::Quat rotate) { ObjectMap::iterator found = mObjects.find(ptr); if (found != mObjects.end()) { - found->second->setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); + found->second->setRotation(rotate); mTaskScheduler->updateSingleAabb(found->second); return; } @@ -635,7 +635,7 @@ namespace MWPhysics { if (!foundActor->second->isRotationallyInvariant()) { - foundActor->second->updateRotation(); + foundActor->second->setRotation(rotate); mTaskScheduler->updateSingleAabb(foundActor->second); } return; diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 6f901067a..c61b368f8 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -121,7 +121,7 @@ namespace MWPhysics void setWaterHeight(float height); void disableWater(); - void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World); + void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType = CollisionType_World); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canTraverseWater); @@ -141,7 +141,7 @@ namespace MWPhysics void remove (const MWWorld::Ptr& ptr); void updateScale (const MWWorld::Ptr& ptr); - void updateRotation (const MWWorld::Ptr& ptr); + void updateRotation (const MWWorld::Ptr& ptr, osg::Quat rotate); void updatePosition (const MWWorld::Ptr& ptr); void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject); diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 950c8a6d4..becf912ea 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -25,16 +25,12 @@ namespace MWWorld { std::map > Class::sClasses; - Class::Class() {} - - Class::~Class() {} - void Class::insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const { } - void Class::insertObject(const Ptr& ptr, const std::string& mesh, MWPhysics::PhysicsSystem& physics) const + void Class::insertObject(const Ptr& ptr, const std::string& mesh, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const { } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 1b3d4029e..39fb6fe4c 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include "ptr.hpp" @@ -57,13 +58,9 @@ namespace MWWorld std::string mTypeName; - // not implemented - Class (const Class&); - Class& operator= (const Class&); - protected: - Class(); + Class() = default; std::shared_ptr defaultItemActivate(const Ptr &ptr, const Ptr &actor) const; ///< Generate default action for activating inventory items @@ -72,14 +69,16 @@ namespace MWWorld public: - virtual ~Class(); + virtual ~Class() = default; + Class (const Class&) = delete; + Class& operator= (const Class&) = delete; const std::string& getTypeName() const { return mTypeName; } virtual void insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const; - virtual void insertObject(const Ptr& ptr, const std::string& mesh, MWPhysics::PhysicsSystem& physics) const; + virtual void insertObject(const Ptr& ptr, const std::string& mesh, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const; ///< Add reference into a cell for rendering (default implementation: don't render anything). virtual std::string getName (const ConstPtr& ptr) const = 0; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 427ee3aa8..313e9a152 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -75,18 +75,20 @@ namespace * osg::Quat(xr, osg::Vec3(-1, 0, 0)); } - void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, RotationOrder order) + osg::Quat makeNodeRotation(const MWWorld::Ptr& ptr, RotationOrder order) { - if (!ptr.getRefData().getBaseNode()) - return; + const auto pos = ptr.getRefData().getPosition(); + + const auto rot = ptr.getClass().isActor() ? makeActorOsgQuat(pos) + : (order == RotationOrder::inverse ? makeInversedOrderObjectOsgQuat(pos) : makeObjectOsgQuat(pos)); + + return rot; + } - rendering.rotateObject(ptr, - ptr.getClass().isActor() - ? makeActorOsgQuat(ptr.getRefData().getPosition()) - : (order == RotationOrder::inverse - ? makeInversedOrderObjectOsgQuat(ptr.getRefData().getPosition()) - : makeObjectOsgQuat(ptr.getRefData().getPosition())) - ); + void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, osg::Quat rotation) + { + if (ptr.getRefData().getBaseNode()) + rendering.rotateObject(ptr, rotation); } std::string getModel(const MWWorld::Ptr &ptr, const VFS::Manager *vfs) @@ -117,11 +119,10 @@ namespace const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) ptr.getClass().insertObjectRendering(ptr, model, rendering); - else - ptr.getRefData().setBaseNode(new SceneUtil::PositionAttitudeTransform); // FIXME remove this when physics code is fixed not to depend on basenode - setNodeRotation(ptr, rendering, RotationOrder::direct); - ptr.getClass().insertObject (ptr, model, physics); + const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); + setNodeRotation(ptr, rendering, rotation); + ptr.getClass().insertObject (ptr, model, rotation, physics); if (useAnim) MWBase::Environment::get().getMechanicsManager()->add(ptr); @@ -276,7 +277,7 @@ namespace MWWorld { if (!ptr.getRefData().getBaseNode()) return; ptr.getClass().insertObjectRendering(ptr, getModel(ptr, mRendering.getResourceSystem()->getVFS()), mRendering); - setNodeRotation(ptr, mRendering, RotationOrder::direct); + setNodeRotation(ptr, mRendering, makeNodeRotation(ptr, RotationOrder::direct)); reloadTerrain(); } } @@ -292,8 +293,9 @@ namespace MWWorld void Scene::updateObjectRotation(const Ptr &ptr, RotationOrder order) { - setNodeRotation(ptr, mRendering, order); - mPhysics->updateRotation(ptr); + const auto rot = makeNodeRotation(ptr, order); + setNodeRotation(ptr, mRendering, rot); + mPhysics->updateRotation(ptr, rot); } void Scene::updateObjectScale(const Ptr &ptr) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index bf5b6db73..af9a5e0bb 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1407,7 +1407,7 @@ namespace MWWorld mWorldScene->removeFromPagedRefs(ptr); mRendering->rotateObject(ptr, rotate); - mPhysics->updateRotation(ptr); + mPhysics->updateRotation(ptr, rotate); if (const auto object = mPhysics->getObject(ptr)) updateNavigatorObject(object); From f031a191b847443c848637b17d0936a43b5070b5 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 23 Jan 2021 20:59:24 +0100 Subject: [PATCH 092/171] Some actors are supposed to spawn on a static object that belong to an adjacent cell. Since actors can be active in 3x3 grid around the player, we need to first load all statics in a 5x5 grid around the player. Split load and unloading in 2 phases. Add an mInactiveCells set into the scene, which contains all cells inside the aforementioned 5x5 grid. These cells contains only heightfields and physics objects of static class. --- apps/openmw/mwclass/static.cpp | 5 + apps/openmw/mwclass/static.hpp | 2 + apps/openmw/mwphysics/physicssystem.cpp | 5 +- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwworld/cellvisitors.hpp | 13 +- apps/openmw/mwworld/class.hpp | 4 + apps/openmw/mwworld/scene.cpp | 405 ++++++++++++++---------- apps/openmw/mwworld/scene.hpp | 15 +- 8 files changed, 269 insertions(+), 182 deletions(-) diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 108c4eaa2..28156c97f 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -63,4 +63,9 @@ namespace MWClass return MWWorld::Ptr(cell.insert(ref), &cell); } + + bool Static::isStatic() const + { + return true; + } } diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index f856e9fd9..d0f4913f0 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -25,6 +25,8 @@ namespace MWClass static void registerSelf(); std::string getModel(const MWWorld::ConstPtr &ptr) const override; + + bool isStatic() const override; }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 68cec48bc..5a9a0be83 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -469,7 +469,7 @@ namespace MWPhysics mAnimatedObjects.insert(obj.get()); } - void PhysicsSystem::remove(const MWWorld::Ptr &ptr) + void PhysicsSystem::remove(const MWWorld::Ptr &ptr, bool keepObject) { ObjectMap::iterator found = mObjects.find(ptr); if (found != mObjects.end()) @@ -479,7 +479,8 @@ namespace MWPhysics mAnimatedObjects.erase(found->second.get()); - mObjects.erase(found); + if (!keepObject) + mObjects.erase(found); } ActorMap::iterator foundActor = mActors.find(ptr); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index c61b368f8..715a6cd1a 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -138,7 +138,7 @@ namespace MWPhysics Projectile* getProjectile(int projectileId) const; // Object or Actor - void remove (const MWWorld::Ptr& ptr); + void remove (const MWWorld::Ptr& ptr, bool keepObject = false); void updateScale (const MWWorld::Ptr& ptr); void updateRotation (const MWWorld::Ptr& ptr, osg::Quat rotate); diff --git a/apps/openmw/mwworld/cellvisitors.hpp b/apps/openmw/mwworld/cellvisitors.hpp index e68b383b7..5985d06fb 100644 --- a/apps/openmw/mwworld/cellvisitors.hpp +++ b/apps/openmw/mwworld/cellvisitors.hpp @@ -18,12 +18,23 @@ namespace MWWorld if (ptr.getRefData().getBaseNode()) { ptr.getRefData().setBaseNode(nullptr); - mObjects.push_back (ptr); } + mObjects.push_back (ptr); return true; } }; + + struct ListObjectsVisitor + { + std::vector mObjects; + + bool operator() (MWWorld::Ptr ptr) + { + mObjects.push_back (ptr); + return true; + } + }; } #endif diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 39fb6fe4c..592552d47 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -318,6 +318,10 @@ namespace MWWorld return false; } + virtual bool isStatic() const { + return false; + } + virtual bool isBipedal(const MWWorld::ConstPtr& ptr) const; virtual bool canFly(const MWWorld::ConstPtr& ptr) const; virtual bool canSwim(const MWWorld::ConstPtr& ptr) const; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 313e9a152..fcf2c4b38 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -105,7 +105,7 @@ namespace } void addObject(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, - MWRender::RenderingManager& rendering, std::set& pagedRefs) + MWRender::RenderingManager& rendering, std::set& pagedRefs, bool onlyPhysics) { if (ptr.getRefData().getBaseNode() || physics.getActor(ptr)) { @@ -113,25 +113,29 @@ namespace return; } - bool useAnim = ptr.getClass().useAnim(); std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS()); + const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); + if (!onlyPhysics) + { + bool useAnim = ptr.getClass().useAnim(); - const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); - if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) - ptr.getClass().insertObjectRendering(ptr, model, rendering); + const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); + if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) + ptr.getClass().insertObjectRendering(ptr, model, rendering); - const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); - setNodeRotation(ptr, rendering, rotation); - ptr.getClass().insertObject (ptr, model, rotation, physics); + setNodeRotation(ptr, rendering, rotation); - if (useAnim) - MWBase::Environment::get().getMechanicsManager()->add(ptr); + if (useAnim) + MWBase::Environment::get().getMechanicsManager()->add(ptr); - if (ptr.getClass().isActor()) - rendering.addWaterRippleEmitter(ptr); + if (ptr.getClass().isActor()) + rendering.addWaterRippleEmitter(ptr); - // Restore effect particles - MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); + // Restore effect particles + MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); + } + if (!physics.getObject(ptr)) + ptr.getClass().insertObject (ptr, model, rotation, physics); } void addObject(const MWWorld::Ptr& ptr, const MWPhysics::PhysicsSystem& physics, DetourNavigator::Navigator& navigator) @@ -202,11 +206,12 @@ namespace { MWWorld::CellStore& mCell; Loading::Listener& mLoadingListener; + bool mOnlyStatics; bool mTest; std::vector mToInsert; - InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool test); + InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool onlyStatics, bool test); bool operator() (const MWWorld::Ptr& ptr); @@ -214,8 +219,8 @@ namespace void insert(AddObject&& addObject); }; - InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool test) - : mCell (cell), mLoadingListener (loadingListener), mTest(test) + InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool onlyStatics, bool test) + : mCell (cell), mLoadingListener (loadingListener), mOnlyStatics(onlyStatics), mTest(test) {} bool InsertVisitor::operator() (const MWWorld::Ptr& ptr) @@ -231,7 +236,7 @@ namespace { for (MWWorld::Ptr& ptr : mToInsert) { - if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled()) + if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled() && ((mOnlyStatics && ptr.getClass().isStatic()) || !mOnlyStatics)) { try { @@ -264,6 +269,16 @@ namespace return std::abs(cellPosition.first) + std::abs(cellPosition.second); } + bool isCellInCollection(int x, int y, MWWorld::Scene::CellStoreCollection& collection) + { + for (auto *cell : collection) + { + assert(cell->getCell()->isExterior()); + if (x == cell->getCell()->getGridX() && y == cell->getCell()->getGridY()) + return true; + } + return false; + } } @@ -315,15 +330,41 @@ namespace MWWorld mRendering.update (duration, paused); } - void Scene::unloadCell (CellStoreCollection::iterator iter, bool test) + void Scene::unloadInactiveCell (CellStore* cell, bool test) + { + assert(mActiveCells.find(cell) == mActiveCells.end()); + assert(mInactiveCells.find(cell) != mInactiveCells.end()); + if (!test) + Log(Debug::Info) << "Unloading cell " << cell->getCell()->getDescription(); + + ListObjectsVisitor visitor; + + cell->forEach(visitor); + for (const auto& ptr : visitor.mObjects) + mPhysics->remove(ptr); + + if (cell->getCell()->isExterior()) + { + const auto cellX = cell->getCell()->getGridX(); + const auto cellY = cell->getCell()->getGridY(); + mPhysics->removeHeightField(cellX, cellY); + } + + mInactiveCells.erase(cell); + } + + void Scene::deactivateCell(CellStore* cell, bool test) { + assert(mInactiveCells.find(cell) != mInactiveCells.end()); + if (mActiveCells.find(cell) == mActiveCells.end()) + return; if (!test) - Log(Debug::Info) << "Unloading cell " << (*iter)->getCell()->getDescription(); + Log(Debug::Info) << "Deactivate cell " << cell->getCell()->getDescription(); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); ListAndResetObjectsVisitor visitor; - (*iter)->forEach(visitor); + cell->forEach(visitor); const auto world = MWBase::Environment::get().getWorld(); for (const auto& ptr : visitor.mObjects) { @@ -334,140 +375,157 @@ namespace MWWorld navigator->removeAgent(world->getPathfindingHalfExtents(ptr)); mRendering.removeActorPath(ptr); } - mPhysics->remove(ptr); + mPhysics->remove(ptr, ptr.getClass().isStatic()); } - const auto cellX = (*iter)->getCell()->getGridX(); - const auto cellY = (*iter)->getCell()->getGridY(); + const auto cellX = cell->getCell()->getGridX(); + const auto cellY = cell->getCell()->getGridY(); - if ((*iter)->getCell()->isExterior()) + if (cell->getCell()->isExterior()) { if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) navigator->removeObject(DetourNavigator::ObjectId(heightField)); - mPhysics->removeHeightField(cellX, cellY); } - if ((*iter)->getCell()->hasWater()) + if (cell->getCell()->hasWater()) navigator->removeWater(osg::Vec2i(cellX, cellY)); - if (const auto pathgrid = world->getStore().get().search(*(*iter)->getCell())) + if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) navigator->removePathgrid(*pathgrid); const auto player = world->getPlayerPtr(); navigator->update(player.getRefData().getPosition().asVec3()); - MWBase::Environment::get().getMechanicsManager()->drop (*iter); + MWBase::Environment::get().getMechanicsManager()->drop (cell); - mRendering.removeCell(*iter); - MWBase::Environment::get().getWindowManager()->removeCell(*iter); + mRendering.removeCell(cell); + MWBase::Environment::get().getWindowManager()->removeCell(cell); - MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (*iter); + MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (cell); - MWBase::Environment::get().getSoundManager()->stopSound (*iter); - mActiveCells.erase(*iter); + MWBase::Environment::get().getSoundManager()->stopSound (cell); + mActiveCells.erase(cell); } - void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test) + void Scene::activateCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test) { - std::pair result = mActiveCells.insert(cell); + assert(mActiveCells.find(cell) == mActiveCells.end()); + assert(mInactiveCells.find(cell) != mInactiveCells.end()); + mActiveCells.insert(cell); + + if (test) + Log(Debug::Info) << "Testing cell " << cell->getCell()->getDescription(); + else + Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); + + const auto world = MWBase::Environment::get().getWorld(); + const auto navigator = world->getNavigator(); + + const int cellX = cell->getCell()->getGridX(); + const int cellY = cell->getCell()->getGridY(); - if(result.second) + if (!test && cell->getCell()->isExterior()) { - if (test) - Log(Debug::Info) << "Testing cell " << cell->getCell()->getDescription(); - else - Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); + if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) + navigator->addObject(DetourNavigator::ObjectId(heightField), *heightField->getShape(), + heightField->getCollisionObject()->getWorldTransform()); + } - float verts = ESM::Land::LAND_SIZE; - float worldsize = ESM::Land::REAL_SIZE; + if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) + navigator->addPathgrid(*cell->getCell(), *pathgrid); - const auto world = MWBase::Environment::get().getWorld(); - const auto navigator = world->getNavigator(); + // register local scripts + // do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice + MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); - const int cellX = cell->getCell()->getGridX(); - const int cellY = cell->getCell()->getGridY(); + if (respawn) + cell->respawn(); + + insertCell (*cell, loadingListener, false, test); - // Load terrain physics first... - if (!test && cell->getCell()->isExterior()) + mRendering.addCell(cell); + if (!test) + { + MWBase::Environment::get().getWindowManager()->addCell(cell); + bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior(); + float waterLevel = cell->getWaterLevel(); + mRendering.setWaterEnabled(waterEnabled); + if (waterEnabled) { - osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); - const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; - if (data) + mPhysics->enableWater(waterLevel); + mRendering.setWaterHeight(waterLevel); + + if (cell->getCell()->isExterior()) { - mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get()); + if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) + navigator->addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, + cell->getWaterLevel(), heightField->getCollisionObject()->getWorldTransform()); } else { - static std::vector defaultHeight; - defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); - mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); + navigator->addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), + cell->getWaterLevel(), btTransform::getIdentity()); } - - if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) - navigator->addObject(DetourNavigator::ObjectId(heightField), *heightField->getShape(), - heightField->getCollisionObject()->getWorldTransform()); } + else + mPhysics->disableWater(); - if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) - navigator->addPathgrid(*cell->getCell(), *pathgrid); - - // register local scripts - // do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice - MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); + const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + navigator->update(player.getRefData().getPosition().asVec3()); - if (respawn) - cell->respawn(); + if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) + mRendering.configureAmbient(cell->getCell()); + } - // ... then references. This is important for adjustPosition to work correctly. - insertCell (*cell, loadingListener, test); + mPreloader->notifyLoaded(cell); + } - mRendering.addCell(cell); - if (!test) - { - MWBase::Environment::get().getWindowManager()->addCell(cell); - bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior(); - float waterLevel = cell->getWaterLevel(); - mRendering.setWaterEnabled(waterEnabled); - if (waterEnabled) - { - mPhysics->enableWater(waterLevel); - mRendering.setWaterHeight(waterLevel); + void Scene::loadInactiveCell (CellStore *cell, Loading::Listener* loadingListener, bool test) + { + assert(mActiveCells.find(cell) == mActiveCells.end()); + assert(mInactiveCells.find(cell) == mInactiveCells.end()); + mInactiveCells.insert(cell); - if (cell->getCell()->isExterior()) - { - if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) - navigator->addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, - cell->getWaterLevel(), heightField->getCollisionObject()->getWorldTransform()); - } - else - { - navigator->addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), - cell->getWaterLevel(), btTransform::getIdentity()); - } - } - else - mPhysics->disableWater(); + if (test) + Log(Debug::Info) << "Testing inactive cell " << cell->getCell()->getDescription(); + else + Log(Debug::Info) << "Loading inactive cell " << cell->getCell()->getDescription(); - const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - navigator->update(player.getRefData().getPosition().asVec3()); + if (!test && cell->getCell()->isExterior()) + { + float verts = ESM::Land::LAND_SIZE; + float worldsize = ESM::Land::REAL_SIZE; - if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) - { + const int cellX = cell->getCell()->getGridX(); + const int cellY = cell->getCell()->getGridY(); - mRendering.configureAmbient(cell->getCell()); - } + osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; + if (data) + { + mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get()); + } + else + { + static std::vector defaultHeight; + defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); + mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); } } - mPreloader->notifyLoaded(cell); + insertCell (*cell, loadingListener, true, test); } void Scene::clear() { - CellStoreCollection::iterator active = mActiveCells.begin(); - while (active!=mActiveCells.end()) - unloadCell (active++); + for (auto iter = mInactiveCells.begin(); iter!=mInactiveCells.end(); ) + { + auto* cell = *iter++; + deactivateCell(cell); + unloadInactiveCell (cell); + } assert(mActiveCells.empty()); + assert(mInactiveCells.empty()); mCurrentCell = nullptr; mPreloader->clear(); @@ -510,20 +568,24 @@ namespace MWWorld void Scene::changeCellGrid (const osg::Vec3f &pos, int playerCellX, int playerCellY, bool changeEvent) { - CellStoreCollection::iterator active = mActiveCells.begin(); - while (active!=mActiveCells.end()) + for (auto iter = mInactiveCells.begin(); iter != mInactiveCells.end(); ) { - if ((*active)->getCell()->isExterior()) + auto* cell = *iter++; + if (cell->getCell()->isExterior()) { - if (std::abs (playerCellX-(*active)->getCell()->getGridX())<=mHalfGridSize && - std::abs (playerCellY-(*active)->getCell()->getGridY())<=mHalfGridSize) - { - // keep cells within the new grid - ++active; - continue; - } + const auto dx = std::abs(playerCellX - cell->getCell()->getGridX()); + const auto dy = std::abs(playerCellY - cell->getCell()->getGridY()); + if (dx > mHalfGridSize || dy > mHalfGridSize) + deactivateCell(cell); + + if (dx > mHalfGridSize+1 || dy > mHalfGridSize+1) + unloadInactiveCell(cell); + } + else + { + deactivateCell(cell); + unloadInactiveCell(cell); } - unloadCell (active++); } mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY); @@ -535,32 +597,24 @@ namespace MWWorld mRendering.getPagedRefnums(newGrid, mPagedRefs); std::size_t refsToLoad = 0; - std::vector> cellsPositionsToLoad; - // get the number of refs to load - for (int x = playerCellX - mHalfGridSize; x <= playerCellX + mHalfGridSize; ++x) + const auto cellsToLoad = [&playerCellX,&playerCellY,&refsToLoad](CellStoreCollection& collection, int range) -> std::vector> { - for (int y = playerCellY - mHalfGridSize; y <= playerCellY + mHalfGridSize; ++y) + std::vector> cellsPositionsToLoad; + for (int x = playerCellX - range; x <= playerCellX + range; ++x) { - CellStoreCollection::iterator iter = mActiveCells.begin(); - - while (iter!=mActiveCells.end()) + for (int y = playerCellY - range; y <= playerCellY + range; ++y) { - assert ((*iter)->getCell()->isExterior()); - - if (x==(*iter)->getCell()->getGridX() && - y==(*iter)->getCell()->getGridY()) - break; - - ++iter; - } - - if (iter==mActiveCells.end()) - { - refsToLoad += MWBase::Environment::get().getWorld()->getExterior(x, y)->count(); - cellsPositionsToLoad.emplace_back(x, y); + if (!isCellInCollection(x, y, collection)) + { + refsToLoad += MWBase::Environment::get().getWorld()->getExterior(x, y)->count(); + cellsPositionsToLoad.emplace_back(x, y); + } } } - } + return cellsPositionsToLoad; + }; + auto cellsPositionsToLoad = cellsToLoad(mActiveCells,mHalfGridSize); + auto cellsPositionsToLoadInactive = cellsToLoad(mInactiveCells,mHalfGridSize+1); Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); @@ -584,30 +638,26 @@ namespace MWWorld return getCellPositionPriority(lhs) < getCellPositionPriority(rhs); }); + std::sort(cellsPositionsToLoadInactive.begin(), cellsPositionsToLoadInactive.end(), + [&] (const std::pair& lhs, const std::pair& rhs) { + return getCellPositionPriority(lhs) < getCellPositionPriority(rhs); + }); + // Load cells - for (const auto& cellPosition : cellsPositionsToLoad) + for (const auto& [x,y] : cellsPositionsToLoadInactive) { - const auto x = cellPosition.first; - const auto y = cellPosition.second; - - CellStoreCollection::iterator iter = mActiveCells.begin(); - - while (iter != mActiveCells.end()) + if (!isCellInCollection(x, y, mInactiveCells)) { - assert ((*iter)->getCell()->isExterior()); - - if (x == (*iter)->getCell()->getGridX() && - y == (*iter)->getCell()->getGridY()) - break; - - ++iter; + CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); + loadInactiveCell (cell, loadingListener); } - - if (iter == mActiveCells.end()) + } + for (const auto& [x,y] : cellsPositionsToLoad) + { + if (!isCellInCollection(x, y, mActiveCells)) { CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); - - loadCell (cell, loadingListener, changeEvent); + activateCell (cell, loadingListener, changeEvent); } } @@ -640,7 +690,8 @@ namespace MWWorld CellStoreCollection::iterator iter = mActiveCells.begin(); CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(it->mData.mX, it->mData.mY); - loadCell (cell, loadingListener, false, true); + loadInactiveCell (cell, loadingListener, true); + activateCell (cell, loadingListener, false, true); iter = mActiveCells.begin(); while (iter != mActiveCells.end()) @@ -648,7 +699,8 @@ namespace MWWorld if (it->isExterior() && it->mData.mX == (*iter)->getCell()->getGridX() && it->mData.mY == (*iter)->getCell()->getGridY()) { - unloadCell(iter, true); + deactivateCell(*iter, true); + unloadInactiveCell (*iter, true); break; } @@ -686,7 +738,8 @@ namespace MWWorld loadingListener->setLabel("Testing interior cells ("+std::to_string(i)+"/"+std::to_string(cells.getIntSize())+")..."); CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(it->mName); - loadCell (cell, loadingListener, false, true); + loadInactiveCell (cell, loadingListener, true); + activateCell (cell, loadingListener, false, true); CellStoreCollection::iterator iter = mActiveCells.begin(); while (iter != mActiveCells.end()) @@ -695,7 +748,8 @@ namespace MWWorld if (it->mName == (*iter)->getCell()->mName) { - unloadCell(iter, true); + deactivateCell(*iter, true); + unloadInactiveCell (*iter, true); break; } @@ -818,15 +872,21 @@ namespace MWWorld Log(Debug::Info) << "Changing to interior"; // unload - CellStoreCollection::iterator active = mActiveCells.begin(); - while (active!=mActiveCells.end()) - unloadCell (active++); + for (auto iter = mInactiveCells.begin(); iter!=mInactiveCells.end(); ) + { + auto* cell = *iter++; + deactivateCell(cell); + unloadInactiveCell(cell); + } + assert(mActiveCells.empty()); + assert(mInactiveCells.empty()); loadingListener->setProgressRange(cell->count()); // Load cell. mPagedRefs.clear(); - loadCell (cell, loadingListener, changeEvent); + loadInactiveCell (cell, loadingListener); + activateCell (cell, loadingListener, changeEvent); changePlayerCell(cell, position, adjustPlayerPos); @@ -874,23 +934,26 @@ namespace MWWorld mCellChanged = false; } - void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test) + void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyStatics, bool test) { - InsertVisitor insertVisitor (cell, *loadingListener, test); + InsertVisitor insertVisitor (cell, *loadingListener, onlyStatics, test); cell.forEach (insertVisitor); - insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs); }); - insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs, onlyStatics); }); + if (!onlyStatics) + { + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); - // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order - PositionVisitor posVisitor; - cell.forEach (posVisitor); + // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order + PositionVisitor posVisitor; + cell.forEach (posVisitor); + } } void Scene::addObjectToScene (const Ptr& ptr) { try { - addObject(ptr, *mPhysics, mRendering, mPagedRefs); + addObject(ptr, *mPhysics, mRendering, mPagedRefs, false); addObject(ptr, *mPhysics, mNavigator); MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index a70d3ccdd..33c7b78d0 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -65,13 +65,13 @@ namespace MWWorld class Scene { public: - - typedef std::set CellStoreCollection; + using CellStoreCollection = std::set; private: CellStore* mCurrentCell; // the cell the player is in CellStoreCollection mActiveCells; + CellStoreCollection mInactiveCells; bool mCellChanged; MWPhysics::PhysicsSystem *mPhysics; MWRender::RenderingManager& mRendering; @@ -92,7 +92,7 @@ namespace MWWorld std::set mPagedRefs; - void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test = false); + void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyStatics, bool test = false); osg::Vec2i mCurrentGridCenter; // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center @@ -108,6 +108,11 @@ namespace MWWorld osg::Vec4i gridCenterToBounds(const osg::Vec2i ¢erCell) const; osg::Vec2i getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i *currentGridCenter = nullptr) const; + void unloadInactiveCell (CellStore* cell, bool test = false); + void deactivateCell (CellStore* cell, bool test = false); + void activateCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test = false); + void loadInactiveCell (CellStore *cell, Loading::Listener* loadingListener, bool test = false); + public: Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics, @@ -119,10 +124,6 @@ namespace MWWorld void preloadTerrain(const osg::Vec3f& pos, bool sync=false); void reloadTerrain(); - void unloadCell (CellStoreCollection::iterator iter, bool test = false); - - void loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test = false); - void playerMoved (const osg::Vec3f& pos); void changePlayerCell (CellStore* newCell, const ESM::Position& position, bool adjustPlayerPos); From f219c5992bc6efe688ab128915f2d36c39da47a0 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 24 Jan 2021 11:28:26 +0100 Subject: [PATCH 093/171] Add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cede01f63..64528e5c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ Bug #5367: Selecting a spell on an enchanted item per hotkey always plays the equip sound Bug #5369: Spawnpoint in the Grazelands doesn't produce oversized creatures Bug #5370: Opening an unlocked but trapped door uses the key + Bug #5379: Wandering NPCs falling through cantons Bug #5384: openmw-cs: deleting an instance requires reload of scene window to show in editor Bug #5387: Move/MoveWorld don't update the object's cell properly Bug #5391: Races Redone 1.2 bodies don't show on the inventory From a2171875a069c2704ee035f1a407eaacee2a3979 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 24 Jan 2021 15:15:51 +0100 Subject: [PATCH 094/171] Prevent nullptr access --- apps/openmw/mwworld/projectilemanager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index c87f62505..092ce270e 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -428,6 +428,9 @@ namespace MWWorld { for (auto& projectileState : mProjectiles) { + if (projectileState.mToDelete) + continue; + auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); if (!projectile->isActive()) continue; From fe815d3d8dcfe5007b229082370113865c5ba930 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sun, 24 Jan 2021 15:22:27 +0000 Subject: [PATCH 095/171] Fix memory leak in MWInput mListener wasn't being cleaned up --- apps/openmw/mwinput/bindingsmanager.cpp | 13 ++++++------- apps/openmw/mwinput/bindingsmanager.hpp | 5 +++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwinput/bindingsmanager.cpp b/apps/openmw/mwinput/bindingsmanager.cpp index 18fac6ae2..851e33a87 100644 --- a/apps/openmw/mwinput/bindingsmanager.cpp +++ b/apps/openmw/mwinput/bindingsmanager.cpp @@ -171,16 +171,16 @@ namespace MWInput , mDragDrop(false) { std::string file = userFileExists ? userFile : ""; - mInputBinder = new InputControlSystem(file); - mListener = new BindingsListener(mInputBinder, this); - mInputBinder->setDetectingBindingListener(mListener); + mInputBinder = std::make_unique(file); + mListener = std::make_unique(mInputBinder.get(), this); + mInputBinder->setDetectingBindingListener(mListener.get()); loadKeyDefaults(); loadControllerDefaults(); for (int i = 0; i < A_Last; ++i) { - mInputBinder->getChannel(i)->addListener(mListener); + mInputBinder->getChannel(i)->addListener(mListener.get()); } } @@ -192,7 +192,6 @@ namespace MWInput BindingsManager::~BindingsManager() { mInputBinder->save(mUserFile); - delete mInputBinder; } void BindingsManager::update(float dt) @@ -315,7 +314,7 @@ namespace MWInput && mInputBinder->getMouseButtonBinding(control, ICS::Control::INCREASE) == ICS_MAX_DEVICE_BUTTONS && mInputBinder->getMouseWheelBinding(control, ICS::Control::INCREASE) == ICS::InputControlSystem::MouseWheelClick::UNASSIGNED)) { - clearAllKeyBindings(mInputBinder, control); + clearAllKeyBindings(mInputBinder.get(), control); if (defaultKeyBindings.find(i) != defaultKeyBindings.end() && (force || !mInputBinder->isKeyBound(defaultKeyBindings[i]))) @@ -402,7 +401,7 @@ namespace MWInput if (!controlExists || force || (mInputBinder->getJoystickAxisBinding(control, sFakeDeviceId, ICS::Control::INCREASE) == ICS::InputControlSystem::UNASSIGNED && mInputBinder->getJoystickButtonBinding(control, sFakeDeviceId, ICS::Control::INCREASE) == ICS_MAX_DEVICE_BUTTONS)) { - clearAllControllerBindings(mInputBinder, control); + clearAllControllerBindings(mInputBinder.get(), control); if (defaultButtonBindings.find(i) != defaultButtonBindings.end() && (force || !mInputBinder->isJoystickButtonBound(sFakeDeviceId, defaultButtonBindings[i]))) diff --git a/apps/openmw/mwinput/bindingsmanager.hpp b/apps/openmw/mwinput/bindingsmanager.hpp index 7a44a1a33..74416d3c7 100644 --- a/apps/openmw/mwinput/bindingsmanager.hpp +++ b/apps/openmw/mwinput/bindingsmanager.hpp @@ -1,6 +1,7 @@ #ifndef MWINPUT_MWBINDINGSMANAGER_H #define MWINPUT_MWBINDINGSMANAGER_H +#include #include #include @@ -64,8 +65,8 @@ namespace MWInput private: void setupSDLKeyMappings(); - InputControlSystem* mInputBinder; - BindingsListener* mListener; + std::unique_ptr mInputBinder; + std::unique_ptr mListener; std::string mUserFile; From 3bb551a6f12e1c90f884a82b7806832a38bf2284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Mon, 25 Jan 2021 10:01:39 +0000 Subject: [PATCH 096/171] Show level multipliers in levelup tooltip --- apps/openmw/mwgui/statswindow.cpp | 21 +++++++++++---------- apps/openmw/mwmechanics/npcstats.cpp | 5 ----- apps/openmw/mwmechanics/npcstats.hpp | 2 -- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 66a1ea1ef..4c4450ee0 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -337,21 +337,22 @@ namespace MWGui int max = MWBase::Environment::get().getWorld()->getStore().get().find("iLevelUpTotal")->mValue.getInteger(); getWidget(levelWidget, i==0 ? "Level_str" : "LevelText"); - std::stringstream detail; - for (int i = 0; i < ESM::Attribute::Length; ++i) - { - if (auto increase = PCstats.getLevelUpAttributeIncrease(i)) - detail << (detail.str().empty() ? "" : "\n") << "#{" - << MyGUI::TextIterator::toTagsString(ESM::Attribute::sGmstAttributeIds[i]) - << "} x" << MyGUI::utility::toString(increase); - } - if (!detail.str().empty()) - levelWidget->setUserString("Caption_LevelDetailText", MyGUI::LanguageManager::getInstance().replaceTags(detail.str())); levelWidget->setUserString("RangePosition_LevelProgress", MyGUI::utility::toString(PCstats.getLevelProgress())); levelWidget->setUserString("Range_LevelProgress", MyGUI::utility::toString(max)); levelWidget->setUserString("Caption_LevelProgressText", MyGUI::utility::toString(PCstats.getLevelProgress()) + "/" + MyGUI::utility::toString(max)); } + std::stringstream detail; + for (int attribute = 0; attribute < ESM::Attribute::Length; ++attribute) + { + float mult = PCstats.getLevelupAttributeMultiplier(attribute); + mult = std::min(mult, 100 - PCstats.getAttribute(attribute).getBase()); + if (mult > 1) + detail << (detail.str().empty() ? "" : "\n") << "#{" + << MyGUI::TextIterator::toTagsString(ESM::Attribute::sGmstAttributeIds[attribute]) + << "} x" << MyGUI::utility::toString(mult); + } + levelWidget->setUserString("Caption_LevelDetailText", MyGUI::LanguageManager::getInstance().replaceTags(detail.str())); setFactions(PCstats.getFactionRanks()); setExpelled(PCstats.getExpelled ()); diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 71453cd07..5d19368bf 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -322,11 +322,6 @@ void MWMechanics::NpcStats::updateHealth() setHealth(floor(0.5f * (strength + endurance))); } -int MWMechanics::NpcStats::getLevelUpAttributeIncrease(int attribute) const -{ - return mSkillIncreases[attribute]; -} - int MWMechanics::NpcStats::getLevelupAttributeMultiplier(int attribute) const { int num = mSkillIncreases[attribute]; diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index cab52cb28..9bd8e20ad 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -87,8 +87,6 @@ namespace MWMechanics int getLevelProgress() const; - int getLevelUpAttributeIncrease(int attribute) const; - int getLevelupAttributeMultiplier(int attribute) const; int getSkillIncreasesForSpecialization(int spec) const; From bc2cec86e9ca16abdab140aff40a1739e35c066a Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 24 Dec 2020 04:18:39 +0100 Subject: [PATCH 097/171] Fix bug: NPCs doesn't move if the target is exactly above or exactly below. --- apps/openmw/mwmechanics/pathfinding.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 276321b81..595f9d629 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -309,7 +309,7 @@ namespace MWMechanics if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance)) mPath.pop_front(); - if (mPath.size() == 1 && sqrDistanceIgnoreZ(mPath.front(), position) < destinationTolerance * destinationTolerance) + if (mPath.size() == 1 && (mPath.front() - position).length2() < destinationTolerance * destinationTolerance) mPath.pop_front(); } From 14cf0ce1dc67d561a04a3e5b13fa633af54ca939 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 12 Jan 2020 11:42:47 +0400 Subject: [PATCH 098/171] Implement instanced groundcover --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/engine.cpp | 9 +- apps/openmw/engine.hpp | 2 + apps/openmw/main.cpp | 15 +- apps/openmw/mwgui/settingswindow.cpp | 4 +- apps/openmw/mwrender/groundcover.cpp | 264 ++++++++++++++++++ apps/openmw/mwrender/groundcover.hpp | 69 +++++ apps/openmw/mwrender/objectpaging.cpp | 1 + apps/openmw/mwrender/renderingmanager.cpp | 77 ++++- apps/openmw/mwrender/renderingmanager.hpp | 9 +- apps/openmw/mwrender/vismask.hpp | 4 +- apps/openmw/mwrender/water.cpp | 5 +- apps/openmw/mwworld/cellpreloader.cpp | 7 + apps/openmw/mwworld/cellreflist.hpp | 2 + apps/openmw/mwworld/cellstore.cpp | 13 + apps/openmw/mwworld/contentloader.hpp | 2 +- apps/openmw/mwworld/esmloader.cpp | 18 +- apps/openmw/mwworld/esmloader.hpp | 2 +- apps/openmw/mwworld/esmstore.cpp | 9 + apps/openmw/mwworld/esmstore.hpp | 17 ++ apps/openmw/mwworld/worldimp.cpp | 29 +- apps/openmw/mwworld/worldimp.hpp | 3 +- components/config/gamesettings.cpp | 2 + components/esm/esmreader.cpp | 9 +- components/esm/esmreader.hpp | 6 +- components/esm/loadstat.cpp | 2 + components/esm/loadstat.hpp | 2 + components/resource/scenemanager.cpp | 6 +- components/resource/scenemanager.hpp | 2 +- components/resource/stats.cpp | 4 +- components/shader/shadermanager.cpp | 2 + components/terrain/chunkmanager.cpp | 2 +- components/terrain/chunkmanager.hpp | 2 +- components/terrain/quadtreeworld.cpp | 29 +- components/terrain/quadtreeworld.hpp | 5 +- components/terrain/terraingrid.cpp | 8 + components/terrain/terraingrid.hpp | 1 + components/terrain/world.cpp | 41 ++- components/terrain/world.hpp | 1 + docs/source/reference/modding/extended.rst | 56 +++- .../modding/settings/groundcover.rst | 68 +++++ .../reference/modding/settings/index.rst | 1 + .../reference/modding/settings/shaders.rst | 1 + .../reference/modding/settings/water.rst | 3 +- files/mygui/openmw_settings_window.layout | 1 + files/settings-default.cfg | 19 ++ files/shaders/CMakeLists.txt | 2 + files/shaders/groundcover_fragment.glsl | 93 ++++++ files/shaders/groundcover_vertex.glsl | 143 ++++++++++ files/shaders/lighting.glsl | 15 +- 50 files changed, 1015 insertions(+), 74 deletions(-) create mode 100644 apps/openmw/mwrender/groundcover.cpp create mode 100644 apps/openmw/mwrender/groundcover.hpp create mode 100644 docs/source/reference/modding/settings/groundcover.rst create mode 100644 files/shaders/groundcover_fragment.glsl create mode 100644 files/shaders/groundcover_vertex.glsl diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index c01cbe60c..fdc47e8d7 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -21,7 +21,7 @@ add_openmw_dir (mwrender actors objects renderingmanager animation rotatecontroller sky npcanimation vismask creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager bulletdebugdraw globalmap characterpreview camera viewovershoulder localmap water terrainstorage ripplesimulation - renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging + renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover ) add_openmw_dir (mwinput diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index ead2726cd..103d06f31 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -460,6 +460,11 @@ void OMW::Engine::addContentFile(const std::string& file) mContentFiles.push_back(file); } +void OMW::Engine::addGroundcoverFile(const std::string& file) +{ + mGroundcoverFiles.emplace_back(file); +} + void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame) { mSkipMenu = skipMenu; @@ -720,8 +725,8 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) } // Create the world - mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), - mFileCollections, mContentFiles, mEncoder, mActivationDistanceOverride, mCellName, + mEnvironment.setWorld(new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), + mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder, mActivationDistanceOverride, mCellName, mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string())); mEnvironment.getWorld()->setupPlayer(); diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 3dd1a69b2..ff362f4b6 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -85,6 +85,7 @@ namespace OMW osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation; std::string mCellName; std::vector mContentFiles; + std::vector mGroundcoverFiles; bool mSkipMenu; bool mUseSound; bool mCompileAll; @@ -155,6 +156,7 @@ namespace OMW * @param file - filename (extension is required) */ void addContentFile(const std::string& file); + void addGroundcoverFile(const std::string& file); /// Disable or enable all sounds void setSoundUsage(bool soundUsage); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 8eaac36e8..709ffda2c 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -62,6 +62,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("content", bpo::value()->default_value(Files::EscapeStringVector(), "") ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon") + ("groundcover", bpo::value()->default_value(Files::EscapeStringVector(), "") + ->multitoken()->composing(), "groundcover content file(s): esm/esp, or omwgame/omwaddon") + ("no-sound", bpo::value()->implicit_value(true) ->default_value(false), "disable all sounds") @@ -190,11 +193,15 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat return false; } - StringsVector::const_iterator it(content.begin()); - StringsVector::const_iterator end(content.end()); - for (; it != end; ++it) + for (auto& file : content) + { + engine.addContentFile(file); + } + + StringsVector groundcover = variables["groundcover"].as().toStdStringVector(); + for (auto& file : groundcover) { - engine.addContentFile(*it); + engine.addGroundcoverFile(file); } // startup-settings diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 68dac4a95..538b3db5e 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -269,7 +269,7 @@ namespace MWGui mWaterTextureSize->setIndexSelected(2); int waterReflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); - waterReflectionDetail = std::min(4, std::max(0, waterReflectionDetail)); + waterReflectionDetail = std::min(5, std::max(0, waterReflectionDetail)); mWaterReflectionDetail->setIndexSelected(waterReflectionDetail); mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video")); @@ -353,7 +353,7 @@ namespace MWGui void SettingsWindow::onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos) { - unsigned int level = std::min((unsigned int)4, (unsigned int)pos); + unsigned int level = std::min((unsigned int)5, (unsigned int)pos); Settings::Manager::setInt("reflection detail", "Water", level); apply(); } diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp new file mode 100644 index 000000000..b9fdc2e28 --- /dev/null +++ b/apps/openmw/mwrender/groundcover.cpp @@ -0,0 +1,264 @@ +#include "groundcover.hpp" + +#include +#include + +#include + +#include "apps/openmw/mwworld/esmstore.hpp" +#include "apps/openmw/mwbase/environment.hpp" +#include "apps/openmw/mwbase/world.hpp" + +#include "vismask.hpp" + +namespace MWRender +{ + void GroundcoverUpdater::setWindSpeed(float windSpeed) + { + mWindSpeed = windSpeed; + } + + void GroundcoverUpdater::setPlayerPos(osg::Vec3f playerPos) + { + mPlayerPos = playerPos; + } + + void GroundcoverUpdater::setDefaults(osg::StateSet *stateset) + { + osg::ref_ptr windUniform = new osg::Uniform("windSpeed", 0.0f); + stateset->addUniform(windUniform.get()); + + osg::ref_ptr playerPosUniform = new osg::Uniform("playerPos", osg::Vec3f(0.f, 0.f, 0.f)); + stateset->addUniform(playerPosUniform.get()); + } + + void GroundcoverUpdater::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) + { + osg::ref_ptr windUniform = stateset->getUniform("windSpeed"); + if (windUniform != nullptr) + windUniform->set(mWindSpeed); + + osg::ref_ptr playerPosUniform = stateset->getUniform("playerPos"); + if (playerPosUniform != nullptr) + playerPosUniform->set(mPlayerPos); + } + + class InstancingVisitor : public osg::NodeVisitor + { + public: + InstancingVisitor(std::vector& instances, osg::Vec3f& chunkPosition) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mInstances(instances) + , mChunkPosition(chunkPosition) + { + } + + void apply(osg::Node& node) override + { + osg::ref_ptr ss = node.getStateSet(); + if (ss != nullptr) + { + removeAlpha(ss); + } + + traverse(node); + } + + void apply(osg::Geometry& geom) override + { + for (unsigned int i = 0; i < geom.getNumPrimitiveSets(); ++i) + { + geom.getPrimitiveSet(i)->setNumInstances(mInstances.size()); + } + + osg::ref_ptr transforms = new osg::Vec4Array(mInstances.size()); + osg::BoundingBox box; + float radius = geom.getBoundingBox().radius(); + for (unsigned int i = 0; i < transforms->getNumElements(); i++) + { + osg::Vec3f pos(mInstances[i].mPos.asVec3()); + osg::Vec3f relativePos = pos - mChunkPosition; + (*transforms)[i] = osg::Vec4f(relativePos, mInstances[i].mScale); + + // Use an additional margin due to groundcover animation + float instanceRadius = radius * mInstances[i].mScale * 1.1f; + osg::BoundingSphere instanceBounds(relativePos, instanceRadius); + box.expandBy(instanceBounds); + } + + geom.setInitialBound(box); + + osg::ref_ptr rotations = new osg::Vec3Array(mInstances.size()); + for (unsigned int i = 0; i < rotations->getNumElements(); i++) + { + (*rotations)[i] = mInstances[i].mPos.asRotationVec3(); + } + + geom.setVertexAttribArray(6, transforms.get(), osg::Array::BIND_PER_VERTEX); + geom.setVertexAttribArray(7, rotations.get(), osg::Array::BIND_PER_VERTEX); + + osg::ref_ptr ss = geom.getOrCreateStateSet(); + ss->setAttribute(new osg::VertexAttribDivisor(6, 1)); + ss->setAttribute(new osg::VertexAttribDivisor(7, 1)); + + removeAlpha(ss); + + traverse(geom); + } + private: + std::vector mInstances; + osg::Vec3f mChunkPosition; + + void removeAlpha(osg::StateSet* stateset) + { + // MGE uses default alpha settings for groundcover, so we can not rely on alpha properties + stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); + stateset->removeMode(GL_ALPHA_TEST); + stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); + stateset->removeMode(GL_BLEND); + stateset->setRenderBinToInherit(); + } + }; + + class DensityCalculator + { + public: + DensityCalculator(float density) + : mDensity(density) + { + } + + bool isInstanceEnabled() + { + if (mDensity >= 1.f) return true; + + mCurrentGroundcover += mDensity; + if (mCurrentGroundcover < 1.f) return false; + + mCurrentGroundcover -= 1.f; + + return true; + } + void reset() { mCurrentGroundcover = 0.f; } + + private: + float mCurrentGroundcover = 0.f; + float mDensity = 0.f; + }; + + inline bool isInChunkBorders(ESM::CellRef& ref, osg::Vec2f& minBound, osg::Vec2f& maxBound) + { + osg::Vec2f size = maxBound - minBound; + if (size.x() >=1 && size.y() >=1) return true; + + osg::Vec3f pos = ref.mPos.asVec3(); + osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE; + if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y()) + || (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) || (minBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y())) + return false; + + return true; + } + + osg::ref_ptr Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) + { + ChunkId id = std::make_tuple(center, size, activeGrid); + + osg::ref_ptr obj = mCache->getRefFromObjectCache(id); + if (obj) + return obj->asNode(); + else + { + InstanceMap instances; + collectInstances(instances, size, center); + osg::ref_ptr node = createChunk(instances, center); + mCache->addEntryToObjectCache(id, node.get()); + return node; + } + } + + Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density) + : GenericResourceManager(nullptr) + , mSceneManager(sceneManager) + , mDensity(density) + { + } + + void Groundcover::collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center) + { + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + osg::Vec2f minBound = (center - osg::Vec2f(size/2.f, size/2.f)); + osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f)); + DensityCalculator calculator(mDensity); + std::vector esm; + osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size/2.f), std::floor(center.y() - size/2.f)); + for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX) + { + for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY) + { + const ESM::Cell* cell = store.get().searchStatic(cellX, cellY); + if (!cell) continue; + + calculator.reset(); + for (size_t i=0; imContextList.size(); ++i) + { + unsigned int index = cell->mContextList.at(i).index; + if (esm.size() <= index) + esm.resize(index+1); + cell->restore(esm[index], i); + ESM::CellRef ref; + ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; + bool deleted = false; + while(cell->getNextRef(esm[index], ref, deleted)) + { + if (deleted) continue; + Misc::StringUtils::lowerCaseInPlace(ref.mRefID); + std::string model; + if (!store.isGroundcover(ref.mRefID, model)) continue; + if (model.empty()) continue; + + if (!calculator.isInstanceEnabled()) continue; + if (!isInChunkBorders(ref, minBound, maxBound)) continue; + + model = "meshes/" + model; + instances[model].emplace_back(ref, model); + } + } + } + } + } + + osg::ref_ptr Groundcover::createChunk(InstanceMap& instances, const osg::Vec2f& center) + { + osg::ref_ptr group = new osg::Group; + osg::Vec3f worldCenter = osg::Vec3f(center.x(), center.y(), 0)*ESM::Land::REAL_SIZE; + for (auto& pair : instances) + { + const osg::Node* temp = mSceneManager->getTemplate(pair.first); + osg::ref_ptr node = static_cast(temp->clone(osg::CopyOp::DEEP_COPY_ALL&(~osg::CopyOp::DEEP_COPY_TEXTURES))); + + // Keep link to original mesh to keep it in cache + group->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(temp)); + + InstancingVisitor visitor(pair.second, worldCenter); + node->accept(visitor); + group->addChild(node); + } + + group->getBound(); + group->setNodeMask(Mask_Groundcover); + mSceneManager->recreateShaders(group, "groundcover", false, true); + + return group; + } + + unsigned int Groundcover::getNodeMask() + { + return Mask_Groundcover; + } + + void Groundcover::reportStats(unsigned int frameNumber, osg::Stats *stats) const + { + stats->setAttribute(frameNumber, "Groundcover Chunk", mCache->getCacheSize()); + } +} diff --git a/apps/openmw/mwrender/groundcover.hpp b/apps/openmw/mwrender/groundcover.hpp new file mode 100644 index 000000000..cd80978be --- /dev/null +++ b/apps/openmw/mwrender/groundcover.hpp @@ -0,0 +1,69 @@ +#ifndef OPENMW_MWRENDER_GROUNDCOVER_H +#define OPENMW_MWRENDER_GROUNDCOVER_H + +#include +#include +#include +#include + +namespace MWRender +{ + class GroundcoverUpdater : public SceneUtil::StateSetUpdater + { + public: + GroundcoverUpdater() + : mWindSpeed(0.f) + , mPlayerPos(osg::Vec3f()) + { + } + + void setWindSpeed(float windSpeed); + void setPlayerPos(osg::Vec3f playerPos); + + protected: + void setDefaults(osg::StateSet *stateset) override; + void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override; + + private: + float mWindSpeed; + osg::Vec3f mPlayerPos; + }; + + typedef std::tuple ChunkId; // Center, Size, ActiveGrid + class Groundcover : public Resource::GenericResourceManager, public Terrain::QuadTreeWorld::ChunkManager + { + public: + Groundcover(Resource::SceneManager* sceneManager, float density); + ~Groundcover() = default; + + osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override; + + unsigned int getNodeMask() override; + + void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; + + struct GroundcoverEntry + { + ESM::Position mPos; + float mScale; + std::string mModel; + + GroundcoverEntry(const ESM::CellRef& ref, const std::string& model) + { + mPos = ref.mPos; + mScale = ref.mScale; + mModel = model; + } + }; + + private: + Resource::SceneManager* mSceneManager; + float mDensity; + + typedef std::map> InstanceMap; + osg::ref_ptr createChunk(InstanceMap& instances, const osg::Vec2f& center); + void collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center); + }; +} + +#endif diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index d8e856e76..478fde0f8 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -398,6 +398,7 @@ namespace MWRender int type = store.findStatic(ref.mRefID); if (!typeFilter(type,size>=2)) continue; if (deleted) { refs.erase(ref.mRefNum); continue; } + if (store.isGroundcover(ref.mRefID)) continue; refs[ref.mRefNum] = ref; } } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6ce431d2e..c755f46f8 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -68,10 +69,10 @@ #include "fogmanager.hpp" #include "objectpaging.hpp" #include "screenshotmanager.hpp" +#include "groundcover.hpp" namespace MWRender { - class StateUpdater : public SceneUtil::StateSetUpdater { public: @@ -243,6 +244,10 @@ namespace MWRender globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0"; globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"; + float groundcoverDistance = (Constants::CellSizeInUnits * Settings::Manager::getInt("distance", "Groundcover") - 1024) * 0.93; + globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * Settings::Manager::getFloat("fade start", "Groundcover")); + globalDefines["groundcoverFadeEnd"] = std::to_string(groundcoverDistance); + // It is unnecessary to stop/start the viewer as no frames are being rendered yet. mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines); @@ -269,7 +274,8 @@ namespace MWRender const bool useTerrainNormalMaps = Settings::Manager::getBool("auto use terrain normal maps", "Shaders"); const bool useTerrainSpecularMaps = Settings::Manager::getBool("auto use terrain specular maps", "Shaders"); - mTerrainStorage = new TerrainStorage(mResourceSystem, normalMapPattern, heightMapPattern, useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps); + mTerrainStorage.reset(new TerrainStorage(mResourceSystem, normalMapPattern, heightMapPattern, useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps)); + const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain"); if (Settings::Manager::getBool("distant terrain", "Terrain")) { @@ -277,12 +283,11 @@ namespace MWRender int compMapPower = Settings::Manager::getInt("composite map level", "Terrain"); compMapPower = std::max(-3, compMapPower); float compMapLevel = pow(2, compMapPower); - const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain"); const int vertexLodMod = Settings::Manager::getInt("vertex lod mod", "Terrain"); float maxCompGeometrySize = Settings::Manager::getFloat("max composite geometry size", "Terrain"); maxCompGeometrySize = std::max(maxCompGeometrySize, 1.f); mTerrain.reset(new Terrain::QuadTreeWorld( - sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug, + sceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug, compMapResolution, compMapLevel, lodFactor, vertexLodMod, maxCompGeometrySize)); if (Settings::Manager::getBool("object paging", "Terrain")) { @@ -292,11 +297,43 @@ namespace MWRender } } else - mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug)); + mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug)); mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); mTerrain->setWorkQueue(mWorkQueue.get()); + if (Settings::Manager::getBool("enabled", "Groundcover")) + { + osg::ref_ptr groundcoverRoot = new osg::Group; + groundcoverRoot->setNodeMask(Mask_Groundcover); + groundcoverRoot->setName("Groundcover Root"); + sceneRoot->addChild(groundcoverRoot); + + // Force a unified alpha handling instead of data from meshes + osg::ref_ptr alpha = new osg::AlphaFunc(osg::AlphaFunc::GEQUAL, 128.f/255.f); + groundcoverRoot->getOrCreateStateSet()->setAttributeAndModes(alpha.get(), osg::StateAttribute::ON); + + mGroundcoverUpdater = new GroundcoverUpdater; + groundcoverRoot->addUpdateCallback(mGroundcoverUpdater); + + float chunkSize = Settings::Manager::getFloat("min chunk size", "Groundcover"); + if (chunkSize >= 1.0f) + chunkSize = 1.0f; + else if (chunkSize >= 0.5f) + chunkSize = 0.5f; + else if (chunkSize >= 0.25f) + chunkSize = 0.25f; + else if (chunkSize != 0.125f) + chunkSize = 0.125f; + + float density = Settings::Manager::getFloat("density", "Groundcover"); + density = std::clamp(density, 0.f, 1.f); + + mGroundcoverWorld.reset(new Terrain::QuadTreeWorld(groundcoverRoot, mTerrainStorage.get(), Mask_Groundcover, lodFactor, chunkSize)); + mGroundcover.reset(new Groundcover(mResourceSystem->getSceneManager(), density)); + static_cast(mGroundcoverWorld.get())->addChunkManager(mGroundcover.get()); + mResourceSystem->addResourceManager(mGroundcover.get()); + } // water goes after terrain for correct waterculling order mWater.reset(new Water(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath)); @@ -508,7 +545,11 @@ namespace MWRender mWater->changeCell(store); if (store->getCell()->isExterior()) + { mTerrain->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + if (mGroundcoverWorld) + mGroundcoverWorld->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + } } void RenderingManager::removeCell(const MWWorld::CellStore *store) { @@ -517,7 +558,11 @@ namespace MWRender mObjects->removeCell(store); if (store->getCell()->isExterior()) + { mTerrain->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + if (mGroundcoverWorld) + mGroundcoverWorld->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + } mWater->removeCell(store); } @@ -527,6 +572,8 @@ namespace MWRender if (!enable) mWater->setCullCallback(nullptr); mTerrain->enable(enable); + if (mGroundcoverWorld) + mGroundcoverWorld->enable(enable); } void RenderingManager::setSkyEnabled(bool enabled) @@ -612,6 +659,16 @@ namespace MWRender mEffectManager->update(dt); mSky->update(dt); mWater->update(dt); + + if (mGroundcoverUpdater) + { + const MWWorld::Ptr& player = mPlayerAnimation->getPtr(); + osg::Vec3f playerPos(player.getRefData().getPosition().asVec3()); + + float windSpeed = mSky->getBaseWindSpeed(); + mGroundcoverUpdater->setWindSpeed(windSpeed); + mGroundcoverUpdater->setPlayerPos(playerPos); + } } updateNavMesh(); @@ -805,7 +862,7 @@ namespace MWRender mIntersectionVisitor->setIntersector(intersector); int mask = ~0; - mask &= ~(Mask_RenderToTexture|Mask_Sky|Mask_Debug|Mask_Effect|Mask_Water|Mask_SimpleWater); + mask &= ~(Mask_RenderToTexture|Mask_Sky|Mask_Debug|Mask_Effect|Mask_Water|Mask_SimpleWater|Mask_Groundcover); if (ignorePlayer) mask &= ~(Mask_Player); if (ignoreActors) @@ -964,6 +1021,12 @@ namespace MWRender fov = std::min(mFieldOfView, 140.f); float distanceMult = std::cos(osg::DegreesToRadians(fov)/2.f); mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f/distanceMult : 1.f)); + + if (mGroundcoverWorld) + { + int groundcoverDistance = Constants::CellSizeInUnits * Settings::Manager::getInt("distance", "Groundcover"); + mGroundcoverWorld->setViewDistance(groundcoverDistance * (distanceMult ? 1.f/distanceMult : 1.f)); + } } void RenderingManager::updateTextureFiltering() @@ -1158,6 +1221,8 @@ namespace MWRender void RenderingManager::setActiveGrid(const osg::Vec4i &grid) { mTerrain->setActiveGrid(grid); + if (mGroundcoverWorld) + mGroundcoverWorld->setActiveGrid(grid); } bool RenderingManager::pagingEnableObject(int type, const MWWorld::ConstPtr& ptr, bool enabled) { diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 39d1a0194..a7afa2fa0 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -70,7 +70,7 @@ namespace DetourNavigator namespace MWRender { - + class GroundcoverUpdater; class StateUpdater; class EffectManager; @@ -88,6 +88,7 @@ namespace MWRender class ActorsPaths; class RecastMesh; class ObjectPaging; + class Groundcover; class RenderingManager : public MWRender::RenderingInterface { @@ -261,6 +262,8 @@ namespace MWRender osg::ref_ptr mSceneRoot; Resource::ResourceSystem* mResourceSystem; + osg::ref_ptr mGroundcoverUpdater; + osg::ref_ptr mWorkQueue; osg::ref_ptr mUnrefQueue; @@ -275,8 +278,10 @@ namespace MWRender std::unique_ptr mObjects; std::unique_ptr mWater; std::unique_ptr mTerrain; - TerrainStorage* mTerrainStorage; + std::unique_ptr mGroundcoverWorld; + std::unique_ptr mTerrainStorage; std::unique_ptr mObjectPaging; + std::unique_ptr mGroundcover; std::unique_ptr mSky; std::unique_ptr mFog; std::unique_ptr mScreenshotManager; diff --git a/apps/openmw/mwrender/vismask.hpp b/apps/openmw/mwrender/vismask.hpp index f9f9dc74c..bc3d3f192 100644 --- a/apps/openmw/mwrender/vismask.hpp +++ b/apps/openmw/mwrender/vismask.hpp @@ -53,7 +53,9 @@ namespace MWRender Mask_PreCompile = (1<<18), // Set on a camera's cull mask to enable the LightManager - Mask_Lighting = (1<<19) + Mask_Lighting = (1<<19), + + Mask_Groundcover = (1<<20), }; } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index e786ce937..1cc5a3cb7 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -244,7 +244,7 @@ public: setCullCallback(new InheritViewPointCallback); setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); - setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Static|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); + setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Static|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting|Mask_Groundcover); setNodeMask(Mask_RenderToTexture); setViewport(0, 0, rttSize, rttSize); @@ -372,12 +372,13 @@ public: void setInterior(bool isInterior) { int reflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); - reflectionDetail = std::min(4, std::max(isInterior ? 2 : 0, reflectionDetail)); + reflectionDetail = std::min(5, std::max(isInterior ? 2 : 0, reflectionDetail)); unsigned int extraMask = 0; if(reflectionDetail >= 1) extraMask |= Mask_Terrain; if(reflectionDetail >= 2) extraMask |= Mask_Static; if(reflectionDetail >= 3) extraMask |= Mask_Effect|Mask_ParticleSystem|Mask_Object; if(reflectionDetail >= 4) extraMask |= Mask_Player|Mask_Actor; + if(reflectionDetail >= 5) extraMask |= Mask_Groundcover; setCullMask(Mask_Scene|Mask_Sky|Mask_Lighting|extraMask); } diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 31af5b24b..937491f62 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -36,6 +36,13 @@ namespace MWWorld virtual bool operator()(const MWWorld::Ptr& ptr) { + if (ptr.getTypeName()==typeid (ESM::Static).name()) + { + const MWWorld::LiveCellRef *ref = ptr.get(); + if (ref->mBase->mIsGroundcover) + return true; + } + ptr.getClass().getModelsToPreload(ptr, mOut); return true; diff --git a/apps/openmw/mwworld/cellreflist.hpp b/apps/openmw/mwworld/cellreflist.hpp index 30be4a661..69161c840 100644 --- a/apps/openmw/mwworld/cellreflist.hpp +++ b/apps/openmw/mwworld/cellreflist.hpp @@ -24,6 +24,8 @@ namespace MWWorld /// all methods are known. void load (ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore); + inline bool ignoreInstance (const X* ptr); + LiveRef &insert (const LiveRef &item) { mList.push_back(item); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index b48fe74a6..d8e2eb65f 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -169,6 +169,17 @@ namespace namespace MWWorld { + template + bool CellRefList::ignoreInstance (const X* ptr) + { + return false; + } + + template <> + bool CellRefList::ignoreInstance (const ESM::Static* ptr) + { + return ptr->mIsGroundcover; + } template void CellRefList::load(ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore) @@ -177,6 +188,8 @@ namespace MWWorld if (const X *ptr = store.search (ref.mRefID)) { + if (ignoreInstance(ptr)) return; + typename std::list::iterator iter = std::find(mList.begin(), mList.end(), ref.mRefNum); diff --git a/apps/openmw/mwworld/contentloader.hpp b/apps/openmw/mwworld/contentloader.hpp index b529ae9db..b559df083 100644 --- a/apps/openmw/mwworld/contentloader.hpp +++ b/apps/openmw/mwworld/contentloader.hpp @@ -21,7 +21,7 @@ struct ContentLoader { } - virtual void load(const boost::filesystem::path& filepath, int& index) + virtual void load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) { Log(Debug::Info) << "Loading content file " << filepath.string(); mListener.setLabel(MyGUI::TextIterator::toTagsString(filepath.string())); diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index b12d646e7..c96618215 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -15,17 +15,17 @@ EsmLoader::EsmLoader(MWWorld::ESMStore& store, std::vector& read { } -void EsmLoader::load(const boost::filesystem::path& filepath, int& index) +void EsmLoader::load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) { - ContentLoader::load(filepath.filename(), index); + ContentLoader::load(filepath.filename(), index, isGroundcover); - ESM::ESMReader lEsm; - lEsm.setEncoder(mEncoder); - lEsm.setIndex(index); - lEsm.setGlobalReaderList(&mEsm); - lEsm.open(filepath.string()); - mEsm[index] = lEsm; - mStore.load(mEsm[index], &mListener); + ESM::ESMReader lEsm; + lEsm.setEncoder(mEncoder); + lEsm.setIndex(index); + lEsm.setGlobalReaderList(&mEsm); + lEsm.open(filepath.string(), isGroundcover); + mEsm[index] = lEsm; + mStore.load(mEsm[index], &mListener); } } /* namespace MWWorld */ diff --git a/apps/openmw/mwworld/esmloader.hpp b/apps/openmw/mwworld/esmloader.hpp index 506105beb..cc4c15a15 100644 --- a/apps/openmw/mwworld/esmloader.hpp +++ b/apps/openmw/mwworld/esmloader.hpp @@ -25,7 +25,7 @@ struct EsmLoader : public ContentLoader EsmLoader(MWWorld::ESMStore& store, std::vector& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener& listener); - void load(const boost::filesystem::path& filepath, int& index) override; + void load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) override; private: std::vector& mEsm; diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 90bc80b48..2731d7eb1 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -190,6 +190,15 @@ void ESMStore::setUp(bool validateRecords) { validate(); countRecords(); + + if (mGroundcovers.empty()) + { + for (const ESM::Static& record : mStatics) + { + if (record.mIsGroundcover) + mGroundcovers[record.mId] = record.mModel; + } + } } } diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index d69c56d8c..18bb95580 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -75,6 +75,7 @@ namespace MWWorld // maps the id name to the record type. std::map mIds; std::map mStaticIds; + std::map mGroundcovers; std::map mRefCount; @@ -121,6 +122,22 @@ namespace MWWorld return it->second; } + bool isGroundcover(const std::string &id, std::string &model) const + { + std::map::const_iterator it = mGroundcovers.find(id); + if (it == mGroundcovers.end()) { + return false; + } + model = it->second; + return true; + } + + bool isGroundcover(const std::string &id) const + { + std::map::const_iterator it = mGroundcovers.find(id); + return (it != mGroundcovers.end()); + } + ESMStore() : mDynamicCount(0) { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index af9a5e0bb..93d1a799c 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -103,12 +103,12 @@ namespace MWWorld return mLoaders.insert(std::make_pair(extension, loader)).second; } - void load(const boost::filesystem::path& filepath, int& index) override + void load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) override { LoadersContainer::iterator it(mLoaders.find(Misc::StringUtils::lowerCase(filepath.extension().string()))); if (it != mLoaders.end()) { - it->second->load(filepath, index); + it->second->load(filepath, index, isGroundcover); } else { @@ -140,6 +140,7 @@ namespace MWWorld Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const Files::Collections& fileCollections, const std::vector& contentFiles, + const std::vector& groundcoverFiles, ToUTF8::Utf8Encoder* encoder, int activationDistanceOverride, const std::string& startCell, const std::string& startupScript, const std::string& resourcePath, const std::string& userDataPath) @@ -152,7 +153,7 @@ namespace MWWorld mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0), mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f) { - mEsm.resize(contentFiles.size()); + mEsm.resize(contentFiles.size() + groundcoverFiles.size()); Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); listener->loadingOn(); @@ -165,7 +166,7 @@ namespace MWWorld gameContentLoader.addLoader(".omwaddon", &esmLoader); gameContentLoader.addLoader(".project", &esmLoader); - loadContentFiles(fileCollections, contentFiles, gameContentLoader); + loadContentFiles(fileCollections, contentFiles, groundcoverFiles, gameContentLoader); listener->loadingOff(); @@ -2941,7 +2942,7 @@ namespace MWWorld } void World::loadContentFiles(const Files::Collections& fileCollections, - const std::vector& content, ContentLoader& contentLoader) + const std::vector& content, const std::vector& groundcover, ContentLoader& contentLoader) { int idx = 0; for (const std::string &file : content) @@ -2950,7 +2951,7 @@ namespace MWWorld const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); if (col.doesExist(file)) { - contentLoader.load(col.getPath(file), idx); + contentLoader.load(col.getPath(file), idx, false); } else { @@ -2959,6 +2960,22 @@ namespace MWWorld } idx++; } + + for (const std::string &file : groundcover) + { + boost::filesystem::path filename(file); + const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); + if (col.doesExist(file)) + { + contentLoader.load(col.getPath(file), idx, true); + } + else + { + std::string message = "Failed loading " + file + ": the groundcover file does not exist"; + throw std::runtime_error(message); + } + idx++; + } } bool World::startSpellCast(const Ptr &actor) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 79c8a4980..29d29a160 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -177,7 +177,7 @@ namespace MWWorld * @param contentLoader - */ void loadContentFiles(const Files::Collections& fileCollections, - const std::vector& content, ContentLoader& contentLoader); + const std::vector& content, const std::vector& groundcover, ContentLoader& contentLoader); float feetToGameUnits(float feet); float getActivationDistancePlusTelekinesis(); @@ -196,6 +196,7 @@ namespace MWWorld Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const Files::Collections& fileCollections, const std::vector& contentFiles, + const std::vector& groundcoverFiles, ToUTF8::Utf8Encoder* encoder, int activationDistanceOverride, const std::string& startCell, const std::string& startupScript, const std::string& resourcePath, const std::string& userDataPath); diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index d7fe7da94..8717a6839 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -100,6 +100,7 @@ bool Config::GameSettings::readFile(QTextStream &stream, QMultiMap node, const std::string& shaderPrefix, bool translucentFramebuffer) + void SceneManager::recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix, bool translucentFramebuffer, bool forceShadersForNode) { osg::ref_ptr shaderVisitor(createShaderVisitor(shaderPrefix, translucentFramebuffer)); shaderVisitor->setAllowedToModifyStateSets(false); + if (forceShadersForNode) + shaderVisitor->setForceShaders(true); node->accept(*shaderVisitor); } @@ -512,7 +514,7 @@ namespace Resource SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor(mMinFilter, mMagFilter, mMaxAnisotropy); loaded->accept(setFilterSettingsControllerVisitor); - osg::ref_ptr shaderVisitor (createShaderVisitor()); + osg::ref_ptr shaderVisitor (createShaderVisitor("objects")); loaded->accept(*shaderVisitor); // share state diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index a815a324f..9da6bc500 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -76,7 +76,7 @@ namespace Resource Shader::ShaderManager& getShaderManager(); /// Re-create shaders for this node, need to call this if texture stages or vertex color mode have changed. - void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false); + void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false, bool forceShadersForNode = false); /// @see ShaderVisitor::setForceShaders void setForceShaders(bool force); diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 942bd92d8..05f97b3ed 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -281,6 +281,7 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) "FrameNumber", "", "Compiling", + "UnrefQueue", "WorkQueue", "WorkThread", "", @@ -294,14 +295,13 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) "Nif", "Keyframe", "", + "Groundcover Chunk", "Object Chunk", "Terrain Chunk", "Terrain Texture", "Land", "Composite", "", - "UnrefQueue", - "", "NavMesh UpdateJobs", "NavMesh CacheSize", "NavMesh UsedTiles", diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 788a8720b..4f887e659 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -342,6 +342,8 @@ namespace Shader osg::ref_ptr program (new osg::Program); program->addShader(vertexShader); program->addShader(fragmentShader); + program->addBindAttribLocation("aOffset", 6); + program->addBindAttribLocation("aRotation", 7); found = mPrograms.insert(std::make_pair(std::make_pair(vertexShader, fragmentShader), program)).first; } return found->second; diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 50724628d..a744471de 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -40,7 +40,7 @@ ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, T mMultiPassRoot->setAttributeAndModes(material, osg::StateAttribute::ON); } -osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f ¢er, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint, bool compile) +osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) { ChunkId id = std::make_tuple(center, lod, lodFlags); osg::ref_ptr obj = mCache->getRefFromObjectCache(id); diff --git a/components/terrain/chunkmanager.hpp b/components/terrain/chunkmanager.hpp index 118df698f..9b7dbf3ee 100644 --- a/components/terrain/chunkmanager.hpp +++ b/components/terrain/chunkmanager.hpp @@ -35,7 +35,7 @@ namespace Terrain public: ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager, CompositeMapRenderer* renderer); - osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint, bool compile) override; + osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override; void setCompositeMapSize(unsigned int size) { mCompositeMapSize = size; } void setCompositeMapLevel(float level) { mCompositeMapLevel = level; } diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 57c09000c..7f184c70e 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -90,8 +90,6 @@ private: osg::Vec4i mActiveGrid; }; -const float MIN_SIZE = 1/8.f; - class RootNode : public QuadTreeNode { public: @@ -250,6 +248,7 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour , mLodFactor(lodFactor) , mVertexLodMod(vertexLodMod) , mViewDistance(std::numeric_limits::max()) + , mMinSize(1/8.f) { mChunkManager->setCompositeMapSize(compMapResolution); mChunkManager->setCompositeMapLevel(compMapLevel); @@ -257,6 +256,17 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour mChunkManagers.push_back(mChunkManager.get()); } +QuadTreeWorld::QuadTreeWorld(osg::Group *parent, Storage *storage, int nodeMask, float lodFactor, float chunkSize) + : TerrainGrid(parent, storage, nodeMask) + , mViewDataMap(new ViewDataMap) + , mQuadTreeBuilt(false) + , mLodFactor(lodFactor) + , mVertexLodMod(0) + , mViewDistance(std::numeric_limits::max()) + , mMinSize(chunkSize) +{ +} + QuadTreeWorld::~QuadTreeWorld() { } @@ -425,7 +435,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) if (needsUpdate) { vd->reset(); - DefaultLodCallback lodCallback(mLodFactor, MIN_SIZE, mViewDistance, mActiveGrid); + DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, mActiveGrid); mRootNode->traverseNodes(vd, nv.getViewPoint(), &lodCallback); } @@ -438,7 +448,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) entry.mRenderingNode->accept(nv); } - if (isCullVisitor) + if (mHeightCullCallback && isCullVisitor) updateWaterCullingView(mHeightCullCallback, vd, static_cast(&nv), mStorage->getCellWorldSize(), !isGridEmpty()); vd->markUnchanged(); @@ -457,7 +467,7 @@ void QuadTreeWorld::ensureQuadTreeBuilt() if (mQuadTreeBuilt) return; - QuadTreeBuilder builder(mStorage, MIN_SIZE); + QuadTreeBuilder builder(mStorage, mMinSize); builder.build(); mRootNode = builder.getRootNode(); @@ -491,7 +501,7 @@ void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg:: ViewData* vd = static_cast(view); vd->setViewPoint(viewPoint); vd->setActiveGrid(grid); - DefaultLodCallback lodCallback(mLodFactor, MIN_SIZE, mViewDistance, grid); + DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, grid); mRootNode->traverseNodes(vd, viewPoint, &lodCallback); if (!progressTotal) @@ -515,14 +525,15 @@ bool QuadTreeWorld::storeView(const View* view, double referenceTime) void QuadTreeWorld::reportStats(unsigned int frameNumber, osg::Stats *stats) { - stats->setAttribute(frameNumber, "Composite", mCompositeMapRenderer->getCompileSetSize()); + if (mCompositeMapRenderer) + stats->setAttribute(frameNumber, "Composite", mCompositeMapRenderer->getCompileSetSize()); } void QuadTreeWorld::loadCell(int x, int y) { // fallback behavior only for undefined cells (every other is already handled in quadtree) float dummy; - if (!mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy)) + if (mChunkManager && !mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy)) TerrainGrid::loadCell(x,y); else World::loadCell(x,y); @@ -532,7 +543,7 @@ void QuadTreeWorld::unloadCell(int x, int y) { // fallback behavior only for undefined cells (every other is already handled in quadtree) float dummy; - if (!mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy)) + if (mChunkManager && !mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy)) TerrainGrid::unloadCell(x,y); else World::unloadCell(x,y); diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index 4c05efe64..aba2dccf3 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -22,6 +22,8 @@ namespace Terrain public: QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask, int borderMask, int compMapResolution, float comMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize); + QuadTreeWorld(osg::Group *parent, Storage *storage, int nodeMask, float lodFactor, float chunkSize); + ~QuadTreeWorld(); void accept(osg::NodeVisitor& nv); @@ -47,7 +49,7 @@ namespace Terrain { public: virtual ~ChunkManager(){} - virtual osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint, bool compile) = 0; + virtual osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) = 0; virtual unsigned int getNodeMask() { return 0; } }; void addChunkManager(ChunkManager*); @@ -66,6 +68,7 @@ namespace Terrain float mLodFactor; int mVertexLodMod; float mViewDistance; + float mMinSize; }; } diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 679597971..cf8debc69 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -26,6 +26,12 @@ TerrainGrid::TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource:: { } +TerrainGrid::TerrainGrid(osg::Group* parent, Storage* storage, int nodeMask) + : Terrain::World(parent, storage, nodeMask) + , mNumSplits(4) +{ +} + TerrainGrid::~TerrainGrid() { while (!mGrid.empty()) @@ -107,6 +113,8 @@ void TerrainGrid::unloadCell(int x, int y) void TerrainGrid::updateWaterCulling() { + if (!mHeightCullCallback) return; + osg::ComputeBoundsVisitor computeBoundsVisitor; mTerrainRoot->accept(computeBoundsVisitor); float lowZ = computeBoundsVisitor.getBoundingBox()._min.z(); diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index f8b0fb259..dc9203466 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -15,6 +15,7 @@ namespace Terrain { public: TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask=~0, int borderMask=0); + TerrainGrid(osg::Group* parent, Storage* storage, int nodeMask=~0); ~TerrainGrid(); void cacheCell(View* view, int x, int y) override; diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 5b4807b38..15ec72973 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -49,17 +49,38 @@ World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSyst mResourceSystem->addResourceManager(mTextureManager.get()); } +World::World(osg::Group* parent, Storage* storage, int nodeMask) + : mStorage(storage) + , mParent(parent) + , mCompositeMapCamera(nullptr) + , mCompositeMapRenderer(nullptr) + , mResourceSystem(nullptr) + , mTextureManager(nullptr) + , mChunkManager(nullptr) + , mCellBorder(nullptr) + , mBorderVisible(false) + , mHeightCullCallback(nullptr) +{ + mTerrainRoot = new osg::Group; + mTerrainRoot->setNodeMask(nodeMask); + + mParent->addChild(mTerrainRoot); +} + World::~World() { - mResourceSystem->removeResourceManager(mChunkManager.get()); - mResourceSystem->removeResourceManager(mTextureManager.get()); + if (mResourceSystem && mChunkManager) + mResourceSystem->removeResourceManager(mChunkManager.get()); + if (mResourceSystem && mTextureManager) + mResourceSystem->removeResourceManager(mTextureManager.get()); mParent->removeChild(mTerrainRoot); - mCompositeMapCamera->removeChild(mCompositeMapRenderer); - mCompositeMapCamera->getParent(0)->removeChild(mCompositeMapCamera); - - delete mStorage; + if (mCompositeMapCamera && mCompositeMapRenderer) + { + mCompositeMapCamera->removeChild(mCompositeMapRenderer); + mCompositeMapCamera->getParent(0)->removeChild(mCompositeMapCamera); + } } void World::setWorkQueue(SceneUtil::WorkQueue* workQueue) @@ -108,16 +129,20 @@ float World::getHeightAt(const osg::Vec3f &worldPos) void World::updateTextureFiltering() { - mTextureManager->updateTextureFiltering(); + if (mTextureManager) + mTextureManager->updateTextureFiltering(); } void World::clearAssociatedCaches() { - mChunkManager->clearCache(); + if (mChunkManager) + mChunkManager->clearCache(); } osg::Callback* World::getHeightCullCallback(float highz, unsigned int mask) { + if (!mHeightCullCallback) return nullptr; + mHeightCullCallback->setHighZ(highz); mHeightCullCallback->setCullMask(mask); return mHeightCullCallback; diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index d94125100..a4be57e8e 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -106,6 +106,7 @@ namespace Terrain /// @param nodeMask mask for the terrain root /// @param preCompileMask mask for pre compiling textures World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask, int borderMask); + World(osg::Group* parent, Storage* storage, int nodeMask); virtual ~World(); /// Set a WorkQueue to delete objects in the background thread. diff --git a/docs/source/reference/modding/extended.rst b/docs/source/reference/modding/extended.rst index 9e8db49fd..98b3e7f00 100644 --- a/docs/source/reference/modding/extended.rst +++ b/docs/source/reference/modding/extended.rst @@ -223,10 +223,10 @@ For example, to attach a custom weapon bone, you'll need to follow this NIF reco :: -NiNode "root" - NiNode "Bip01 L Hand" - NiNode "Weapon Bone Left" - NiStringExtraData "BONE" + NiNode "root" + NiNode "Bip01 L Hand" + NiNode "Weapon Bone Left" + NiStringExtraData "BONE" OpenMW will detect ``Weapon Bone Left`` node and attach it to ``Bip01 L Hand`` bone of the target skeleton. @@ -276,6 +276,54 @@ Also it is possible to add a "Bip01 Arrow" bone to actor skeletons. In this case Such approach allows to implement better shooting animations (for example, beast races have tail, so quivers should be attached under different angle and default arrow fetching animation does not look good). +Groundcover support +------------------- + +Groundcover objects is a special kind of objects (e.g. grass), which can be used to improve visual fidelity. +They use these assumptions: + +1. Each object is independent, so part of objects can be removed from scene without causing graphical artifacts. + +2. Groundover should not have collisions. + +3. They are not important for some parts of game scene (e.g. local map). + +4. They can not be moved or disabled on the fly. + +5. They can not be interacted with. + +As result, such objects can be treated in the separate way: + +1. It is possible to tweak groundcover objects density. + +2. It is possible to safely merge such objects even near player. + +3. Such objects can be animated (to simulate wind, for example). + +4. Some parts of processing can be skipped. + +For example, we do not need to have collision or animation objects for groundcover, +do not need to render groundcover on the map, do not need to render it for the whole visible area (which can be very large with Distant Terrain). It allows to increase performance a lot. + +General advices to create assets for this feature: +1. Alpha properties from Nif files are not used, a unified alpha settings are used (alpha testing, "greater of equal" function, 128/255 threshold). +2. Use a single NiTriShape in groundocver mesh, or at least use same properties (texture, alpha, material, etc), so OpenMW can merge them on the fly. Otherwise animations may not work properly. +3. Smooth fading does not work for meshes, which have textures without alpha (e.g. rock). + +Groundcover mods can be registered in the openmw.cfg via "groundcover" entries instead of "content" ones: + +:: + + groundcover=my_grass_mod.esp + +Every static from such mod is treated as a groundcover object. +Also groundcover detection should be enabled via settings.cfg: + +:: + + [Groundcover] + enabled = true + .. _`Graphic Herbalism`: https://www.nexusmods.com/morrowind/mods/46599 .. _`OpenMW Containers Animated`: https://www.nexusmods.com/morrowind/mods/46232 .. _`Glow in the Dahrk`: https://www.nexusmods.com/morrowind/mods/45886 diff --git a/docs/source/reference/modding/settings/groundcover.rst b/docs/source/reference/modding/settings/groundcover.rst new file mode 100644 index 000000000..9b00d85a1 --- /dev/null +++ b/docs/source/reference/modding/settings/groundcover.rst @@ -0,0 +1,68 @@ +Groundcover Settings +#################### + +enabled +------- + +:Type: boolean +:Range: True/False +:Default: False + +Allows the engine to use groundcover. +Groundcover objects are static objects which come from ESP files, registered via +"groundcover" entries from openmw.cfg rather than "content" ones. +We assume that groundcover objects have no collisions, can not be moved or interacted with, +so we can merge them to pages and animate them indifferently from distance from player. + +This setting can only be configured by editing the settings configuration file. + +fade start +---------- + +:Type: floating point +:Range: 0.0 to 1.0 +:Default: 0.85 + +Determines on which distance from player groundcover fading starts. +Does not work for meshes which textures do not have transparency (e.g. rocks). + +This setting can only be configured by editing the settings configuration file. + +density +------- + +:Type: floating point +:Range: 0.0 (0%) to 1.0 (100%) +:Default: 1.0 + +Determines how many groundcover instances from content files +are used in the game. Can affect performance a lot. + +This setting can only be configured by editing the settings configuration file. + +distance +-------- + +:Type: integer +:Range: > 0 +:Default: 1 + +Determines on which distance in cells grass pages are rendered. +Default 1 value means 3x3 cells area (active grid). +May affect performance a lot. + +This setting can only be configured by editing the settings configuration file. + +min chunk size +-------------- + +:Type: floating point +:Range: 0.125, 0.25, 0.5, 1.0 +:Default: 0.5 + +Determines a minimum size of groundcover chunks in cells. For example, with 0.5 value +chunks near player will have size 4096x4096 game units. Larger chunks reduce CPU usage +(Draw and Cull bars), but can increase GPU usage (GPU bar) since culling becomes less efficient. +Smaller values do an opposite. + +This setting can only be configured by editing the settings configuration file. diff --git a/docs/source/reference/modding/settings/index.rst b/docs/source/reference/modding/settings/index.rst index 586a99dbb..220ee88c4 100644 --- a/docs/source/reference/modding/settings/index.rst +++ b/docs/source/reference/modding/settings/index.rst @@ -42,6 +42,7 @@ The ranges included with each setting are the physically possible ranges, not re camera cells fog + groundcover map GUI HUD diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index e23cc3d54..ed43b19a2 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -26,6 +26,7 @@ Has no effect if the 'force shaders' option is false. Enabling per-pixel lighting results in visual differences to the original MW engine. It is not recommended to enable this option when using vanilla Morrowind files, because certain lights in Morrowind rely on vertex lighting to look as intended. +Note that groundcover shaders ignore this setting. clamp lighting -------------- diff --git a/docs/source/reference/modding/settings/water.rst b/docs/source/reference/modding/settings/water.rst index b79daacb7..b3a6f8c1f 100644 --- a/docs/source/reference/modding/settings/water.rst +++ b/docs/source/reference/modding/settings/water.rst @@ -62,7 +62,7 @@ reflection detail ----------------- :Type: integer -:Range: 0, 1, 2, 3, 4 +:Range: 0, 1, 2, 3, 4, 5 :Default: 2 Controls what kinds of things are rendered in water reflections. @@ -72,6 +72,7 @@ Controls what kinds of things are rendered in water reflections. 2: statics, activators, and doors are also reflected 3: items, containers, and particles are also reflected 4: actors are also reflected +5: groundcover objects are also reflected In interiors the lowest level is 2. This setting can be changed ingame with the "Reflection shader detail" dropdown under the Water tab of the Video panel in the Options menu. diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 14ab7c9de..b57d362ed 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -449,6 +449,7 @@ + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index d0793fc81..f6bfff7b1 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -950,6 +950,25 @@ lineofsight keep inactive cache = 0 defer aabb update = true [Models] + # Attempt to load any valid NIF file regardless of its version and track the progress. # Loading arbitrary meshes is not advised and may cause instability. load unsupported nif files = false + +[Groundcover] + +# enable separate groundcover handling +enabled = false + +# configure groundcover fade out threshold +fade start = 0.85 + +# A groundcover density (0.0 <= value <= 1.0) +# 1.0 means 100% density +density = 1.0 + +# A maximum distance in cells on which groundcover is rendered. +distance = 1 + +# A minimum size of groundcover chunk in cells (0.125, 0.25, 0.5, 1.0) +min chunk size = 0.5 diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 47670e7a0..a4e898e4b 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -7,6 +7,8 @@ set(SDIR ${CMAKE_CURRENT_SOURCE_DIR}) set(DDIRRELATIVE resources/shaders) set(SHADER_FILES + groundcover_vertex.glsl + groundcover_fragment.glsl water_vertex.glsl water_fragment.glsl water_nm.png diff --git a/files/shaders/groundcover_fragment.glsl b/files/shaders/groundcover_fragment.glsl new file mode 100644 index 000000000..9c680853a --- /dev/null +++ b/files/shaders/groundcover_fragment.glsl @@ -0,0 +1,93 @@ +#version 120 + +#define GROUNDCOVER + +#if @diffuseMap +uniform sampler2D diffuseMap; +varying vec2 diffuseMapUV; +#endif + +#if @normalMap +uniform sampler2D normalMap; +varying vec2 normalMapUV; +varying vec4 passTangent; +#endif + +// Other shaders respect forcePPL, but legacy groundcover mods were designed to work with vertex lighting. +// They may do not look as intended with per-pixel lighting, so ignore this setting for now. +#define PER_PIXEL_LIGHTING @normalMap + +varying float euclideanDepth; +varying float linearDepth; + +#if PER_PIXEL_LIGHTING +varying vec3 passViewPos; +varying vec3 passNormal; +#else +centroid varying vec3 passLighting; +centroid varying vec3 shadowDiffuseLighting; +#endif + +#include "shadows_fragment.glsl" +#include "lighting.glsl" + +float calc_coverage(float a, float alpha_ref, float falloff_rate) +{ + return clamp(falloff_rate * (a - alpha_ref) + alpha_ref, 0.0, 1.0); +} + +void main() +{ +#if @normalMap + vec4 normalTex = texture2D(normalMap, normalMapUV); + + vec3 normalizedNormal = normalize(passNormal); + vec3 normalizedTangent = normalize(passTangent.xyz); + vec3 binormal = cross(normalizedTangent, normalizedNormal) * passTangent.w; + mat3 tbnTranspose = mat3(normalizedTangent, binormal, normalizedNormal); + + vec3 viewNormal = gl_NormalMatrix * normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); +#endif + +#if (!@normalMap && @forcePPL && false) + vec3 viewNormal = gl_NormalMatrix * normalize(passNormal); +#endif + +#if @diffuseMap + gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); +#else + gl_FragData[0] = vec4(1.0); +#endif + + gl_FragData[0].a = calc_coverage(gl_FragData[0].a, 128.0/255.0, 4.0); + + float shadowing = unshadowedLightRatio(linearDepth); + if (euclideanDepth > @groundcoverFadeStart) + gl_FragData[0].a *= 1.0-smoothstep(@groundcoverFadeStart, @groundcoverFadeEnd, euclideanDepth); + + vec3 lighting; +#if !PER_PIXEL_LIGHTING + lighting = passLighting + shadowDiffuseLighting * shadowing; +#else + vec3 diffuseLight, ambientLight; + doLighting(passViewPos, normalize(viewNormal), shadowing, diffuseLight, ambientLight); + lighting = diffuseLight + ambientLight; +#endif + +#if @clamp + lighting = clamp(lighting, vec3(0.0), vec3(1.0)); +#else + lighting = max(lighting, 0.0); +#endif + + gl_FragData[0].xyz *= lighting; + +#if @radialFog + float fogValue = clamp((euclideanDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); +#else + float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); +#endif + gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); + + applyShadowDebugOverlay(); +} diff --git a/files/shaders/groundcover_vertex.glsl b/files/shaders/groundcover_vertex.glsl new file mode 100644 index 000000000..4f3303b03 --- /dev/null +++ b/files/shaders/groundcover_vertex.glsl @@ -0,0 +1,143 @@ +#version 120 + +#define GROUNDCOVER + +attribute vec4 aOffset; +attribute vec3 aRotation; + +#if @diffuseMap +varying vec2 diffuseMapUV; +#endif + +#if @normalMap +varying vec2 normalMapUV; +varying vec4 passTangent; +#endif + +// Other shaders respect forcePPL, but legacy groundcover mods were designed to work with vertex lighting. +// They may do not look as intended with per-pixel lighting, so ignore this setting for now. +#define PER_PIXEL_LIGHTING @normalMap + +varying float euclideanDepth; +varying float linearDepth; + +#if PER_PIXEL_LIGHTING +varying vec3 passViewPos; +varying vec3 passNormal; +#else +centroid varying vec3 passLighting; +centroid varying vec3 shadowDiffuseLighting; +#endif + +#include "shadows_vertex.glsl" +#include "lighting.glsl" + +uniform float osg_SimulationTime; +uniform mat4 osg_ViewMatrixInverse; +uniform mat4 osg_ViewMatrix; +uniform float windSpeed; +uniform vec3 playerPos; + +vec2 groundcoverDisplacement(in vec3 worldpos, float h) +{ + vec2 windDirection = vec2(1.0); + vec3 footPos = playerPos; + vec3 windVec = vec3(windSpeed * windDirection, 1.0); + + float v = length(windVec); + vec2 displace = vec2(2.0 * windVec + 0.1); + vec2 harmonics = vec2(0.0); + + harmonics += vec2((1.0 - 0.10*v) * sin(1.0*osg_SimulationTime + worldpos.xy / 1100.0)); + harmonics += vec2((1.0 - 0.04*v) * cos(2.0*osg_SimulationTime + worldpos.xy / 750.0)); + harmonics += vec2((1.0 + 0.14*v) * sin(3.0*osg_SimulationTime + worldpos.xy / 500.0)); + harmonics += vec2((1.0 + 0.28*v) * sin(5.0*osg_SimulationTime + worldpos.xy / 200.0)); + + // FIXME: stomping function does not work well in MGE: + // 1. It does not take in account Z coordinate, so it works even when player levitates. + // 2. It works more-or-less well only for grass meshes, but not for other types of plants. + // So disable this function for now, until we find a better one. + vec2 stomp = vec2(0.0); + //float d = length(worldpos.xy - footPos.xy); + //if (d < 150.0 && d > 0.0) + //{ + // stomp = (60.0 / d - 0.4) * (worldpos.xy - footPos.xy); + //} + + return clamp(0.02 * h, 0.0, 1.0) * (harmonics * displace + stomp); +} + +mat4 rotation(in vec3 angle) +{ + float sin_x = sin(angle.x); + float cos_x = cos(angle.x); + float sin_y = sin(angle.y); + float cos_y = cos(angle.y); + float sin_z = sin(angle.z); + float cos_z = cos(angle.z); + + return mat4( + cos_z*cos_y+sin_x*sin_y*sin_z, -sin_z*cos_x, cos_z*sin_y+sin_z*sin_x*cos_y, 0.0, + sin_z*cos_y+cos_z*sin_x*sin_y, cos_z*cos_x, sin_z*sin_y-cos_z*sin_x*cos_y, 0.0, + -sin_y*cos_x, sin_x, cos_x*cos_y, 0.0, + 0.0, 0.0, 0.0, 1.0); +} + +mat3 rotation3(in mat4 rot4) +{ + return mat3( + rot4[0].xyz, + rot4[1].xyz, + rot4[2].xyz); +} + +void main(void) +{ + vec3 position = aOffset.xyz; + float scale = aOffset.w; + + mat4 rotation = rotation(aRotation); + vec4 displacedVertex = rotation * scale * gl_Vertex; + + displacedVertex = vec4(displacedVertex.xyz + position, 1.0); + + vec4 worldPos = osg_ViewMatrixInverse * gl_ModelViewMatrix * displacedVertex; + worldPos.xy += groundcoverDisplacement(worldPos.xyz, gl_Vertex.z); + vec4 viewPos = osg_ViewMatrix * worldPos; + + gl_ClipVertex = viewPos; + euclideanDepth = length(viewPos.xyz); + + if (length(gl_ModelViewMatrix * vec4(position, 1.0)) > @groundcoverFadeEnd) + gl_Position = vec4(0.0, 0.0, 0.0, 1.0); + else + gl_Position = gl_ProjectionMatrix * viewPos; + + linearDepth = gl_Position.z; + +#if (!PER_PIXEL_LIGHTING || @shadows_enabled) + vec3 viewNormal = normalize((gl_NormalMatrix * rotation3(rotation) * gl_Normal).xyz); +#endif + +#if @diffuseMap + diffuseMapUV = (gl_TextureMatrix[@diffuseMapUV] * gl_MultiTexCoord@diffuseMapUV).xy; +#endif + +#if @normalMap + normalMapUV = (gl_TextureMatrix[@normalMapUV] * gl_MultiTexCoord@normalMapUV).xy; + passTangent = gl_MultiTexCoord7.xyzw * rotation; +#endif + +#if PER_PIXEL_LIGHTING + passViewPos = viewPos.xyz; + passNormal = rotation3(rotation) * gl_Normal.xyz; +#else + vec3 diffuseLight, ambientLight; + doLighting(viewPos.xyz, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting); + passLighting = diffuseLight + ambientLight; +#endif + +#if (@shadows_enabled) + setupShadowCoords(viewPos, viewNormal); +#endif +} diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 930f4de26..1b3ff288a 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -8,7 +8,20 @@ void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 vie float illumination = clamp(1.0 / (gl_LightSource[lightIndex].constantAttenuation + gl_LightSource[lightIndex].linearAttenuation * lightDistance + gl_LightSource[lightIndex].quadraticAttenuation * lightDistance * lightDistance), 0.0, 1.0); ambientOut = gl_LightSource[lightIndex].ambient.xyz * illumination; - diffuseOut = gl_LightSource[lightIndex].diffuse.xyz * max(dot(viewNormal, lightDir), 0.0) * illumination; + + float lambert = dot(viewNormal.xyz, lightDir) * illumination; +#ifndef GROUNDCOVER + lambert = max(lambert, 0.0); +#else + { + // might need to be < 0 depending on direction of viewPos + if (dot(viewPos, viewNormal.xyz) > 0) + lambert = -lambert; + if (lambert < 0) + lambert *= -0.3; + } +#endif + diffuseOut = gl_LightSource[lightIndex].diffuse.xyz * lambert; } #if PER_PIXEL_LIGHTING From 5124e81348ecdc915dd7248c8b088353f2037ea2 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 22 Nov 2020 10:17:20 +0400 Subject: [PATCH 099/171] Use linear interpolation instead of abrupt transitions for groundcover lighting --- files/shaders/lighting.glsl | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 1b3ff288a..5eae89029 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -14,11 +14,20 @@ void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 vie lambert = max(lambert, 0.0); #else { - // might need to be < 0 depending on direction of viewPos - if (dot(viewPos, viewNormal.xyz) > 0) - lambert = -lambert; - if (lambert < 0) - lambert *= -0.3; + float cosine = dot(normalize(viewPos), normalize(viewNormal.xyz)); + if (lambert >= 0.0) + cosine = -cosine; + + float mult = 1.0; + float divisor = 8.0; + + if (cosine < 0.0 && cosine >= -1.0/divisor) + mult = mix(1.0, 0.3, -cosine*divisor); + else if (cosine < -1.0/divisor) + mult = 0.3; + + lambert *= mult; + lambert = abs(lambert); } #endif diffuseOut = gl_LightSource[lightIndex].diffuse.xyz * lambert; From 36fc573375dcea73cc412d9452e6684f0de9f9ec Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 17 Dec 2020 13:55:20 +0400 Subject: [PATCH 100/171] Take in account Z direction for stomping --- files/shaders/groundcover_vertex.glsl | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/files/shaders/groundcover_vertex.glsl b/files/shaders/groundcover_vertex.glsl index 4f3303b03..407599eff 100644 --- a/files/shaders/groundcover_vertex.glsl +++ b/files/shaders/groundcover_vertex.glsl @@ -53,18 +53,14 @@ vec2 groundcoverDisplacement(in vec3 worldpos, float h) harmonics += vec2((1.0 + 0.14*v) * sin(3.0*osg_SimulationTime + worldpos.xy / 500.0)); harmonics += vec2((1.0 + 0.28*v) * sin(5.0*osg_SimulationTime + worldpos.xy / 200.0)); - // FIXME: stomping function does not work well in MGE: - // 1. It does not take in account Z coordinate, so it works even when player levitates. - // 2. It works more-or-less well only for grass meshes, but not for other types of plants. - // So disable this function for now, until we find a better one. - vec2 stomp = vec2(0.0); - //float d = length(worldpos.xy - footPos.xy); - //if (d < 150.0 && d > 0.0) - //{ - // stomp = (60.0 / d - 0.4) * (worldpos.xy - footPos.xy); - //} - - return clamp(0.02 * h, 0.0, 1.0) * (harmonics * displace + stomp); + float d = length(worldpos - footPos.xyz); + vec3 stomp = vec3(0.0); + if (d < 150.0 && d > 0.0) + { + stomp = (60.0 / d - 0.4) * (worldpos - footPos.xyz); + } + + return clamp(0.02 * h, 0.0, 1.0) * (harmonics * displace + stomp.xy); } mat4 rotation(in vec3 angle) From a09f03c85062180d5ae82796a92b5eab4ce27543 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 18 Dec 2020 14:52:30 +0400 Subject: [PATCH 101/171] Drop groundcover materials - they are not used --- apps/openmw/mwrender/groundcover.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index b9fdc2e28..1a5ccc29f 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -58,6 +58,7 @@ namespace MWRender osg::ref_ptr ss = node.getStateSet(); if (ss != nullptr) { + ss->removeAttribute(osg::StateAttribute::MATERIAL); removeAlpha(ss); } @@ -101,6 +102,7 @@ namespace MWRender ss->setAttribute(new osg::VertexAttribDivisor(6, 1)); ss->setAttribute(new osg::VertexAttribDivisor(7, 1)); + ss->removeAttribute(osg::StateAttribute::MATERIAL); removeAlpha(ss); traverse(geom); From 859cd0fd0c34ef6256b0c70cd4be0bef9797a5a0 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 22:47:19 +0400 Subject: [PATCH 102/171] Do not use display lists for instanced meshes --- apps/openmw/mwrender/groundcover.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 1a5ccc29f..11a155f9f 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -95,6 +95,9 @@ namespace MWRender (*rotations)[i] = mInstances[i].mPos.asRotationVec3(); } + // Display lists do not support instancing in OSG 3.4 + geom.setUseDisplayList(false); + geom.setVertexAttribArray(6, transforms.get(), osg::Array::BIND_PER_VERTEX); geom.setVertexAttribArray(7, rotations.get(), osg::Array::BIND_PER_VERTEX); From 1a6c06f7b59c05c60b31693f46f9a25940881332 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 10 Jan 2021 16:36:25 +0400 Subject: [PATCH 103/171] Do not allow to set distance to non-positive values --- apps/openmw/mwrender/renderingmanager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c755f46f8..1b4b0cf27 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -244,7 +244,7 @@ namespace MWRender globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0"; globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"; - float groundcoverDistance = (Constants::CellSizeInUnits * Settings::Manager::getInt("distance", "Groundcover") - 1024) * 0.93; + float groundcoverDistance = (Constants::CellSizeInUnits * std::max(1, Settings::Manager::getInt("distance", "Groundcover")) - 1024) * 0.93; globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * Settings::Manager::getFloat("fade start", "Groundcover")); globalDefines["groundcoverFadeEnd"] = std::to_string(groundcoverDistance); @@ -1024,7 +1024,7 @@ namespace MWRender if (mGroundcoverWorld) { - int groundcoverDistance = Constants::CellSizeInUnits * Settings::Manager::getInt("distance", "Groundcover"); + int groundcoverDistance = Constants::CellSizeInUnits * std::max(1, Settings::Manager::getInt("distance", "Groundcover")); mGroundcoverWorld->setViewDistance(groundcoverDistance * (distanceMult ? 1.f/distanceMult : 1.f)); } } From 8874e88ff1e0339afdf8365ed793a43bf4a750ea Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 11 Jan 2021 09:59:57 +0400 Subject: [PATCH 104/171] Drop fading setting --- apps/openmw/mwrender/renderingmanager.cpp | 2 +- .../reference/modding/settings/groundcover.rst | 12 ------------ files/settings-default.cfg | 3 --- 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 1b4b0cf27..68576cf44 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -245,7 +245,7 @@ namespace MWRender globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"; float groundcoverDistance = (Constants::CellSizeInUnits * std::max(1, Settings::Manager::getInt("distance", "Groundcover")) - 1024) * 0.93; - globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * Settings::Manager::getFloat("fade start", "Groundcover")); + globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * 0.9f); globalDefines["groundcoverFadeEnd"] = std::to_string(groundcoverDistance); // It is unnecessary to stop/start the viewer as no frames are being rendered yet. diff --git a/docs/source/reference/modding/settings/groundcover.rst b/docs/source/reference/modding/settings/groundcover.rst index 9b00d85a1..f0c37b738 100644 --- a/docs/source/reference/modding/settings/groundcover.rst +++ b/docs/source/reference/modding/settings/groundcover.rst @@ -16,18 +16,6 @@ so we can merge them to pages and animate them indifferently from distance from This setting can only be configured by editing the settings configuration file. -fade start ----------- - -:Type: floating point -:Range: 0.0 to 1.0 -:Default: 0.85 - -Determines on which distance from player groundcover fading starts. -Does not work for meshes which textures do not have transparency (e.g. rocks). - -This setting can only be configured by editing the settings configuration file. - density ------- diff --git a/files/settings-default.cfg b/files/settings-default.cfg index f6bfff7b1..3660f56f0 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -960,9 +960,6 @@ load unsupported nif files = false # enable separate groundcover handling enabled = false -# configure groundcover fade out threshold -fade start = 0.85 - # A groundcover density (0.0 <= value <= 1.0) # 1.0 means 100% density density = 1.0 From d12a0fdcb3dbbe056ae4734f8e931fd3e060274f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 11 Jan 2021 10:02:55 +0400 Subject: [PATCH 105/171] Mark only instances from groundcover files as groundcover objects --- apps/openmw/mwrender/groundcover.cpp | 21 +++++++++++++++++---- apps/openmw/mwrender/objectpaging.cpp | 3 +-- apps/openmw/mwworld/cellpreloader.cpp | 8 ++------ apps/openmw/mwworld/cellstore.cpp | 20 +++++--------------- apps/openmw/mwworld/contentloader.hpp | 2 +- apps/openmw/mwworld/esmloader.cpp | 6 +++--- apps/openmw/mwworld/esmloader.hpp | 2 +- apps/openmw/mwworld/esmstore.cpp | 9 --------- apps/openmw/mwworld/esmstore.hpp | 17 ----------------- apps/openmw/mwworld/worldimp.cpp | 10 ++++++---- components/esm/cellref.cpp | 5 +++++ components/esm/cellref.hpp | 5 +++++ components/esm/esmreader.cpp | 9 +++------ components/esm/esmreader.hpp | 7 ++----- components/esm/loadstat.cpp | 2 -- components/esm/loadstat.hpp | 2 -- 16 files changed, 51 insertions(+), 77 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 11a155f9f..049118c90 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -13,6 +13,17 @@ namespace MWRender { + std::string getGroundcoverModel(int type, const std::string& id, const MWWorld::ESMStore& store) + { + switch (type) + { + case ESM::REC_STAT: + return store.get().searchStatic(id)->mModel; + default: + return std::string(); + } + } + void GroundcoverUpdater::setWindSpeed(float windSpeed) { mWindSpeed = windSpeed; @@ -217,15 +228,17 @@ namespace MWRender while(cell->getNextRef(esm[index], ref, deleted)) { if (deleted) continue; - Misc::StringUtils::lowerCaseInPlace(ref.mRefID); - std::string model; - if (!store.isGroundcover(ref.mRefID, model)) continue; - if (model.empty()) continue; + if (!ref.mRefNum.fromGroundcoverFile()) continue; if (!calculator.isInstanceEnabled()) continue; if (!isInChunkBorders(ref, minBound, maxBound)) continue; + Misc::StringUtils::lowerCaseInPlace(ref.mRefID); + int type = store.findStatic(ref.mRefID); + std::string model = getGroundcoverModel(type, ref.mRefID, store); + if (model.empty()) continue; model = "meshes/" + model; + instances[model].emplace_back(ref, model); } } diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 478fde0f8..b85358c20 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -373,7 +373,6 @@ namespace MWRender std::map refs; std::vector esm; const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX) { for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY) @@ -398,7 +397,7 @@ namespace MWRender int type = store.findStatic(ref.mRefID); if (!typeFilter(type,size>=2)) continue; if (deleted) { refs.erase(ref.mRefNum); continue; } - if (store.isGroundcover(ref.mRefID)) continue; + if (ref.mRefNum.fromGroundcoverFile()) continue; refs[ref.mRefNum] = ref; } } diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 937491f62..421de4a7d 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -1,5 +1,6 @@ #include "cellpreloader.hpp" +#include #include #include @@ -36,12 +37,7 @@ namespace MWWorld virtual bool operator()(const MWWorld::Ptr& ptr) { - if (ptr.getTypeName()==typeid (ESM::Static).name()) - { - const MWWorld::LiveCellRef *ref = ptr.get(); - if (ref->mBase->mIsGroundcover) - return true; - } + if (ptr.getCellRef().getRefNum().fromGroundcoverFile()) return true; ptr.getClass().getModelsToPreload(ptr, mOut); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index d8e2eb65f..7ca35a1df 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -169,18 +169,6 @@ namespace namespace MWWorld { - template - bool CellRefList::ignoreInstance (const X* ptr) - { - return false; - } - - template <> - bool CellRefList::ignoreInstance (const ESM::Static* ptr) - { - return ptr->mIsGroundcover; - } - template void CellRefList::load(ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore) { @@ -188,8 +176,6 @@ namespace MWWorld if (const X *ptr = store.search (ref.mRefID)) { - if (ignoreInstance(ptr)) return; - typename std::list::iterator iter = std::find(mList.begin(), mList.end(), ref.mRefNum); @@ -700,7 +686,11 @@ namespace MWWorld case ESM::REC_NPC_: mNpcs.load(ref, deleted, store); break; case ESM::REC_PROB: mProbes.load(ref, deleted, store); break; case ESM::REC_REPA: mRepairs.load(ref, deleted, store); break; - case ESM::REC_STAT: mStatics.load(ref, deleted, store); break; + case ESM::REC_STAT: + { + if (ref.mRefNum.fromGroundcoverFile()) return; + mStatics.load(ref, deleted, store); break; + } case ESM::REC_WEAP: mWeapons.load(ref, deleted, store); break; case ESM::REC_BODY: mBodyParts.load(ref, deleted, store); break; diff --git a/apps/openmw/mwworld/contentloader.hpp b/apps/openmw/mwworld/contentloader.hpp index b559df083..b529ae9db 100644 --- a/apps/openmw/mwworld/contentloader.hpp +++ b/apps/openmw/mwworld/contentloader.hpp @@ -21,7 +21,7 @@ struct ContentLoader { } - virtual void load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) + virtual void load(const boost::filesystem::path& filepath, int& index) { Log(Debug::Info) << "Loading content file " << filepath.string(); mListener.setLabel(MyGUI::TextIterator::toTagsString(filepath.string())); diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index c96618215..46b806582 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -15,15 +15,15 @@ EsmLoader::EsmLoader(MWWorld::ESMStore& store, std::vector& read { } -void EsmLoader::load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) +void EsmLoader::load(const boost::filesystem::path& filepath, int& index) { - ContentLoader::load(filepath.filename(), index, isGroundcover); + ContentLoader::load(filepath.filename(), index); ESM::ESMReader lEsm; lEsm.setEncoder(mEncoder); lEsm.setIndex(index); lEsm.setGlobalReaderList(&mEsm); - lEsm.open(filepath.string(), isGroundcover); + lEsm.open(filepath.string()); mEsm[index] = lEsm; mStore.load(mEsm[index], &mListener); } diff --git a/apps/openmw/mwworld/esmloader.hpp b/apps/openmw/mwworld/esmloader.hpp index cc4c15a15..506105beb 100644 --- a/apps/openmw/mwworld/esmloader.hpp +++ b/apps/openmw/mwworld/esmloader.hpp @@ -25,7 +25,7 @@ struct EsmLoader : public ContentLoader EsmLoader(MWWorld::ESMStore& store, std::vector& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener& listener); - void load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) override; + void load(const boost::filesystem::path& filepath, int& index) override; private: std::vector& mEsm; diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 2731d7eb1..90bc80b48 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -190,15 +190,6 @@ void ESMStore::setUp(bool validateRecords) { validate(); countRecords(); - - if (mGroundcovers.empty()) - { - for (const ESM::Static& record : mStatics) - { - if (record.mIsGroundcover) - mGroundcovers[record.mId] = record.mModel; - } - } } } diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 18bb95580..d69c56d8c 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -75,7 +75,6 @@ namespace MWWorld // maps the id name to the record type. std::map mIds; std::map mStaticIds; - std::map mGroundcovers; std::map mRefCount; @@ -122,22 +121,6 @@ namespace MWWorld return it->second; } - bool isGroundcover(const std::string &id, std::string &model) const - { - std::map::const_iterator it = mGroundcovers.find(id); - if (it == mGroundcovers.end()) { - return false; - } - model = it->second; - return true; - } - - bool isGroundcover(const std::string &id) const - { - std::map::const_iterator it = mGroundcovers.find(id); - return (it != mGroundcovers.end()); - } - ESMStore() : mDynamicCount(0) { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 93d1a799c..98af121a5 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -103,12 +103,12 @@ namespace MWWorld return mLoaders.insert(std::make_pair(extension, loader)).second; } - void load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) override + void load(const boost::filesystem::path& filepath, int& index) override { LoadersContainer::iterator it(mLoaders.find(Misc::StringUtils::lowerCase(filepath.extension().string()))); if (it != mLoaders.end()) { - it->second->load(filepath, index, isGroundcover); + it->second->load(filepath, index); } else { @@ -2951,7 +2951,7 @@ namespace MWWorld const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); if (col.doesExist(file)) { - contentLoader.load(col.getPath(file), idx, false); + contentLoader.load(col.getPath(file), idx); } else { @@ -2961,13 +2961,15 @@ namespace MWWorld idx++; } + ESM::GroundcoverIndex = idx; + for (const std::string &file : groundcover) { boost::filesystem::path filename(file); const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); if (col.doesExist(file)) { - contentLoader.load(col.getPath(file), idx, true); + contentLoader.load(col.getPath(file), idx); } else { diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 4b9852d65..b4d6ac7a7 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -5,6 +5,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +namespace ESM +{ + int GroundcoverIndex = std::numeric_limits::max(); +} + void ESM::RefNum::load (ESMReader& esm, bool wide, const std::string& tag) { if (wide) diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp index 5bb7fbc53..c2f7ff6de 100644 --- a/components/esm/cellref.hpp +++ b/components/esm/cellref.hpp @@ -12,6 +12,7 @@ namespace ESM class ESMReader; const int UnbreakableLock = std::numeric_limits::max(); + extern int GroundcoverIndex; struct RefNum { @@ -25,6 +26,10 @@ namespace ESM enum { RefNum_NoContentFile = -1 }; inline bool hasContentFile() const { return mContentFile != RefNum_NoContentFile; } inline void unset() { mIndex = 0; mContentFile = RefNum_NoContentFile; } + + // Note: this method should not be used for objects with invalid RefNum + // (for example, for objects from disabled plugins in savegames). + inline bool fromGroundcoverFile() const { return mContentFile >= GroundcoverIndex; } }; /* Cell reference. This represents ONE object (of many) inside the diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 63d2f4d4f..1b6eca734 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -25,7 +25,6 @@ ESMReader::ESMReader() , mGlobalReaderList(nullptr) , mEncoder(nullptr) , mFileSize(0) - , mIsGroundcoverFile(false) { clearCtx(); } @@ -81,10 +80,8 @@ void ESMReader::openRaw(const std::string& filename) openRaw(Files::openConstrainedFileStream(filename.c_str()), filename); } -void ESMReader::open(Files::IStreamPtr _esm, const std::string &name, bool isGroundcover) +void ESMReader::open(Files::IStreamPtr _esm, const std::string &name) { - mIsGroundcoverFile = isGroundcover; - openRaw(_esm, name); if (getRecName() != "TES3") @@ -95,9 +92,9 @@ void ESMReader::open(Files::IStreamPtr _esm, const std::string &name, bool isGro mHeader.load (*this); } -void ESMReader::open(const std::string &file, bool isGroundcover) +void ESMReader::open(const std::string &file) { - open (Files::openConstrainedFileStream (file.c_str ()), file, isGroundcover); + open (Files::openConstrainedFileStream (file.c_str ()), file); } int64_t ESMReader::getHNLong(const char *name) diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index 600cd497b..c660b0dda 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -31,7 +31,6 @@ public: int getVer() const { return mHeader.mData.version; } int getRecordCount() const { return mHeader.mData.records; } - bool isGroundcoverFile() const { return mIsGroundcoverFile; } float getFVer() const { return (mHeader.mData.version == VER_12) ? 1.2f : 1.3f; } const std::string getAuthor() const { return mHeader.mData.author; } const std::string getDesc() const { return mHeader.mData.desc; } @@ -67,9 +66,9 @@ public: /// Load ES file from a new stream, parses the header. Closes the /// currently open file first, if any. - void open(Files::IStreamPtr _esm, const std::string &name, bool isGroundcover = false); + void open(Files::IStreamPtr _esm, const std::string &name); - void open(const std::string &file, bool isGroundcover = false); + void open(const std::string &file); void openRaw(const std::string &filename); @@ -290,8 +289,6 @@ private: ToUTF8::Utf8Encoder* mEncoder; size_t mFileSize; - - bool mIsGroundcoverFile; }; } #endif diff --git a/components/esm/loadstat.cpp b/components/esm/loadstat.cpp index 85e366f1c..6c9de22bd 100644 --- a/components/esm/loadstat.cpp +++ b/components/esm/loadstat.cpp @@ -37,8 +37,6 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - - mIsGroundcover = esm.isGroundcoverFile(); } void Static::save(ESMWriter &esm, bool isDeleted) const { diff --git a/components/esm/loadstat.hpp b/components/esm/loadstat.hpp index da94c4b8d..3d9144040 100644 --- a/components/esm/loadstat.hpp +++ b/components/esm/loadstat.hpp @@ -28,8 +28,6 @@ struct Static std::string mId, mModel; - bool mIsGroundcover = false; - void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; From b975f16e6b9ef5ee8f11dbed504d067a2087b9b1 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 12 Jan 2021 11:23:36 +0400 Subject: [PATCH 106/171] Remove redundant check - groundcover is not present in the CellStore --- apps/openmw/mwworld/cellpreloader.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 421de4a7d..31af5b24b 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -1,6 +1,5 @@ #include "cellpreloader.hpp" -#include #include #include @@ -37,8 +36,6 @@ namespace MWWorld virtual bool operator()(const MWWorld::Ptr& ptr) { - if (ptr.getCellRef().getRefNum().fromGroundcoverFile()) return true; - ptr.getClass().getModelsToPreload(ptr, mOut); return true; From f40e22768673eb9a7d8ffe2126f853659111d599 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 12 Jan 2021 12:39:19 +0400 Subject: [PATCH 107/171] Remove redundant formatting changes --- apps/openmw/engine.cpp | 2 +- apps/openmw/mwrender/objectpaging.cpp | 1 + apps/openmw/mwrender/renderingmanager.cpp | 1 + apps/openmw/mwworld/cellreflist.hpp | 2 -- apps/openmw/mwworld/cellstore.cpp | 1 + apps/openmw/mwworld/esmloader.cpp | 16 ++++++++-------- components/esm/esmreader.hpp | 1 + 7 files changed, 13 insertions(+), 11 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 103d06f31..dfaf09c21 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -725,7 +725,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) } // Create the world - mEnvironment.setWorld(new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), + mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder, mActivationDistanceOverride, mCellName, mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string())); mEnvironment.getWorld()->setupPlayer(); diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index b85358c20..7386c0069 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -373,6 +373,7 @@ namespace MWRender std::map refs; std::vector esm; const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX) { for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 68576cf44..6ba4baec5 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -73,6 +73,7 @@ namespace MWRender { + class StateUpdater : public SceneUtil::StateSetUpdater { public: diff --git a/apps/openmw/mwworld/cellreflist.hpp b/apps/openmw/mwworld/cellreflist.hpp index 69161c840..30be4a661 100644 --- a/apps/openmw/mwworld/cellreflist.hpp +++ b/apps/openmw/mwworld/cellreflist.hpp @@ -24,8 +24,6 @@ namespace MWWorld /// all methods are known. void load (ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore); - inline bool ignoreInstance (const X* ptr); - LiveRef &insert (const LiveRef &item) { mList.push_back(item); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 7ca35a1df..3f98684ae 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -169,6 +169,7 @@ namespace namespace MWWorld { + template void CellRefList::load(ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore) { diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index 46b806582..b12d646e7 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -17,15 +17,15 @@ EsmLoader::EsmLoader(MWWorld::ESMStore& store, std::vector& read void EsmLoader::load(const boost::filesystem::path& filepath, int& index) { - ContentLoader::load(filepath.filename(), index); + ContentLoader::load(filepath.filename(), index); - ESM::ESMReader lEsm; - lEsm.setEncoder(mEncoder); - lEsm.setIndex(index); - lEsm.setGlobalReaderList(&mEsm); - lEsm.open(filepath.string()); - mEsm[index] = lEsm; - mStore.load(mEsm[index], &mListener); + ESM::ESMReader lEsm; + lEsm.setEncoder(mEncoder); + lEsm.setIndex(index); + lEsm.setGlobalReaderList(&mEsm); + lEsm.open(filepath.string()); + mEsm[index] = lEsm; + mStore.load(mEsm[index], &mListener); } } /* namespace MWWorld */ diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index c660b0dda..761756e8f 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -289,6 +289,7 @@ private: ToUTF8::Utf8Encoder* mEncoder; size_t mFileSize; + }; } #endif From 24e1dfcddc50bd51a019d062cc8ad7f4ea2b1515 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Jan 2021 14:30:51 +0400 Subject: [PATCH 108/171] Use default argument --- components/resource/scenemanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 0a4faed01..71f11e382 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -514,7 +514,7 @@ namespace Resource SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor(mMinFilter, mMagFilter, mMaxAnisotropy); loaded->accept(setFilterSettingsControllerVisitor); - osg::ref_ptr shaderVisitor (createShaderVisitor("objects")); + osg::ref_ptr shaderVisitor (createShaderVisitor()); loaded->accept(*shaderVisitor); // share state From e3490c8606c3c83a981421ddfc5bdd74e25c88ae Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 23 Jan 2021 09:30:57 +0400 Subject: [PATCH 109/171] Remove dead code --- files/shaders/groundcover_fragment.glsl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/files/shaders/groundcover_fragment.glsl b/files/shaders/groundcover_fragment.glsl index 9c680853a..77fd32e58 100644 --- a/files/shaders/groundcover_fragment.glsl +++ b/files/shaders/groundcover_fragment.glsl @@ -49,10 +49,6 @@ void main() vec3 viewNormal = gl_NormalMatrix * normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); #endif -#if (!@normalMap && @forcePPL && false) - vec3 viewNormal = gl_NormalMatrix * normalize(passNormal); -#endif - #if @diffuseMap gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); #else From 5225ec9e5045142f7191b6d81a19350f694682b3 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 26 Jan 2021 22:32:06 +0400 Subject: [PATCH 110/171] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96f0460f1..c6c7b5757 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -127,6 +127,7 @@ Feature #5692: Improve spell/magic item search to factor in magic effect names Feature #5730: Add graphic herbalism option to the launcher and documents Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used. + Feature #5813: Instanced groundcover support Task #5480: Drop Qt4 support Task #5520: Improve cell name autocompleter implementation From 64475ebedb803a853c62062eb199df2183076005 Mon Sep 17 00:00:00 2001 From: fredzio Date: Wed, 27 Jan 2021 07:15:09 +0100 Subject: [PATCH 111/171] Remove a brainfart from precise projectile handling: all non-actor non-projectile objects were treated as ground. --- apps/openmw/mwphysics/projectileconvexcallback.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwphysics/projectileconvexcallback.cpp b/apps/openmw/mwphysics/projectileconvexcallback.cpp index 0d0ac8720..b803c4400 100644 --- a/apps/openmw/mwphysics/projectileconvexcallback.cpp +++ b/apps/openmw/mwphysics/projectileconvexcallback.cpp @@ -55,7 +55,9 @@ namespace MWPhysics } default: { - mProjectile->hit(MWWorld::Ptr(), m_hitPointWorld, m_hitNormalWorld); + auto* target = static_cast(result.m_hitCollisionObject->getUserPointer()); + auto ptr = target ? target->getPtr() : MWWorld::Ptr(); + mProjectile->hit(ptr, m_hitPointWorld, m_hitNormalWorld); break; } } From 7cd7fa2f08eaba7de05532a2a1998f2f751959f7 Mon Sep 17 00:00:00 2001 From: Frederic Chardon Date: Wed, 27 Jan 2021 08:04:33 +0000 Subject: [PATCH 112/171] Collect all available stats if OPENMW_OSG_STATS_FILE is set and point to a valid file. --- apps/openmw/engine.cpp | 27 ++++--- apps/openmw/mwphysics/mtphysics.cpp | 2 + components/resource/stats.cpp | 110 +++++++++++++++++++++++++++- components/resource/stats.hpp | 11 ++- 4 files changed, 135 insertions(+), 15 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index ead2726cd..a81a37337 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -177,6 +177,8 @@ namespace ~ScopedProfile() { + if (!mStats.collectStats("engine")) + return; const osg::Timer_t end = mTimer.tick(); const UserStats& stats = UserStatsValue::sValue; @@ -863,16 +865,29 @@ void OMW::Engine::go() prepareEngine (settings); + std::ofstream stats; + if (const auto path = std::getenv("OPENMW_OSG_STATS_FILE")) + { + stats.open(path, std::ios_base::out); + if (stats.is_open()) + Log(Debug::Info) << "Stats will be written to: " << path; + else + Log(Debug::Warning) << "Failed to open file for stats: " << path; + } + // Setup profiler - osg::ref_ptr statshandler = new Resource::Profiler; + osg::ref_ptr statshandler = new Resource::Profiler(stats.is_open()); initStatsHandler(*statshandler); mViewer->addEventHandler(statshandler); - osg::ref_ptr resourceshandler = new Resource::StatsHandler; + osg::ref_ptr resourceshandler = new Resource::StatsHandler(stats.is_open()); mViewer->addEventHandler(resourceshandler); + if (stats.is_open()) + Resource::CollectStatistics(mViewer); + // Start the game if (!mSaveGameFile.empty()) { @@ -897,14 +912,6 @@ void OMW::Engine::go() mEnvironment.getWindowManager()->executeInConsole(mStartupScript); } - std::ofstream stats; - if (const auto path = std::getenv("OPENMW_OSG_STATS_FILE")) - { - stats.open(path, std::ios_base::out); - if (!stats) - Log(Debug::Warning) << "Failed to open file for stats: " << path; - } - // Start the main rendering loop osg::Timer frameTimer; double simulationTime = 0.0; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 6c7c573a4..754bb60af 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -540,6 +540,8 @@ namespace MWPhysics void PhysicsTaskScheduler::updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { + if (!stats.collectStats("engine")) + return; if (mFrameNumber == frameNumber - 1) { stats.setAttribute(mFrameNumber, "physicsworker_time_begin", mTimer->delta_s(mFrameStart, mTimeBegin)); diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 942bd92d8..28b90c31c 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -18,10 +18,72 @@ namespace Resource { -StatsHandler::StatsHandler(): +static bool collectStatRendering = false; +static bool collectStatCameraObjects = false; +static bool collectStatViewerObjects = false; +static bool collectStatResource = false; +static bool collectStatGPU = false; +static bool collectStatEvent = false; +static bool collectStatFrameRate = false; +static bool collectStatUpdate = false; +static bool collectStatEngine = false; + +static void setupStatCollection() +{ + const char* envList = getenv("OPENMW_OSG_STATS_LIST"); + if (envList == nullptr) + return; + + std::string_view kwList(envList); + + auto kwBegin = kwList.begin(); + + while (kwBegin != kwList.end()) + { + auto kwEnd = std::find(kwBegin, kwList.end(), ';'); + + const auto kw = kwList.substr(std::distance(kwList.begin(), kwBegin), std::distance(kwBegin, kwEnd)); + + if (kw.compare("gpu") == 0) + collectStatGPU = true; + else if (kw.compare("event") == 0) + collectStatEvent = true; + else if (kw.compare("frame_rate") == 0) + collectStatFrameRate = true; + else if (kw.compare("update") == 0) + collectStatUpdate = true; + else if (kw.compare("engine") == 0) + collectStatEngine = true; + else if (kw.compare("rendering") == 0) + collectStatRendering = true; + else if (kw.compare("cameraobjects") == 0) + collectStatCameraObjects = true; + else if (kw.compare("viewerobjects") == 0) + collectStatViewerObjects = true; + else if (kw.compare("resource") == 0) + collectStatResource = true; + else if (kw.compare("times") == 0) + { + collectStatGPU = true; + collectStatEvent = true; + collectStatFrameRate = true; + collectStatUpdate = true; + collectStatEngine = true; + collectStatRendering = true; + } + + if (kwEnd == kwList.end()) + break; + + kwBegin = std::next(kwEnd); + } +} + +StatsHandler::StatsHandler(bool offlineCollect): _key(osgGA::GUIEventAdapter::KEY_F4), _initialized(false), _statsType(false), + _offlineCollect(offlineCollect), _statsWidth(1280.0f), _statsHeight(1024.0f), _font(""), @@ -38,7 +100,8 @@ StatsHandler::StatsHandler(): _font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf"); } -Profiler::Profiler() +Profiler::Profiler(bool offlineCollect): + _offlineCollect(offlineCollect) { if (osgDB::Registry::instance()->getReaderWriterForExtension("ttf")) _font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf"); @@ -48,6 +111,28 @@ Profiler::Profiler() _characterSize = 18; setKeyEventTogglesOnScreenStats(osgGA::GUIEventAdapter::KEY_F3); + setupStatCollection(); +} + +bool Profiler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa) +{ + osgViewer::ViewerBase* viewer = nullptr; + + bool handled = StatsHandler::handle(ea, aa); + + auto* view = dynamic_cast(&aa); + if (view) + viewer = view->getViewerBase(); + + if (viewer) + { + // Add/remove openmw stats to the osd as necessary + viewer->getViewerStats()->collectStats("engine", _statsType == StatsHandler::StatsType::VIEWER_STATS); + + if (_offlineCollect) + CollectStatistics(viewer); + } + return handled; } bool StatsHandler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa) @@ -67,6 +152,9 @@ bool StatsHandler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdap toggle(viewer); + if (_offlineCollect) + CollectStatistics(viewer); + aa.requestRedraw(); return true; } @@ -370,6 +458,22 @@ void StatsHandler::getUsage(osg::ApplicationUsage &usage) const usage.addKeyboardMouseBinding(_key, "On screen resource usage stats."); } - +void CollectStatistics(osgViewer::ViewerBase* viewer) +{ + osgViewer::Viewer::Cameras cameras; + viewer->getCameras(cameras); + for (auto* camera : cameras) + { + if (collectStatGPU) camera->getStats()->collectStats("gpu", true); + if (collectStatRendering) camera->getStats()->collectStats("rendering", true); + if (collectStatCameraObjects) camera->getStats()->collectStats("scene", true); + } + if (collectStatEvent) viewer->getViewerStats()->collectStats("event", true); + if (collectStatFrameRate) viewer->getViewerStats()->collectStats("frame_rate", true); + if (collectStatUpdate) viewer->getViewerStats()->collectStats("update", true); + if (collectStatResource) viewer->getViewerStats()->collectStats("resource", true); + if (collectStatViewerObjects) viewer->getViewerStats()->collectStats("scene", true); + if (collectStatEngine) viewer->getViewerStats()->collectStats("engine", true); +} } diff --git a/components/resource/stats.hpp b/components/resource/stats.hpp index 9fa583cca..560275d70 100644 --- a/components/resource/stats.hpp +++ b/components/resource/stats.hpp @@ -18,13 +18,17 @@ namespace Resource class Profiler : public osgViewer::StatsHandler { public: - Profiler(); + Profiler(bool offlineCollect); + bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) override; + + private: + bool _offlineCollect; }; class StatsHandler : public osgGA::GUIEventHandler { public: - StatsHandler(); + StatsHandler(bool offlineCollect); void setKey(int key) { _key = key; } int getKey() const { return _key; } @@ -47,6 +51,7 @@ namespace Resource osg::ref_ptr _camera; bool _initialized; bool _statsType; + bool _offlineCollect; float _statsWidth; float _statsHeight; @@ -58,6 +63,8 @@ namespace Resource }; + void CollectStatistics(osgViewer::ViewerBase* viewer); + } #endif From 3194520dcd5eecb04540af0fc936bd303b38c566 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 30 Dec 2020 22:11:32 +0200 Subject: [PATCH 113/171] Move base_anim settings to settings-default.cfg --- apps/openmw/mwclass/npc.cpp | 12 ++++++------ apps/openmw/mwrender/animation.cpp | 2 +- apps/openmw/mwrender/creatureanimation.cpp | 6 +++--- apps/openmw/mwrender/npcanimation.cpp | 4 ++-- apps/openmw/mwrender/renderingmanager.cpp | 15 +++++++++------ components/resource/scenemanager.cpp | 2 ++ components/sceneutil/actorutil.cpp | 18 ++++++++++-------- files/settings-default.cfg | 16 ++++++++++++++++ 8 files changed, 49 insertions(+), 26 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 27c98bbce..1f7e77daf 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -418,10 +418,10 @@ namespace MWClass { const MWWorld::LiveCellRef *ref = ptr.get(); - std::string model = "meshes\\base_anim.nif"; + std::string model = Settings::Manager::getString("xbaseanim", "Models"); const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); if(race->mData.mFlags & ESM::Race::Beast) - model = "meshes\\base_animkna.nif"; + model = Settings::Manager::getString("baseanimkna", "Models"); return model; } @@ -431,12 +431,12 @@ namespace MWClass const MWWorld::LiveCellRef *npc = ptr.get(); const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().search(npc->mBase->mRace); if(race && race->mData.mFlags & ESM::Race::Beast) - models.emplace_back("meshes\\base_animkna.nif"); + models.emplace_back(Settings::Manager::getString("baseanimkna", "Models")); // keep these always loaded just in case - models.emplace_back("meshes/xargonian_swimkna.nif"); - models.emplace_back("meshes/xbase_anim_female.nif"); - models.emplace_back("meshes/xbase_anim.nif"); + models.emplace_back(Settings::Manager::getString("xargonianswimkna", "Models")); + models.emplace_back(Settings::Manager::getString("xbaseanimfemale", "Models")); + models.emplace_back(Settings::Manager::getString("xbaseanim", "Models")); if (!npc->mBase->mModel.empty()) models.push_back("meshes/"+npc->mBase->mModel); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index f8ff3780d..d8c759935 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1500,7 +1500,7 @@ namespace MWRender MWWorld::LiveCellRef *ref = mPtr.get(); if(ref->mBase->mFlags & ESM::Creature::Bipedal) { - defaultSkeleton = "meshes\\xbase_anim.nif"; + defaultSkeleton = Settings::Manager::getString("xbaseanim", "Models"); inject = true; } } diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index f1df6c90f..298711162 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -10,7 +10,7 @@ #include #include #include - +#include #include #include "../mwbase/environment.hpp" @@ -35,7 +35,7 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr, setObjectRoot(model, false, false, true); if((ref->mBase->mFlags&ESM::Creature::Bipedal)) - addAnimSource("meshes\\xbase_anim.nif", model); + addAnimSource(Settings::Manager::getString("xbaseanim", "Models"), model); addAnimSource(model, model); } } @@ -54,7 +54,7 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const if((ref->mBase->mFlags&ESM::Creature::Bipedal)) { - addAnimSource("meshes\\xbase_anim.nif", model); + addAnimSource(Settings::Manager::getString("xbaseanim", "Models"), model); } addAnimSource(model, model); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index d97e57115..b538f0b7b 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -524,7 +524,7 @@ void NpcAnimation::updateNpcBase() if(!is1stPerson) { - const std::string base = "meshes\\xbase_anim.nif"; + const std::string base = Settings::Manager::getString("xbaseanim", "Models"); if (smodel != base && !isWerewolf) addAnimSource(base, smodel); @@ -538,7 +538,7 @@ void NpcAnimation::updateNpcBase() } else { - const std::string base = "meshes\\xbase_anim.1st.nif"; + const std::string base = Settings::Manager::getString("xbaseanim1st", "Models"); if (smodel != base && !isWerewolf) addAnimSource(base, smodel); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6ba4baec5..a3aee5c0f 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -456,12 +456,15 @@ namespace MWRender mSky->listAssetsToPreload(workItem->mModels, workItem->mTextures); mWater->listAssetsToPreload(workItem->mTextures); - const char* basemodels[] = {"xbase_anim", "xbase_anim.1st", "xbase_anim_female", "xbase_animkna"}; - for (size_t i=0; imModels.push_back(std::string("meshes/") + basemodels[i] + ".nif"); - workItem->mKeyframes.push_back(std::string("meshes/") + basemodels[i] + ".kf"); - } + workItem->mModels.push_back(Settings::Manager::getString("xbaseanim", "Models")); + workItem->mModels.push_back(Settings::Manager::getString("xbaseanim1st", "Models")); + workItem->mModels.push_back(Settings::Manager::getString("xbaseanimfemale", "Models")); + workItem->mModels.push_back(Settings::Manager::getString("xargonianswimkna", "Models")); + + workItem->mKeyframes.push_back(Settings::Manager::getString("xbaseanimkf", "Models")); + workItem->mKeyframes.push_back(Settings::Manager::getString("xbaseanim1stkf", "Models")); + workItem->mKeyframes.push_back(Settings::Manager::getString("xbaseanimfemalekf", "Models")); + workItem->mKeyframes.push_back(Settings::Manager::getString("xargonianswimknakf", "Models")); workItem->mTextures.emplace_back("textures/_land_default.dds"); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 71f11e382..92de2e0dd 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -26,6 +26,8 @@ #include #include +#include + #include #include diff --git a/components/sceneutil/actorutil.cpp b/components/sceneutil/actorutil.cpp index 988a61f60..a0785e413 100644 --- a/components/sceneutil/actorutil.cpp +++ b/components/sceneutil/actorutil.cpp @@ -1,5 +1,7 @@ #include "actorutil.hpp" +#include + namespace SceneUtil { std::string getActorSkeleton(bool firstPerson, bool isFemale, bool isBeast, bool isWerewolf) @@ -7,24 +9,24 @@ namespace SceneUtil if (!firstPerson) { if (isWerewolf) - return "meshes\\wolf\\skin.nif"; + return Settings::Manager::getString("wolfskin", "Models"); else if (isBeast) - return "meshes\\base_animkna.nif"; + return Settings::Manager::getString("baseanimkna", "Models"); else if (isFemale) - return "meshes\\base_anim_female.nif"; + return Settings::Manager::getString("baseanimfemale", "Models"); else - return "meshes\\base_anim.nif"; + return Settings::Manager::getString("baseanim", "Models"); } else { if (isWerewolf) - return "meshes\\wolf\\skin.1st.nif"; + return Settings::Manager::getString("wolfskin1st", "Models"); else if (isBeast) - return "meshes\\base_animkna.1st.nif"; + return Settings::Manager::getString("baseanimkna1st", "Models"); else if (isFemale) - return "meshes\\base_anim_female.1st.nif"; + return Settings::Manager::getString("baseanimfemale1st", "Models"); else - return "meshes\\base_anim.1st.nif"; + return Settings::Manager::getString("xbaseanim1st", "Models"); } } } diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 3660f56f0..db947f8ed 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -969,3 +969,19 @@ distance = 1 # A minimum size of groundcover chunk in cells (0.125, 0.25, 0.5, 1.0) min chunk size = 0.5 + +xbaseanim = meshes\xbase_anim.nif +baseanim = meshes\base_anim.nif +xbaseanim1st = meshes\xbase_anim.1st.nif +baseanimkna = meshes\base_animkna.nif +baseanimkna1st = meshes\base_animkna.1st.nif +xbaseanimfemale = meshes/xbase_anim_female.nif +baseanimfemale = meshes/base_anim_female.nif +baseanimfemale1st = meshes\base_anim_female.1st.nif +wolfskin = meshes\wolf\skin.nif +wolfskin1st = meshes\wolf\skin.1st.nif +xargonianswimkna = meshes/xargonian_swimkna.nif +xbaseanimkf = meshes\xbase_anim.kf +xbaseanim1stkf = meshes\xbase_anim.1st.kf +xbaseanimfemalekf = meshes/xbase_anim_female.kf +xargonianswimknakf = meshes/xargonian_swimkna.kf From 83ee1cc582e355a78e836d674bb228ec47cd1c9e Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Fri, 1 Jan 2021 22:51:23 +0200 Subject: [PATCH 114/171] fix xbase to baseanim --- apps/openmw/mwclass/npc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 1f7e77daf..5de4c197e 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -418,7 +418,7 @@ namespace MWClass { const MWWorld::LiveCellRef *ref = ptr.get(); - std::string model = Settings::Manager::getString("xbaseanim", "Models"); + std::string model = Settings::Manager::getString("baseanim", "Models"); const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); if(race->mData.mFlags & ESM::Race::Beast) model = Settings::Manager::getString("baseanimkna", "Models"); From 23137d0c54f555b30323359953e3db81ded3f361 Mon Sep 17 00:00:00 2001 From: fredzio Date: Wed, 27 Jan 2021 16:24:11 +0100 Subject: [PATCH 115/171] Revert a wrong change introduced in MR 546 A prerequisite to create physics objects for statics was to remove the dependency on base node (since it doesn't yet exists) for object position. It is still necessary for animation though. Restore the basenode (and the associated FIXME) so that animated objects works properly. --- apps/openmw/mwphysics/object.cpp | 3 --- apps/openmw/mwworld/scene.cpp | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 0a7b9540c..6d3954088 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -116,9 +116,6 @@ namespace MWPhysics if (mShapeInstance->mAnimatedShapes.empty()) return false; - if (mPtr.getRefData().getBaseNode() == nullptr) - return true; - assert (mShapeInstance->getCollisionShape()->isCompound()); btCompoundShape* compound = static_cast(mShapeInstance->getCollisionShape()); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index fcf2c4b38..2abedcd78 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -122,6 +122,8 @@ namespace const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) ptr.getClass().insertObjectRendering(ptr, model, rendering); + else + ptr.getRefData().setBaseNode(new SceneUtil::PositionAttitudeTransform); // FIXME remove this when physics code is fixed not to depend on basenode setNodeRotation(ptr, rendering, rotation); From a3ab8dfbb44aef52e895e8746e525d82519799b5 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 28 Jan 2021 12:48:19 +0000 Subject: [PATCH 116/171] Revert "Merge branch 'movement_fix2' into 'master'" This reverts merge request !496 --- apps/openmw/mwmechanics/pathfinding.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 595f9d629..276321b81 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -309,7 +309,7 @@ namespace MWMechanics if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance)) mPath.pop_front(); - if (mPath.size() == 1 && (mPath.front() - position).length2() < destinationTolerance * destinationTolerance) + if (mPath.size() == 1 && sqrDistanceIgnoreZ(mPath.front(), position) < destinationTolerance * destinationTolerance) mPath.pop_front(); } From 3b9f8b5fa2318e35bae594ad94a759e2d6d4a998 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 28 Jan 2021 18:37:47 +0400 Subject: [PATCH 117/171] Avoid null dereference for objects without cells --- apps/openmw/mwworld/worldimp.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 98af121a5..ef00315cb 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1337,6 +1337,12 @@ namespace MWWorld void World::adjustPosition(const Ptr &ptr, bool force) { + if (ptr.isEmpty()) + { + Log(Debug::Warning) << "Unable to adjust position for empty object"; + return; + } + osg::Vec3f pos (ptr.getRefData().getPosition().asVec3()); if(!ptr.getRefData().getBaseNode()) @@ -1345,6 +1351,12 @@ namespace MWWorld return; } + if (!ptr.isInCell()) + { + Log(Debug::Warning) << "Unable to adjust position for object '" << ptr.getCellRef().getRefId() << "' - it has no cell"; + return; + } + const float terrainHeight = ptr.getCell()->isExterior() ? getTerrainHeightAt(pos) : -std::numeric_limits::max(); pos.z() = std::max(pos.z(), terrainHeight) + 20; // place slightly above terrain. will snap down to ground with code below From 36cd81815504cef195fe853b0b93046ac04c3c19 Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 28 Jan 2021 22:10:33 +0100 Subject: [PATCH 118/171] Fix separate drop, refactor for code reuse --- apps/opencs/view/render/instancemode.cpp | 116 ++++++++--------------- apps/opencs/view/render/instancemode.hpp | 21 ++-- 2 files changed, 54 insertions(+), 83 deletions(-) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 4f6759cdb..2489f4f57 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -802,39 +802,15 @@ void CSVRender::InstanceMode::deleteSelectedInstances(bool active) getWorldspaceWidget().clearSelection (Mask_Reference); } -void CSVRender::InstanceMode::dropInstance(DropMode dropMode, CSVRender::Object* object, float objectHeight) +void CSVRender::InstanceMode::dropInstance(CSVRender::Object* object, float dropHeight) { - osg::Vec3d point = object->getPosition().asVec3(); - - osg::Vec3d start = point; - start.z() += objectHeight; - osg::Vec3d end = point; - end.z() = std::numeric_limits::lowest(); - - osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector( - osgUtil::Intersector::MODEL, start, end) ); - intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); - osgUtil::IntersectionVisitor visitor(intersector); - - if (dropMode == TerrainSep) - visitor.setTraversalMask(Mask_Terrain); - if (dropMode == CollisionSep) - visitor.setTraversalMask(Mask_Terrain | Mask_Reference); - - mParentNode->accept(visitor); - - osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin(); - if (it != intersector->getIntersections().end()) - { - osgUtil::LineSegmentIntersector::Intersection intersection = *it; - ESM::Position position = object->getPosition(); - object->setEdited (Object::Override_Position); - position.pos[2] = intersection.getWorldIntersectPoint().z() + objectHeight; - object->setPosition(position.pos); - } + object->setEdited(Object::Override_Position); + ESM::Position position = object->getPosition(); + position.pos[2] -= dropHeight; + object->setPosition(position.pos); } -float CSVRender::InstanceMode::getDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight) +float CSVRender::InstanceMode::calculateDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight) { osg::Vec3d point = object->getPosition().asVec3(); @@ -848,9 +824,9 @@ float CSVRender::InstanceMode::getDropHeight(DropMode dropMode, CSVRender::Objec intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); osgUtil::IntersectionVisitor visitor(intersector); - if (dropMode == Terrain) + if (dropMode & Terrain) visitor.setTraversalMask(Mask_Terrain); - if (dropMode == Collision) + if (dropMode & Collision) visitor.setTraversalMask(Mask_Terrain | Mask_Reference); mParentNode->accept(visitor); @@ -897,52 +873,44 @@ void CSVRender::InstanceMode::handleDropMethod(DropMode dropMode, QString comman CSMWorld::CommandMacro macro (undoStack, commandMsg); - DropObjectDataHandler dropObjectDataHandler(&getWorldspaceWidget()); + DropObjectHeightHandler dropObjectDataHandler(&getWorldspaceWidget()); - switch (dropMode) + if(dropMode & Separate) { - case Terrain: - case Collision: - { - float smallestDropHeight = std::numeric_limits::max(); - int counter = 0; - for(osg::ref_ptr tag: selection) - if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) - { - float thisDrop = getDropHeight(dropMode, objectTag->mObject, dropObjectDataHandler.mObjectHeights[counter]); - if (thisDrop < smallestDropHeight) - smallestDropHeight = thisDrop; - counter++; - } - for(osg::ref_ptr tag: selection) - if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) - { - objectTag->mObject->setEdited (Object::Override_Position); - ESM::Position position = objectTag->mObject->getPosition(); - position.pos[2] -= smallestDropHeight; - objectTag->mObject->setPosition(position.pos); - objectTag->mObject->apply (macro); - } - } - break; - - case TerrainSep: - case CollisionSep: - { - int counter = 0; - for(osg::ref_ptr tag: selection) - if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) - { - dropInstance(dropMode, objectTag->mObject, dropObjectDataHandler.mObjectHeights[counter]); - objectTag->mObject->apply (macro); - counter++; - } - } - break; + int counter = 0; + for (osg::ref_ptr tag : selection) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) + { + float objectHeight = dropObjectDataHandler.mObjectHeights[counter]; + float dropHeight = calculateDropHeight(dropMode, objectTag->mObject, objectHeight); + dropInstance(objectTag->mObject, dropHeight); + objectTag->mObject->apply(macro); + counter++; + } + } + else + { + float smallestDropHeight = std::numeric_limits::max(); + int counter = 0; + for (osg::ref_ptr tag : selection) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) + { + float objectHeight = dropObjectDataHandler.mObjectHeights[counter]; + float thisDrop = calculateDropHeight(dropMode, objectTag->mObject, objectHeight); + if (thisDrop < smallestDropHeight) + smallestDropHeight = thisDrop; + counter++; + } + for (osg::ref_ptr tag : selection) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) + { + dropInstance(objectTag->mObject, smallestDropHeight); + objectTag->mObject->apply(macro); + } } } -CSVRender::DropObjectDataHandler::DropObjectDataHandler(WorldspaceWidget* worldspacewidget) +CSVRender::DropObjectHeightHandler::DropObjectHeightHandler(WorldspaceWidget* worldspacewidget) : mWorldspaceWidget(worldspacewidget) { std::vector > selection = mWorldspaceWidget->getSelection (Mask_Reference); @@ -969,7 +937,7 @@ CSVRender::DropObjectDataHandler::DropObjectDataHandler(WorldspaceWidget* worlds } } -CSVRender::DropObjectDataHandler::~DropObjectDataHandler() +CSVRender::DropObjectHeightHandler::~DropObjectHeightHandler() { std::vector > selection = mWorldspaceWidget->getSelection (Mask_Reference); int counter = 0; diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 0a4f2e478..d0a263ed8 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -28,10 +28,13 @@ namespace CSVRender enum DropMode { - Collision, - Terrain, - CollisionSep, - TerrainSep + Separate = 0x1, + + Collision = 0b10, + Terrain = 0b100, + + CollisionSep = Collision | Separate, + TerrainSep = Terrain | Separate, }; CSVWidget::SceneToolMode *mSubMode; @@ -53,8 +56,8 @@ namespace CSVRender osg::Vec3f getProjectionSpaceCoords(const osg::Vec3f& pos); osg::Vec3f getMousePlaneCoords(const QPoint& point, const osg::Vec3d& dragStart); void handleSelectDrag(const QPoint& pos); - void dropInstance(DropMode dropMode, CSVRender::Object* object, float objectHeight); - float getDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight); + void dropInstance(CSVRender::Object* object, float objectHeight); + float calculateDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight); public: @@ -116,11 +119,11 @@ namespace CSVRender }; /// \brief Helper class to handle object mask data in safe way - class DropObjectDataHandler + class DropObjectHeightHandler { public: - DropObjectDataHandler(WorldspaceWidget* worldspacewidget); - ~DropObjectDataHandler(); + DropObjectHeightHandler(WorldspaceWidget* worldspacewidget); + ~DropObjectHeightHandler(); std::vector mObjectHeights; private: From edc6d5c3e725a2da0c53d82dc025f718008e185d Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 28 Jan 2021 22:22:48 +0100 Subject: [PATCH 119/171] Fix a typo in separate drop binds --- apps/opencs/view/render/instancemode.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 2489f4f57..99ddce7f7 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -854,12 +854,12 @@ void CSVRender::InstanceMode::dropSelectedInstancesToTerrain() void CSVRender::InstanceMode::dropSelectedInstancesToCollisionSeparately() { - handleDropMethod(TerrainSep, "Drop instances to next collision level separately"); + handleDropMethod(CollisionSep, "Drop instances to next collision level separately"); } void CSVRender::InstanceMode::dropSelectedInstancesToTerrainSeparately() { - handleDropMethod(CollisionSep, "Drop instances to terrain level separately"); + handleDropMethod(TerrainSep, "Drop instances to terrain level separately"); } void CSVRender::InstanceMode::handleDropMethod(DropMode dropMode, QString commandMsg) From eca0d8b7ea9a9e63779a8dff55623c12a151f870 Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 28 Jan 2021 22:40:44 +0100 Subject: [PATCH 120/171] Fix typo in DropMode enum --- apps/opencs/view/render/instancemode.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index d0a263ed8..c81c153d4 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -28,7 +28,7 @@ namespace CSVRender enum DropMode { - Separate = 0x1, + Separate = 0b1, Collision = 0b10, Terrain = 0b100, From ee2f0e7eb33d1226f4e9b961f8a355224bff079f Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 28 Jan 2021 23:47:38 +0100 Subject: [PATCH 121/171] Fix inconsistent argument name --- apps/opencs/view/render/instancemode.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index c81c153d4..73b7fff12 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -56,7 +56,7 @@ namespace CSVRender osg::Vec3f getProjectionSpaceCoords(const osg::Vec3f& pos); osg::Vec3f getMousePlaneCoords(const QPoint& point, const osg::Vec3d& dragStart); void handleSelectDrag(const QPoint& pos); - void dropInstance(CSVRender::Object* object, float objectHeight); + void dropInstance(CSVRender::Object* object, float dropHeight); float calculateDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight); public: From f8e8496d36b2a232c15c421b1d55c3c8a9cf49f4 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 29 Jan 2021 16:50:39 +0400 Subject: [PATCH 122/171] Revert "Revert a wrong change introduced in MR 546" This reverts commit 23137d0c54f555b30323359953e3db81ded3f361. --- apps/openmw/mwphysics/object.cpp | 3 +++ apps/openmw/mwworld/scene.cpp | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 6d3954088..0a7b9540c 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -116,6 +116,9 @@ namespace MWPhysics if (mShapeInstance->mAnimatedShapes.empty()) return false; + if (mPtr.getRefData().getBaseNode() == nullptr) + return true; + assert (mShapeInstance->getCollisionShape()->isCompound()); btCompoundShape* compound = static_cast(mShapeInstance->getCollisionShape()); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 2abedcd78..fcf2c4b38 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -122,8 +122,6 @@ namespace const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) ptr.getClass().insertObjectRendering(ptr, model, rendering); - else - ptr.getRefData().setBaseNode(new SceneUtil::PositionAttitudeTransform); // FIXME remove this when physics code is fixed not to depend on basenode setNodeRotation(ptr, rendering, rotation); From 8019fd594defac7d18e88c690f1bde910811a85d Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 29 Jan 2021 16:50:56 +0400 Subject: [PATCH 123/171] Revert "Add changelog" This reverts commit f219c5992bc6efe688ab128915f2d36c39da47a0. --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6c7b5757..f347fb46e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,7 +44,6 @@ Bug #5367: Selecting a spell on an enchanted item per hotkey always plays the equip sound Bug #5369: Spawnpoint in the Grazelands doesn't produce oversized creatures Bug #5370: Opening an unlocked but trapped door uses the key - Bug #5379: Wandering NPCs falling through cantons Bug #5384: openmw-cs: deleting an instance requires reload of scene window to show in editor Bug #5387: Move/MoveWorld don't update the object's cell properly Bug #5391: Races Redone 1.2 bodies don't show on the inventory From 165af1c365628a4e2ae1bec6a54cc39b335db83a Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 29 Jan 2021 16:51:05 +0400 Subject: [PATCH 124/171] Revert "Some actors are supposed to spawn on a static object that belong to an adjacent cell." This reverts commit f031a191b847443c848637b17d0936a43b5070b5. --- apps/openmw/mwclass/static.cpp | 5 - apps/openmw/mwclass/static.hpp | 2 - apps/openmw/mwphysics/physicssystem.cpp | 5 +- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwworld/cellvisitors.hpp | 13 +- apps/openmw/mwworld/class.hpp | 4 - apps/openmw/mwworld/scene.cpp | 405 ++++++++++-------------- apps/openmw/mwworld/scene.hpp | 15 +- 8 files changed, 182 insertions(+), 269 deletions(-) diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 28156c97f..108c4eaa2 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -63,9 +63,4 @@ namespace MWClass return MWWorld::Ptr(cell.insert(ref), &cell); } - - bool Static::isStatic() const - { - return true; - } } diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index d0f4913f0..f856e9fd9 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -25,8 +25,6 @@ namespace MWClass static void registerSelf(); std::string getModel(const MWWorld::ConstPtr &ptr) const override; - - bool isStatic() const override; }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 5a9a0be83..68cec48bc 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -469,7 +469,7 @@ namespace MWPhysics mAnimatedObjects.insert(obj.get()); } - void PhysicsSystem::remove(const MWWorld::Ptr &ptr, bool keepObject) + void PhysicsSystem::remove(const MWWorld::Ptr &ptr) { ObjectMap::iterator found = mObjects.find(ptr); if (found != mObjects.end()) @@ -479,8 +479,7 @@ namespace MWPhysics mAnimatedObjects.erase(found->second.get()); - if (!keepObject) - mObjects.erase(found); + mObjects.erase(found); } ActorMap::iterator foundActor = mActors.find(ptr); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 715a6cd1a..c61b368f8 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -138,7 +138,7 @@ namespace MWPhysics Projectile* getProjectile(int projectileId) const; // Object or Actor - void remove (const MWWorld::Ptr& ptr, bool keepObject = false); + void remove (const MWWorld::Ptr& ptr); void updateScale (const MWWorld::Ptr& ptr); void updateRotation (const MWWorld::Ptr& ptr, osg::Quat rotate); diff --git a/apps/openmw/mwworld/cellvisitors.hpp b/apps/openmw/mwworld/cellvisitors.hpp index 5985d06fb..e68b383b7 100644 --- a/apps/openmw/mwworld/cellvisitors.hpp +++ b/apps/openmw/mwworld/cellvisitors.hpp @@ -18,23 +18,12 @@ namespace MWWorld if (ptr.getRefData().getBaseNode()) { ptr.getRefData().setBaseNode(nullptr); + mObjects.push_back (ptr); } - mObjects.push_back (ptr); return true; } }; - - struct ListObjectsVisitor - { - std::vector mObjects; - - bool operator() (MWWorld::Ptr ptr) - { - mObjects.push_back (ptr); - return true; - } - }; } #endif diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 592552d47..39fb6fe4c 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -318,10 +318,6 @@ namespace MWWorld return false; } - virtual bool isStatic() const { - return false; - } - virtual bool isBipedal(const MWWorld::ConstPtr& ptr) const; virtual bool canFly(const MWWorld::ConstPtr& ptr) const; virtual bool canSwim(const MWWorld::ConstPtr& ptr) const; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index fcf2c4b38..313e9a152 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -105,7 +105,7 @@ namespace } void addObject(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, - MWRender::RenderingManager& rendering, std::set& pagedRefs, bool onlyPhysics) + MWRender::RenderingManager& rendering, std::set& pagedRefs) { if (ptr.getRefData().getBaseNode() || physics.getActor(ptr)) { @@ -113,29 +113,25 @@ namespace return; } + bool useAnim = ptr.getClass().useAnim(); std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS()); - const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); - if (!onlyPhysics) - { - bool useAnim = ptr.getClass().useAnim(); - const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); - if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) - ptr.getClass().insertObjectRendering(ptr, model, rendering); + const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); + if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) + ptr.getClass().insertObjectRendering(ptr, model, rendering); - setNodeRotation(ptr, rendering, rotation); + const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); + setNodeRotation(ptr, rendering, rotation); + ptr.getClass().insertObject (ptr, model, rotation, physics); - if (useAnim) - MWBase::Environment::get().getMechanicsManager()->add(ptr); + if (useAnim) + MWBase::Environment::get().getMechanicsManager()->add(ptr); - if (ptr.getClass().isActor()) - rendering.addWaterRippleEmitter(ptr); + if (ptr.getClass().isActor()) + rendering.addWaterRippleEmitter(ptr); - // Restore effect particles - MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); - } - if (!physics.getObject(ptr)) - ptr.getClass().insertObject (ptr, model, rotation, physics); + // Restore effect particles + MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); } void addObject(const MWWorld::Ptr& ptr, const MWPhysics::PhysicsSystem& physics, DetourNavigator::Navigator& navigator) @@ -206,12 +202,11 @@ namespace { MWWorld::CellStore& mCell; Loading::Listener& mLoadingListener; - bool mOnlyStatics; bool mTest; std::vector mToInsert; - InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool onlyStatics, bool test); + InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool test); bool operator() (const MWWorld::Ptr& ptr); @@ -219,8 +214,8 @@ namespace void insert(AddObject&& addObject); }; - InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool onlyStatics, bool test) - : mCell (cell), mLoadingListener (loadingListener), mOnlyStatics(onlyStatics), mTest(test) + InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool test) + : mCell (cell), mLoadingListener (loadingListener), mTest(test) {} bool InsertVisitor::operator() (const MWWorld::Ptr& ptr) @@ -236,7 +231,7 @@ namespace { for (MWWorld::Ptr& ptr : mToInsert) { - if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled() && ((mOnlyStatics && ptr.getClass().isStatic()) || !mOnlyStatics)) + if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled()) { try { @@ -269,16 +264,6 @@ namespace return std::abs(cellPosition.first) + std::abs(cellPosition.second); } - bool isCellInCollection(int x, int y, MWWorld::Scene::CellStoreCollection& collection) - { - for (auto *cell : collection) - { - assert(cell->getCell()->isExterior()); - if (x == cell->getCell()->getGridX() && y == cell->getCell()->getGridY()) - return true; - } - return false; - } } @@ -330,41 +315,15 @@ namespace MWWorld mRendering.update (duration, paused); } - void Scene::unloadInactiveCell (CellStore* cell, bool test) - { - assert(mActiveCells.find(cell) == mActiveCells.end()); - assert(mInactiveCells.find(cell) != mInactiveCells.end()); - if (!test) - Log(Debug::Info) << "Unloading cell " << cell->getCell()->getDescription(); - - ListObjectsVisitor visitor; - - cell->forEach(visitor); - for (const auto& ptr : visitor.mObjects) - mPhysics->remove(ptr); - - if (cell->getCell()->isExterior()) - { - const auto cellX = cell->getCell()->getGridX(); - const auto cellY = cell->getCell()->getGridY(); - mPhysics->removeHeightField(cellX, cellY); - } - - mInactiveCells.erase(cell); - } - - void Scene::deactivateCell(CellStore* cell, bool test) + void Scene::unloadCell (CellStoreCollection::iterator iter, bool test) { - assert(mInactiveCells.find(cell) != mInactiveCells.end()); - if (mActiveCells.find(cell) == mActiveCells.end()) - return; if (!test) - Log(Debug::Info) << "Deactivate cell " << cell->getCell()->getDescription(); + Log(Debug::Info) << "Unloading cell " << (*iter)->getCell()->getDescription(); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); ListAndResetObjectsVisitor visitor; - cell->forEach(visitor); + (*iter)->forEach(visitor); const auto world = MWBase::Environment::get().getWorld(); for (const auto& ptr : visitor.mObjects) { @@ -375,157 +334,140 @@ namespace MWWorld navigator->removeAgent(world->getPathfindingHalfExtents(ptr)); mRendering.removeActorPath(ptr); } - mPhysics->remove(ptr, ptr.getClass().isStatic()); + mPhysics->remove(ptr); } - const auto cellX = cell->getCell()->getGridX(); - const auto cellY = cell->getCell()->getGridY(); + const auto cellX = (*iter)->getCell()->getGridX(); + const auto cellY = (*iter)->getCell()->getGridY(); - if (cell->getCell()->isExterior()) + if ((*iter)->getCell()->isExterior()) { if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) navigator->removeObject(DetourNavigator::ObjectId(heightField)); + mPhysics->removeHeightField(cellX, cellY); } - if (cell->getCell()->hasWater()) + if ((*iter)->getCell()->hasWater()) navigator->removeWater(osg::Vec2i(cellX, cellY)); - if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) + if (const auto pathgrid = world->getStore().get().search(*(*iter)->getCell())) navigator->removePathgrid(*pathgrid); const auto player = world->getPlayerPtr(); navigator->update(player.getRefData().getPosition().asVec3()); - MWBase::Environment::get().getMechanicsManager()->drop (cell); + MWBase::Environment::get().getMechanicsManager()->drop (*iter); - mRendering.removeCell(cell); - MWBase::Environment::get().getWindowManager()->removeCell(cell); + mRendering.removeCell(*iter); + MWBase::Environment::get().getWindowManager()->removeCell(*iter); - MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (cell); + MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (*iter); - MWBase::Environment::get().getSoundManager()->stopSound (cell); - mActiveCells.erase(cell); + MWBase::Environment::get().getSoundManager()->stopSound (*iter); + mActiveCells.erase(*iter); } - void Scene::activateCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test) + void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test) { - assert(mActiveCells.find(cell) == mActiveCells.end()); - assert(mInactiveCells.find(cell) != mInactiveCells.end()); - mActiveCells.insert(cell); - - if (test) - Log(Debug::Info) << "Testing cell " << cell->getCell()->getDescription(); - else - Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); - - const auto world = MWBase::Environment::get().getWorld(); - const auto navigator = world->getNavigator(); - - const int cellX = cell->getCell()->getGridX(); - const int cellY = cell->getCell()->getGridY(); + std::pair result = mActiveCells.insert(cell); - if (!test && cell->getCell()->isExterior()) + if(result.second) { - if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) - navigator->addObject(DetourNavigator::ObjectId(heightField), *heightField->getShape(), - heightField->getCollisionObject()->getWorldTransform()); - } - - if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) - navigator->addPathgrid(*cell->getCell(), *pathgrid); + if (test) + Log(Debug::Info) << "Testing cell " << cell->getCell()->getDescription(); + else + Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); - // register local scripts - // do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice - MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); + float verts = ESM::Land::LAND_SIZE; + float worldsize = ESM::Land::REAL_SIZE; - if (respawn) - cell->respawn(); + const auto world = MWBase::Environment::get().getWorld(); + const auto navigator = world->getNavigator(); - insertCell (*cell, loadingListener, false, test); + const int cellX = cell->getCell()->getGridX(); + const int cellY = cell->getCell()->getGridY(); - mRendering.addCell(cell); - if (!test) - { - MWBase::Environment::get().getWindowManager()->addCell(cell); - bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior(); - float waterLevel = cell->getWaterLevel(); - mRendering.setWaterEnabled(waterEnabled); - if (waterEnabled) + // Load terrain physics first... + if (!test && cell->getCell()->isExterior()) { - mPhysics->enableWater(waterLevel); - mRendering.setWaterHeight(waterLevel); - - if (cell->getCell()->isExterior()) + osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; + if (data) { - if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) - navigator->addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, - cell->getWaterLevel(), heightField->getCollisionObject()->getWorldTransform()); + mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get()); } else { - navigator->addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), - cell->getWaterLevel(), btTransform::getIdentity()); + static std::vector defaultHeight; + defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); + mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); } + + if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) + navigator->addObject(DetourNavigator::ObjectId(heightField), *heightField->getShape(), + heightField->getCollisionObject()->getWorldTransform()); } - else - mPhysics->disableWater(); - const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - navigator->update(player.getRefData().getPosition().asVec3()); + if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) + navigator->addPathgrid(*cell->getCell(), *pathgrid); - if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) - mRendering.configureAmbient(cell->getCell()); - } + // register local scripts + // do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice + MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); - mPreloader->notifyLoaded(cell); - } + if (respawn) + cell->respawn(); - void Scene::loadInactiveCell (CellStore *cell, Loading::Listener* loadingListener, bool test) - { - assert(mActiveCells.find(cell) == mActiveCells.end()); - assert(mInactiveCells.find(cell) == mInactiveCells.end()); - mInactiveCells.insert(cell); + // ... then references. This is important for adjustPosition to work correctly. + insertCell (*cell, loadingListener, test); - if (test) - Log(Debug::Info) << "Testing inactive cell " << cell->getCell()->getDescription(); - else - Log(Debug::Info) << "Loading inactive cell " << cell->getCell()->getDescription(); + mRendering.addCell(cell); + if (!test) + { + MWBase::Environment::get().getWindowManager()->addCell(cell); + bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior(); + float waterLevel = cell->getWaterLevel(); + mRendering.setWaterEnabled(waterEnabled); + if (waterEnabled) + { + mPhysics->enableWater(waterLevel); + mRendering.setWaterHeight(waterLevel); - if (!test && cell->getCell()->isExterior()) - { - float verts = ESM::Land::LAND_SIZE; - float worldsize = ESM::Land::REAL_SIZE; + if (cell->getCell()->isExterior()) + { + if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) + navigator->addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, + cell->getWaterLevel(), heightField->getCollisionObject()->getWorldTransform()); + } + else + { + navigator->addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), + cell->getWaterLevel(), btTransform::getIdentity()); + } + } + else + mPhysics->disableWater(); - const int cellX = cell->getCell()->getGridX(); - const int cellY = cell->getCell()->getGridY(); + const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + navigator->update(player.getRefData().getPosition().asVec3()); - osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); - const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; - if (data) - { - mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get()); - } - else - { - static std::vector defaultHeight; - defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); - mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); + if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) + { + + mRendering.configureAmbient(cell->getCell()); + } } } - insertCell (*cell, loadingListener, true, test); + mPreloader->notifyLoaded(cell); } void Scene::clear() { - for (auto iter = mInactiveCells.begin(); iter!=mInactiveCells.end(); ) - { - auto* cell = *iter++; - deactivateCell(cell); - unloadInactiveCell (cell); - } + CellStoreCollection::iterator active = mActiveCells.begin(); + while (active!=mActiveCells.end()) + unloadCell (active++); assert(mActiveCells.empty()); - assert(mInactiveCells.empty()); mCurrentCell = nullptr; mPreloader->clear(); @@ -568,24 +510,20 @@ namespace MWWorld void Scene::changeCellGrid (const osg::Vec3f &pos, int playerCellX, int playerCellY, bool changeEvent) { - for (auto iter = mInactiveCells.begin(); iter != mInactiveCells.end(); ) + CellStoreCollection::iterator active = mActiveCells.begin(); + while (active!=mActiveCells.end()) { - auto* cell = *iter++; - if (cell->getCell()->isExterior()) - { - const auto dx = std::abs(playerCellX - cell->getCell()->getGridX()); - const auto dy = std::abs(playerCellY - cell->getCell()->getGridY()); - if (dx > mHalfGridSize || dy > mHalfGridSize) - deactivateCell(cell); - - if (dx > mHalfGridSize+1 || dy > mHalfGridSize+1) - unloadInactiveCell(cell); - } - else + if ((*active)->getCell()->isExterior()) { - deactivateCell(cell); - unloadInactiveCell(cell); + if (std::abs (playerCellX-(*active)->getCell()->getGridX())<=mHalfGridSize && + std::abs (playerCellY-(*active)->getCell()->getGridY())<=mHalfGridSize) + { + // keep cells within the new grid + ++active; + continue; + } } + unloadCell (active++); } mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY); @@ -597,24 +535,32 @@ namespace MWWorld mRendering.getPagedRefnums(newGrid, mPagedRefs); std::size_t refsToLoad = 0; - const auto cellsToLoad = [&playerCellX,&playerCellY,&refsToLoad](CellStoreCollection& collection, int range) -> std::vector> + std::vector> cellsPositionsToLoad; + // get the number of refs to load + for (int x = playerCellX - mHalfGridSize; x <= playerCellX + mHalfGridSize; ++x) { - std::vector> cellsPositionsToLoad; - for (int x = playerCellX - range; x <= playerCellX + range; ++x) + for (int y = playerCellY - mHalfGridSize; y <= playerCellY + mHalfGridSize; ++y) { - for (int y = playerCellY - range; y <= playerCellY + range; ++y) + CellStoreCollection::iterator iter = mActiveCells.begin(); + + while (iter!=mActiveCells.end()) { - if (!isCellInCollection(x, y, collection)) - { - refsToLoad += MWBase::Environment::get().getWorld()->getExterior(x, y)->count(); - cellsPositionsToLoad.emplace_back(x, y); - } + assert ((*iter)->getCell()->isExterior()); + + if (x==(*iter)->getCell()->getGridX() && + y==(*iter)->getCell()->getGridY()) + break; + + ++iter; + } + + if (iter==mActiveCells.end()) + { + refsToLoad += MWBase::Environment::get().getWorld()->getExterior(x, y)->count(); + cellsPositionsToLoad.emplace_back(x, y); } } - return cellsPositionsToLoad; - }; - auto cellsPositionsToLoad = cellsToLoad(mActiveCells,mHalfGridSize); - auto cellsPositionsToLoadInactive = cellsToLoad(mInactiveCells,mHalfGridSize+1); + } Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); @@ -638,26 +584,30 @@ namespace MWWorld return getCellPositionPriority(lhs) < getCellPositionPriority(rhs); }); - std::sort(cellsPositionsToLoadInactive.begin(), cellsPositionsToLoadInactive.end(), - [&] (const std::pair& lhs, const std::pair& rhs) { - return getCellPositionPriority(lhs) < getCellPositionPriority(rhs); - }); - // Load cells - for (const auto& [x,y] : cellsPositionsToLoadInactive) + for (const auto& cellPosition : cellsPositionsToLoad) { - if (!isCellInCollection(x, y, mInactiveCells)) + const auto x = cellPosition.first; + const auto y = cellPosition.second; + + CellStoreCollection::iterator iter = mActiveCells.begin(); + + while (iter != mActiveCells.end()) { - CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); - loadInactiveCell (cell, loadingListener); + assert ((*iter)->getCell()->isExterior()); + + if (x == (*iter)->getCell()->getGridX() && + y == (*iter)->getCell()->getGridY()) + break; + + ++iter; } - } - for (const auto& [x,y] : cellsPositionsToLoad) - { - if (!isCellInCollection(x, y, mActiveCells)) + + if (iter == mActiveCells.end()) { CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); - activateCell (cell, loadingListener, changeEvent); + + loadCell (cell, loadingListener, changeEvent); } } @@ -690,8 +640,7 @@ namespace MWWorld CellStoreCollection::iterator iter = mActiveCells.begin(); CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(it->mData.mX, it->mData.mY); - loadInactiveCell (cell, loadingListener, true); - activateCell (cell, loadingListener, false, true); + loadCell (cell, loadingListener, false, true); iter = mActiveCells.begin(); while (iter != mActiveCells.end()) @@ -699,8 +648,7 @@ namespace MWWorld if (it->isExterior() && it->mData.mX == (*iter)->getCell()->getGridX() && it->mData.mY == (*iter)->getCell()->getGridY()) { - deactivateCell(*iter, true); - unloadInactiveCell (*iter, true); + unloadCell(iter, true); break; } @@ -738,8 +686,7 @@ namespace MWWorld loadingListener->setLabel("Testing interior cells ("+std::to_string(i)+"/"+std::to_string(cells.getIntSize())+")..."); CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(it->mName); - loadInactiveCell (cell, loadingListener, true); - activateCell (cell, loadingListener, false, true); + loadCell (cell, loadingListener, false, true); CellStoreCollection::iterator iter = mActiveCells.begin(); while (iter != mActiveCells.end()) @@ -748,8 +695,7 @@ namespace MWWorld if (it->mName == (*iter)->getCell()->mName) { - deactivateCell(*iter, true); - unloadInactiveCell (*iter, true); + unloadCell(iter, true); break; } @@ -872,21 +818,15 @@ namespace MWWorld Log(Debug::Info) << "Changing to interior"; // unload - for (auto iter = mInactiveCells.begin(); iter!=mInactiveCells.end(); ) - { - auto* cell = *iter++; - deactivateCell(cell); - unloadInactiveCell(cell); - } - assert(mActiveCells.empty()); - assert(mInactiveCells.empty()); + CellStoreCollection::iterator active = mActiveCells.begin(); + while (active!=mActiveCells.end()) + unloadCell (active++); loadingListener->setProgressRange(cell->count()); // Load cell. mPagedRefs.clear(); - loadInactiveCell (cell, loadingListener); - activateCell (cell, loadingListener, changeEvent); + loadCell (cell, loadingListener, changeEvent); changePlayerCell(cell, position, adjustPlayerPos); @@ -934,26 +874,23 @@ namespace MWWorld mCellChanged = false; } - void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyStatics, bool test) + void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test) { - InsertVisitor insertVisitor (cell, *loadingListener, onlyStatics, test); + InsertVisitor insertVisitor (cell, *loadingListener, test); cell.forEach (insertVisitor); - insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs, onlyStatics); }); - if (!onlyStatics) - { - insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs); }); + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); - // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order - PositionVisitor posVisitor; - cell.forEach (posVisitor); - } + // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order + PositionVisitor posVisitor; + cell.forEach (posVisitor); } void Scene::addObjectToScene (const Ptr& ptr) { try { - addObject(ptr, *mPhysics, mRendering, mPagedRefs, false); + addObject(ptr, *mPhysics, mRendering, mPagedRefs); addObject(ptr, *mPhysics, mNavigator); MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 33c7b78d0..a70d3ccdd 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -65,13 +65,13 @@ namespace MWWorld class Scene { public: - using CellStoreCollection = std::set; + + typedef std::set CellStoreCollection; private: CellStore* mCurrentCell; // the cell the player is in CellStoreCollection mActiveCells; - CellStoreCollection mInactiveCells; bool mCellChanged; MWPhysics::PhysicsSystem *mPhysics; MWRender::RenderingManager& mRendering; @@ -92,7 +92,7 @@ namespace MWWorld std::set mPagedRefs; - void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyStatics, bool test = false); + void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test = false); osg::Vec2i mCurrentGridCenter; // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center @@ -108,11 +108,6 @@ namespace MWWorld osg::Vec4i gridCenterToBounds(const osg::Vec2i ¢erCell) const; osg::Vec2i getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i *currentGridCenter = nullptr) const; - void unloadInactiveCell (CellStore* cell, bool test = false); - void deactivateCell (CellStore* cell, bool test = false); - void activateCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test = false); - void loadInactiveCell (CellStore *cell, Loading::Listener* loadingListener, bool test = false); - public: Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics, @@ -124,6 +119,10 @@ namespace MWWorld void preloadTerrain(const osg::Vec3f& pos, bool sync=false); void reloadTerrain(); + void unloadCell (CellStoreCollection::iterator iter, bool test = false); + + void loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test = false); + void playerMoved (const osg::Vec3f& pos); void changePlayerCell (CellStore* newCell, const ESM::Position& position, bool adjustPlayerPos); From 7b727e4d70df35560fe08da8a0ba839e8d6790de Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 29 Jan 2021 16:51:13 +0400 Subject: [PATCH 125/171] Revert "Remove physics dependency on basenode" This reverts commit 165c7314928dc281a364fa1a0143c45fd6d2adfd. --- apps/openmw/mwclass/activator.cpp | 4 +-- apps/openmw/mwclass/activator.hpp | 2 +- apps/openmw/mwclass/actor.cpp | 6 ++++- apps/openmw/mwclass/actor.hpp | 10 +++---- apps/openmw/mwclass/apparatus.cpp | 5 ++++ apps/openmw/mwclass/apparatus.hpp | 2 ++ apps/openmw/mwclass/armor.cpp | 5 ++++ apps/openmw/mwclass/armor.hpp | 2 ++ apps/openmw/mwclass/bodypart.cpp | 4 +++ apps/openmw/mwclass/bodypart.hpp | 2 ++ apps/openmw/mwclass/book.cpp | 5 ++++ apps/openmw/mwclass/book.hpp | 2 ++ apps/openmw/mwclass/clothing.cpp | 5 ++++ apps/openmw/mwclass/clothing.hpp | 2 ++ apps/openmw/mwclass/container.cpp | 4 +-- apps/openmw/mwclass/container.hpp | 2 +- apps/openmw/mwclass/door.cpp | 4 +-- apps/openmw/mwclass/door.hpp | 2 +- apps/openmw/mwclass/ingredient.cpp | 5 ++++ apps/openmw/mwclass/ingredient.hpp | 2 ++ apps/openmw/mwclass/light.cpp | 4 +-- apps/openmw/mwclass/light.hpp | 2 +- apps/openmw/mwclass/lockpick.cpp | 5 ++++ apps/openmw/mwclass/lockpick.hpp | 2 ++ apps/openmw/mwclass/misc.cpp | 5 ++++ apps/openmw/mwclass/misc.hpp | 2 ++ apps/openmw/mwclass/potion.cpp | 5 ++++ apps/openmw/mwclass/potion.hpp | 2 ++ apps/openmw/mwclass/probe.cpp | 5 ++++ apps/openmw/mwclass/probe.hpp | 2 ++ apps/openmw/mwclass/repair.cpp | 5 ++++ apps/openmw/mwclass/repair.hpp | 2 ++ apps/openmw/mwclass/static.cpp | 4 +-- apps/openmw/mwclass/static.hpp | 2 +- apps/openmw/mwclass/weapon.cpp | 5 ++++ apps/openmw/mwclass/weapon.hpp | 2 ++ apps/openmw/mwphysics/actor.cpp | 6 ++--- apps/openmw/mwphysics/actor.hpp | 2 +- apps/openmw/mwphysics/object.cpp | 11 +++----- apps/openmw/mwphysics/object.hpp | 4 +-- apps/openmw/mwphysics/physicssystem.cpp | 10 +++---- apps/openmw/mwphysics/physicssystem.hpp | 4 +-- apps/openmw/mwworld/class.cpp | 6 ++++- apps/openmw/mwworld/class.hpp | 13 ++++----- apps/openmw/mwworld/scene.cpp | 36 ++++++++++++------------- apps/openmw/mwworld/worldimp.cpp | 2 +- 46 files changed, 155 insertions(+), 68 deletions(-) diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 716e548e1..c54b1c369 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -38,10 +38,10 @@ namespace MWClass } } - void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, rotation); + physics.addObject(ptr, model); } std::string Activator::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index c7b65ef67..10ace6f74 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -17,7 +17,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp index 1789f1b19..33aeb26bb 100644 --- a/apps/openmw/mwclass/actor.cpp +++ b/apps/openmw/mwclass/actor.cpp @@ -17,12 +17,16 @@ namespace MWClass { + Actor::Actor() {} + + Actor::~Actor() {} + void Actor::adjustPosition(const MWWorld::Ptr& ptr, bool force) const { MWBase::Environment::get().getWorld()->adjustPosition(ptr, force); } - void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if (!model.empty()) { diff --git a/apps/openmw/mwclass/actor.hpp b/apps/openmw/mwclass/actor.hpp index 98998b4eb..3d509b276 100644 --- a/apps/openmw/mwclass/actor.hpp +++ b/apps/openmw/mwclass/actor.hpp @@ -15,16 +15,16 @@ namespace MWClass { protected: - Actor() = default; + Actor(); public: - ~Actor() override = default; + virtual ~Actor(); void adjustPosition(const MWWorld::Ptr& ptr, bool force) const override; ///< Adjust position to stand on ground. Must be called post model load /// @param force do this even if the ptr is flying - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; bool useAnim() const override; @@ -46,8 +46,8 @@ namespace MWClass float getCurrentSpeed(const MWWorld::Ptr& ptr) const override; // not implemented - Actor(const Actor&) = delete; - Actor& operator= (const Actor&) = delete; + Actor(const Actor&); + Actor& operator= (const Actor&); }; } diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index e09e4804c..518695fab 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -26,6 +26,11 @@ namespace MWClass } } + void Apparatus::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Apparatus::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/apparatus.hpp b/apps/openmw/mwclass/apparatus.hpp index 828abef25..8087c57ba 100644 --- a/apps/openmw/mwclass/apparatus.hpp +++ b/apps/openmw/mwclass/apparatus.hpp @@ -17,6 +17,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 817adbc1f..3f9bfb859 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -34,6 +34,11 @@ namespace MWClass } } + void Armor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Armor::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index f64f138a2..4f04e0824 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -16,6 +16,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/bodypart.cpp b/apps/openmw/mwclass/bodypart.cpp index 7fe798e27..0315d3ddb 100644 --- a/apps/openmw/mwclass/bodypart.cpp +++ b/apps/openmw/mwclass/bodypart.cpp @@ -22,6 +22,10 @@ namespace MWClass } } + void BodyPart::insertObject(const MWWorld::Ptr &ptr, const std::string &model, MWPhysics::PhysicsSystem &physics) const + { + } + std::string BodyPart::getName(const MWWorld::ConstPtr &ptr) const { return std::string(); diff --git a/apps/openmw/mwclass/bodypart.hpp b/apps/openmw/mwclass/bodypart.hpp index 0e372b884..13d914138 100644 --- a/apps/openmw/mwclass/bodypart.hpp +++ b/apps/openmw/mwclass/bodypart.hpp @@ -15,6 +15,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 51b9e39d7..4ea71e3ac 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -31,6 +31,11 @@ namespace MWClass } } + void Book::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Book::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index f3d34c516..c58e68ad8 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 400cd97e4..6d7960aac 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -29,6 +29,11 @@ namespace MWClass } } + void Clothing::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Clothing::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index 3d5c162aa..a87e0cbe0 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 3023466f0..28305c394 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -111,10 +111,10 @@ namespace MWClass } } - void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, rotation); + physics.addObject(ptr, model); } std::string Container::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 74d9ac0da..2dc0c06ca 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -44,7 +44,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 983953c20..ba51d9c2b 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -62,10 +62,10 @@ namespace MWClass } } - void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_Door); + physics.addObject(ptr, model, MWPhysics::CollisionType_Door); // Resume the door's opening/closing animation if it wasn't finished if (ptr.getRefData().getCustomData()) diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index 6d40a840b..6c2fa26b8 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -18,7 +18,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; bool isDoor() const override; diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 20f9576df..a007ad115 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -28,6 +28,11 @@ namespace MWClass } } + void Ingredient::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Ingredient::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/ingredient.hpp b/apps/openmw/mwclass/ingredient.hpp index 2aa831f86..5219cf39c 100644 --- a/apps/openmw/mwclass/ingredient.hpp +++ b/apps/openmw/mwclass/ingredient.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index d4d196bc4..3bdf10f47 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -33,7 +33,7 @@ namespace MWClass renderingInterface.getObjects().insertModel(ptr, model, true, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)); } - void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { MWWorld::LiveCellRef *ref = ptr.get(); @@ -41,7 +41,7 @@ namespace MWClass // TODO: add option somewhere to enable collision for placeable objects if (!model.empty() && (ref->mBase->mData.mFlags & ESM::Light::Carry) == 0) - physics.addObject(ptr, model, rotation); + physics.addObject(ptr, model); if (!ref->mBase->mSound.empty() && !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)) MWBase::Environment::get().getSoundManager()->playSound3D(ptr, ref->mBase->mSound, 1.0, 1.0, diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index 1b1794d4a..e37dddc25 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -14,7 +14,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; bool useAnim() const override; diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 985b08771..9b8abc8f2 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -28,6 +28,11 @@ namespace MWClass } } + void Lockpick::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Lockpick::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index d4b265e39..fabae3343 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index facab9d51..8d3cda6fe 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -37,6 +37,11 @@ namespace MWClass } } + void Miscellaneous::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Miscellaneous::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index 18788c7ed..9bff85ca5 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 56d9dff27..4af97e634 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -30,6 +30,11 @@ namespace MWClass } } + void Potion::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Potion::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp index 75b962164..75d923f0b 100644 --- a/apps/openmw/mwclass/potion.hpp +++ b/apps/openmw/mwclass/potion.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 51273337a..dba4e8c06 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -28,6 +28,11 @@ namespace MWClass } } + void Probe::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Probe::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index ef9273a37..a0a41dcfb 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index f1b88e422..8907c8212 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -25,6 +25,11 @@ namespace MWClass } } + void Repair::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Repair::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/repair.hpp b/apps/openmw/mwclass/repair.hpp index c403449e1..b9791e9cf 100644 --- a/apps/openmw/mwclass/repair.hpp +++ b/apps/openmw/mwclass/repair.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 108c4eaa2..5551b3d73 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -23,10 +23,10 @@ namespace MWClass } } - void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, rotation); + physics.addObject(ptr, model); } std::string Static::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index f856e9fd9..6bc783dad 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -14,7 +14,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 6246c8fb0..0d6a27cf6 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -34,6 +34,11 @@ namespace MWClass } } + void Weapon::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Weapon::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index db17e6b70..f1824b7d1 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -15,6 +15,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 06600fd6a..3b52ee934 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -67,7 +67,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic updateScale(); if(!mRotationallyInvariant) - setRotation(mPtr.getRefData().getBaseNode()->getAttitude()); + updateRotation(); updatePosition(); addCollisionMask(getCollisionMask()); @@ -197,10 +197,10 @@ osg::Vec3f Actor::getPreviousPosition() const return mPreviousPosition; } -void Actor::setRotation(osg::Quat quat) +void Actor::updateRotation () { std::scoped_lock lock(mPositionMutex); - mRotation = quat; + mRotation = mPtr.getRefData().getBaseNode()->getAttitude(); } bool Actor::isRotationallyInvariant() const diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 4039f4481..9d129f2ba 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -49,7 +49,7 @@ namespace MWPhysics void enableCollisionBody(bool collision); void updateScale(); - void setRotation(osg::Quat quat); + void updateRotation(); /** * Return true if the collision shape looks the same no matter how its Z rotated. diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 0a7b9540c..e3615989d 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -14,7 +14,7 @@ namespace MWPhysics { - Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, osg::Quat rotation, int collisionType, PhysicsTaskScheduler* scheduler) + Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler) : mShapeInstance(shapeInstance) , mSolid(true) , mTaskScheduler(scheduler) @@ -27,7 +27,7 @@ namespace MWPhysics mCollisionObject->setUserPointer(this); setScale(ptr.getCellRef().getScale()); - setRotation(rotation); + setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); setOrigin(Misc::Convert::toBullet(ptr.getRefData().getPosition().asVec3())); commitPositionChange(); @@ -51,10 +51,10 @@ namespace MWPhysics mScaleUpdatePending = true; } - void Object::setRotation(osg::Quat quat) + void Object::setRotation(const btQuaternion& quat) { std::unique_lock lock(mPositionMutex); - mLocalTransform.setRotation(Misc::Convert::toBullet(quat)); + mLocalTransform.setRotation(quat); mTransformUpdatePending = true; } @@ -116,9 +116,6 @@ namespace MWPhysics if (mShapeInstance->mAnimatedShapes.empty()) return false; - if (mPtr.getRefData().getBaseNode() == nullptr) - return true; - assert (mShapeInstance->getCollisionShape()->isCompound()); btCompoundShape* compound = static_cast(mShapeInstance->getCollisionShape()); diff --git a/apps/openmw/mwphysics/object.hpp b/apps/openmw/mwphysics/object.hpp index c2273831e..cae877180 100644 --- a/apps/openmw/mwphysics/object.hpp +++ b/apps/openmw/mwphysics/object.hpp @@ -26,12 +26,12 @@ namespace MWPhysics class Object final : public PtrHolder { public: - Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, osg::Quat rotation, int collisionType, PhysicsTaskScheduler* scheduler); + Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler); ~Object() override; const Resource::BulletShapeInstance* getShapeInstance() const; void setScale(float scale); - void setRotation(osg::Quat quat); + void setRotation(const btQuaternion& quat); void setOrigin(const btVector3& vec); void commitPositionChange(); btCollisionObject* getCollisionObject(); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 68cec48bc..ed0e0c915 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -456,13 +456,13 @@ namespace MWPhysics return heightField->second.get(); } - void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType) + void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType) { osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); if (!shapeInstance || !shapeInstance->getCollisionShape()) return; - auto obj = std::make_shared(ptr, shapeInstance, rotation, collisionType, mTaskScheduler.get()); + auto obj = std::make_shared(ptr, shapeInstance, collisionType, mTaskScheduler.get()); mObjects.emplace(ptr, obj); if (obj->isAnimated()) @@ -621,12 +621,12 @@ namespace MWPhysics mTaskScheduler->updateSingleAabb(foundProjectile->second); } - void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr, osg::Quat rotate) + void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr) { ObjectMap::iterator found = mObjects.find(ptr); if (found != mObjects.end()) { - found->second->setRotation(rotate); + found->second->setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); mTaskScheduler->updateSingleAabb(found->second); return; } @@ -635,7 +635,7 @@ namespace MWPhysics { if (!foundActor->second->isRotationallyInvariant()) { - foundActor->second->setRotation(rotate); + foundActor->second->updateRotation(); mTaskScheduler->updateSingleAabb(foundActor->second); } return; diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index c61b368f8..6f901067a 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -121,7 +121,7 @@ namespace MWPhysics void setWaterHeight(float height); void disableWater(); - void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType = CollisionType_World); + void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canTraverseWater); @@ -141,7 +141,7 @@ namespace MWPhysics void remove (const MWWorld::Ptr& ptr); void updateScale (const MWWorld::Ptr& ptr); - void updateRotation (const MWWorld::Ptr& ptr, osg::Quat rotate); + void updateRotation (const MWWorld::Ptr& ptr); void updatePosition (const MWWorld::Ptr& ptr); void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject); diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index becf912ea..950c8a6d4 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -25,12 +25,16 @@ namespace MWWorld { std::map > Class::sClasses; + Class::Class() {} + + Class::~Class() {} + void Class::insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const { } - void Class::insertObject(const Ptr& ptr, const std::string& mesh, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Class::insertObject(const Ptr& ptr, const std::string& mesh, MWPhysics::PhysicsSystem& physics) const { } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 39fb6fe4c..1b3d4029e 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -6,7 +6,6 @@ #include #include -#include #include #include "ptr.hpp" @@ -58,9 +57,13 @@ namespace MWWorld std::string mTypeName; + // not implemented + Class (const Class&); + Class& operator= (const Class&); + protected: - Class() = default; + Class(); std::shared_ptr defaultItemActivate(const Ptr &ptr, const Ptr &actor) const; ///< Generate default action for activating inventory items @@ -69,16 +72,14 @@ namespace MWWorld public: - virtual ~Class() = default; - Class (const Class&) = delete; - Class& operator= (const Class&) = delete; + virtual ~Class(); const std::string& getTypeName() const { return mTypeName; } virtual void insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const; - virtual void insertObject(const Ptr& ptr, const std::string& mesh, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const; + virtual void insertObject(const Ptr& ptr, const std::string& mesh, MWPhysics::PhysicsSystem& physics) const; ///< Add reference into a cell for rendering (default implementation: don't render anything). virtual std::string getName (const ConstPtr& ptr) const = 0; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 313e9a152..427ee3aa8 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -75,20 +75,18 @@ namespace * osg::Quat(xr, osg::Vec3(-1, 0, 0)); } - osg::Quat makeNodeRotation(const MWWorld::Ptr& ptr, RotationOrder order) + void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, RotationOrder order) { - const auto pos = ptr.getRefData().getPosition(); - - const auto rot = ptr.getClass().isActor() ? makeActorOsgQuat(pos) - : (order == RotationOrder::inverse ? makeInversedOrderObjectOsgQuat(pos) : makeObjectOsgQuat(pos)); - - return rot; - } + if (!ptr.getRefData().getBaseNode()) + return; - void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, osg::Quat rotation) - { - if (ptr.getRefData().getBaseNode()) - rendering.rotateObject(ptr, rotation); + rendering.rotateObject(ptr, + ptr.getClass().isActor() + ? makeActorOsgQuat(ptr.getRefData().getPosition()) + : (order == RotationOrder::inverse + ? makeInversedOrderObjectOsgQuat(ptr.getRefData().getPosition()) + : makeObjectOsgQuat(ptr.getRefData().getPosition())) + ); } std::string getModel(const MWWorld::Ptr &ptr, const VFS::Manager *vfs) @@ -119,10 +117,11 @@ namespace const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) ptr.getClass().insertObjectRendering(ptr, model, rendering); + else + ptr.getRefData().setBaseNode(new SceneUtil::PositionAttitudeTransform); // FIXME remove this when physics code is fixed not to depend on basenode + setNodeRotation(ptr, rendering, RotationOrder::direct); - const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); - setNodeRotation(ptr, rendering, rotation); - ptr.getClass().insertObject (ptr, model, rotation, physics); + ptr.getClass().insertObject (ptr, model, physics); if (useAnim) MWBase::Environment::get().getMechanicsManager()->add(ptr); @@ -277,7 +276,7 @@ namespace MWWorld { if (!ptr.getRefData().getBaseNode()) return; ptr.getClass().insertObjectRendering(ptr, getModel(ptr, mRendering.getResourceSystem()->getVFS()), mRendering); - setNodeRotation(ptr, mRendering, makeNodeRotation(ptr, RotationOrder::direct)); + setNodeRotation(ptr, mRendering, RotationOrder::direct); reloadTerrain(); } } @@ -293,9 +292,8 @@ namespace MWWorld void Scene::updateObjectRotation(const Ptr &ptr, RotationOrder order) { - const auto rot = makeNodeRotation(ptr, order); - setNodeRotation(ptr, mRendering, rot); - mPhysics->updateRotation(ptr, rot); + setNodeRotation(ptr, mRendering, order); + mPhysics->updateRotation(ptr); } void Scene::updateObjectScale(const Ptr &ptr) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ef00315cb..442672d2b 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1420,7 +1420,7 @@ namespace MWWorld mWorldScene->removeFromPagedRefs(ptr); mRendering->rotateObject(ptr, rotate); - mPhysics->updateRotation(ptr, rotate); + mPhysics->updateRotation(ptr); if (const auto object = mPhysics->getObject(ptr)) updateNavigatorObject(object); From 642ca02e3501f0d6691642b6d9476af0745d424e Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 29 Jan 2021 21:07:24 +0100 Subject: [PATCH 126/171] Shorten almost straight paths only if smooth movement is enabled; reduce angle limit for the shortening. --- apps/openmw/mwmechanics/aipackage.cpp | 4 ++-- apps/openmw/mwmechanics/pathfinding.cpp | 21 ++++++++++++--------- apps/openmw/mwmechanics/pathfinding.hpp | 2 +- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index ce5673909..da1766f4d 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -150,7 +150,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& + 1.2 * std::max(halfExtents.x(), halfExtents.y()); const float pointTolerance = std::max(MIN_TOLERANCE, actorTolerance); - mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE); + static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game"); + mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE, /*shortenIfAlmostStraight=*/smoothMovement); if (isDestReached || mPathFinder.checkPathCompleted()) // if path is finished { @@ -180,7 +181,6 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& const auto destination = mPathFinder.getPath().empty() ? dest : mPathFinder.getPath().front(); mObstacleCheck.update(actor, destination, duration); - static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game"); if (smoothMovement) { const float smoothTurnReservedDist = 150; diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 276321b81..cc28b7995 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -97,12 +97,12 @@ namespace float dotProduct = v1.x() * v3.x() + v1.y() * v3.y(); float crossProduct = v1.x() * v3.y() - v1.y() * v3.x(); - // Check that the angle between v1 and v3 is less or equal than 10 degrees. - static const float cos170 = std::cos(osg::PI / 180 * 170); - bool checkAngle = dotProduct <= cos170 * v1.length() * v3.length(); + // Check that the angle between v1 and v3 is less or equal than 5 degrees. + static const float cos175 = std::cos(osg::PI * (175.0 / 180)); + bool checkAngle = dotProduct <= cos175 * v1.length() * v3.length(); // Check that distance from p2 to the line (p1, p3) is less or equal than `pointTolerance`. - bool checkDist = std::abs(crossProduct) <= pointTolerance * (p3 - p1).length() * 2; + bool checkDist = std::abs(crossProduct) <= pointTolerance * (p3 - p1).length(); return checkAngle && checkDist; } @@ -296,7 +296,7 @@ namespace MWMechanics return getXAngleToDir(dir); } - void PathFinder::update(const osg::Vec3f& position, const float pointTolerance, const float destinationTolerance) + void PathFinder::update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, bool shortenIfAlmostStraight) { if (mPath.empty()) return; @@ -304,10 +304,13 @@ namespace MWMechanics while (mPath.size() > 1 && sqrDistanceIgnoreZ(mPath.front(), position) < pointTolerance * pointTolerance) mPath.pop_front(); - while (mPath.size() > 2 && isAlmostStraight(mPath[0], mPath[1], mPath[2], pointTolerance)) - mPath.erase(mPath.begin() + 1); - if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance)) - mPath.pop_front(); + if (shortenIfAlmostStraight) + { + while (mPath.size() > 2 && isAlmostStraight(mPath[0], mPath[1], mPath[2], pointTolerance)) + mPath.erase(mPath.begin() + 1); + if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance)) + mPath.pop_front(); + } if (mPath.size() == 1 && sqrDistanceIgnoreZ(mPath.front(), position) < destinationTolerance * destinationTolerance) mPath.pop_front(); diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 5af822fee..bd81bbfe1 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -102,7 +102,7 @@ namespace MWMechanics const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts); /// Remove front point if exist and within tolerance - void update(const osg::Vec3f& position, const float pointTolerance, const float destinationTolerance); + void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, bool shortenIfAlmostStraight); bool checkPathCompleted() const { From 9590377f224c49b62eb86ac6a8505ef023b50c9c Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 29 Jan 2021 21:27:22 +0100 Subject: [PATCH 127/171] Don't ignore Z in path finding if actor can move by Z. --- apps/openmw/mwmechanics/aipackage.cpp | 5 +++-- apps/openmw/mwmechanics/pathfinding.cpp | 15 ++++++++++++--- apps/openmw/mwmechanics/pathfinding.hpp | 3 ++- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index da1766f4d..99132b711 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -96,6 +96,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& const float distToTarget = distance(position, dest); const bool isDestReached = (distToTarget <= destTolerance); + const bool actorCanMoveByZ = canActorMoveByZAxis(actor); if (!isDestReached && mTimer > AI_REACTION_TIME) { @@ -104,7 +105,6 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& const bool wasShortcutting = mIsShortcutting; bool destInLOS = false; - const bool actorCanMoveByZ = canActorMoveByZAxis(actor); // Prohibit shortcuts for AiWander, if the actor can not move in 3 dimensions. mIsShortcutting = actorCanMoveByZ @@ -151,7 +151,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& const float pointTolerance = std::max(MIN_TOLERANCE, actorTolerance); static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game"); - mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE, /*shortenIfAlmostStraight=*/smoothMovement); + mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE, + /*shortenIfAlmostStraight=*/smoothMovement, actorCanMoveByZ); if (isDestReached || mPathFinder.checkPathCompleted()) // if path is finished { diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index cc28b7995..93ae90547 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -296,7 +296,8 @@ namespace MWMechanics return getXAngleToDir(dir); } - void PathFinder::update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, bool shortenIfAlmostStraight) + void PathFinder::update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, + bool shortenIfAlmostStraight, bool canMoveByZ) { if (mPath.empty()) return; @@ -312,8 +313,16 @@ namespace MWMechanics mPath.pop_front(); } - if (mPath.size() == 1 && sqrDistanceIgnoreZ(mPath.front(), position) < destinationTolerance * destinationTolerance) - mPath.pop_front(); + if (mPath.size() == 1) + { + float distSqr; + if (canMoveByZ) + distSqr = (mPath.front() - position).length2(); + else + distSqr = sqrDistanceIgnoreZ(mPath.front(), position); + if (distSqr < destinationTolerance * destinationTolerance) + mPath.pop_front(); + } } void PathFinder::buildStraightPath(const osg::Vec3f& endPoint) diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index bd81bbfe1..b5c376b8c 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -102,7 +102,8 @@ namespace MWMechanics const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts); /// Remove front point if exist and within tolerance - void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, bool shortenIfAlmostStraight); + void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, + bool shortenIfAlmostStraight, bool canMoveByZ); bool checkPathCompleted() const { From 157b14cdaa07395a8e2cf17c0242d57d908d3ae1 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 29 Jan 2021 22:53:02 +0100 Subject: [PATCH 128/171] Fix #5821: NPCs from mods getting removed if mod order was changed --- CHANGELOG.md | 1 + apps/openmw/mwworld/cellstore.cpp | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f347fb46e..aa74164d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -97,6 +97,7 @@ Bug #5739: Saving and loading the save a second or two before hitting the ground doesn't count fall damage Bug #5758: Paralyzed actors behavior is inconsistent with vanilla Bug #5762: Movement solver is insufficiently robust + Bug #5821: NPCs from mods getting removed if mod order was changed Feature #390: 3rd person look "over the shoulder" Feature #1536: Show more information about level on menu Feature #2386: Distant Statics in the form of Object Paging diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 3f98684ae..cc614133a 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -921,6 +921,13 @@ namespace MWWorld refnum.load(reader, true, "MVRF"); movedTo.load(reader); + if (refnum.hasContentFile()) + { + auto iter = contentFileMap.find(refnum.mContentFile); + if (iter != contentFileMap.end()) + refnum.mContentFile = iter->second; + } + // Search for the reference. It might no longer exist if its content file was removed. Ptr movedRef = searchViaRefNum(refnum); if (movedRef.isEmpty()) From f87c45c92a6cfba68d431398a1ceab57976b74ff Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sat, 30 Jan 2021 16:03:02 +0200 Subject: [PATCH 129/171] Get collision box extents and center from btBvhTriangleMeshShape --- components/resource/bulletshapemanager.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index bcadf51c4..d1da9090d 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -86,7 +86,17 @@ public: return osg::ref_ptr(); osg::ref_ptr shape (new BulletShape); - shape->mCollisionShape = new TriangleMeshShape(mTriangleMesh.release(), true); + btBvhTriangleMeshShape* triangleMeshShape = new TriangleMeshShape(mTriangleMesh.release(), true); + btVector3 aabbMin = triangleMeshShape->getLocalAabbMin(); + btVector3 aabbMax = triangleMeshShape->getLocalAabbMax(); + shape->mCollisionBox.extents[0] = (aabbMax[0] - aabbMin[0]) / 2.0f; + shape->mCollisionBox.extents[1] = (aabbMax[1] - aabbMin[1]) / 2.0f; + shape->mCollisionBox.extents[2] = (aabbMax[2] - aabbMin[2]) / 2.0f; + shape->mCollisionBox.center = osg::Vec3f( (aabbMax[0] + aabbMin[0]) / 2.0f, + (aabbMax[1] + aabbMin[1]) / 2.0f, + (aabbMax[2] + aabbMin[2]) / 2.0f ); + shape->mCollisionShape = triangleMeshShape; + return shape; } From 7edaa50195baad8ac126ca116b12049d0fe29e5d Mon Sep 17 00:00:00 2001 From: madsbuvi Date: Sun, 31 Jan 2021 18:02:05 +0100 Subject: [PATCH 130/171] another approach --- components/sceneutil/mwshadowtechnique.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 294780cfd..7476bc219 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -997,9 +997,9 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) } // 1. Traverse main scene graph - cv.pushStateSet( _shadowRecievingPlaceholderStateSet.get() ); - - osg::ref_ptr decoratorStateGraph = cv.getCurrentStateGraph(); + auto* shadowReceiverStateSet = vdd->getStateSet(cv.getTraversalNumber()); + shadowReceiverStateSet->clear(); + cv.pushStateSet(shadowReceiverStateSet); cullShadowReceivingScene(&cv); @@ -1426,7 +1426,7 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) if (numValidShadows>0) { - decoratorStateGraph->setStateSet(selectStateSetForRenderingShadow(*vdd, cv.getTraversalNumber())); + selectStateSetForRenderingShadow(*vdd, cv.getTraversalNumber()); } // OSG_NOTICE<<"End of shadow setup Projection matrix "<<*cv.getProjectionMatrix()< Date: Mon, 1 Feb 2021 18:34:10 +0000 Subject: [PATCH 131/171] Fix the regression involving Cure --- apps/openmw/mwmechanics/actors.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index c9fcf8280..cb3570d21 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -958,22 +958,22 @@ namespace MWMechanics if (actor.getClass().hasInventoryStore(actor)) actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Paralyze); } - else if (effects.get(ESM::MagicEffect::CureCommonDisease).getModifier() > 0) + if (effects.get(ESM::MagicEffect::CureCommonDisease).getModifier() > 0) { creatureStats.getSpells().purgeCommonDisease(); } - else if (effects.get(ESM::MagicEffect::CureBlightDisease).getModifier() > 0) + if (effects.get(ESM::MagicEffect::CureBlightDisease).getModifier() > 0) { creatureStats.getSpells().purgeBlightDisease(); } - else if (effects.get(ESM::MagicEffect::CureCorprusDisease).getModifier() > 0) + if (effects.get(ESM::MagicEffect::CureCorprusDisease).getModifier() > 0) { creatureStats.getActiveSpells().purgeCorprusDisease(); creatureStats.getSpells().purgeCorprusDisease(); if (actor.getClass().hasInventoryStore(actor)) actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Corprus, true); } - else if (effects.get(ESM::MagicEffect::RemoveCurse).getModifier() > 0) + if (effects.get(ESM::MagicEffect::RemoveCurse).getModifier() > 0) { creatureStats.getSpells().purgeCurses(); } From 7d3f2bc113aed491a004abab576e7871788eb400 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Tue, 2 Feb 2021 17:33:40 +0200 Subject: [PATCH 132/171] Convert underscores in bone names to whitespaces --- components/sceneutil/visitor.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index 00d18ffcb..169e07ada 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -60,7 +60,19 @@ namespace SceneUtil void NodeMapVisitor::apply(osg::MatrixTransform& trans) { // Take transformation for first found node in file - const std::string nodeName = Misc::StringUtils::lowerCase(trans.getName()); + std::string originalNodeName = Misc::StringUtils::lowerCase(trans.getName()); + + // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses whitespace-separated names) + std::string underscore = "_"; + std::size_t foundUnderscore = originalNodeName.find(underscore); + + if (foundUnderscore != std::string::npos) + { + std::replace(originalNodeName.begin(), originalNodeName.end(), '_', ' '); + //originalNodeName.replace(foundUnderscore.begin(), foundPageNumberEnd - foundPageNumber - 6, " ") ); + } + const std::string nodeName = originalNodeName; + mMap.emplace(nodeName, &trans); traverse(trans); From 2162b97fefd91678154d9a8b1c13723e64c266ab Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Tue, 2 Feb 2021 17:34:02 +0200 Subject: [PATCH 133/171] Handle case in osgAnimation bone names --- components/resource/keyframemanager.cpp | 3 ++- components/sceneutil/osgacontroller.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index d739392e8..b6417597d 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "animation.hpp" #include "objectcache.hpp" @@ -21,7 +22,7 @@ namespace Resource void RetrieveAnimationsVisitor::apply(osg::Node& node) { - if (node.libraryName() == std::string("osgAnimation") && node.className() == std::string("Bone") && node.getName() == std::string("bip01")) + if (node.libraryName() == std::string("osgAnimation") && node.className() == std::string("Bone") && Misc::StringUtils::lowerCase(node.getName()) == std::string("bip01")) { osg::ref_ptr callback = new SceneUtil::OsgAnimationController(); diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 37aa525af..8f447621f 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -83,7 +84,7 @@ namespace SceneUtil { osgAnimation::UpdateMatrixTransform* umt = dynamic_cast(cb); if (umt) - if (node.getName() != "bip01") link(umt); + if (Misc::StringUtils::lowerCase(node.getName()) != "bip01") link(umt); cb = cb->getNestedCallback(); } From 5b88d16a50bc3c5def4aa2bcb22501e7a0d2b0a0 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Tue, 2 Feb 2021 21:09:50 +0200 Subject: [PATCH 134/171] Clean-up --- components/resource/scenemanager.cpp | 2 -- components/sceneutil/visitor.cpp | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 92de2e0dd..71f11e382 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -26,8 +26,6 @@ #include #include -#include - #include #include diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index 169e07ada..e632e7ce9 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -67,10 +67,8 @@ namespace SceneUtil std::size_t foundUnderscore = originalNodeName.find(underscore); if (foundUnderscore != std::string::npos) - { std::replace(originalNodeName.begin(), originalNodeName.end(), '_', ' '); - //originalNodeName.replace(foundUnderscore.begin(), foundPageNumberEnd - foundPageNumber - 6, " ") ); - } + const std::string nodeName = originalNodeName; mMap.emplace(nodeName, &trans); From 2c67f34c6d8ca8ba794e3b7bd810e7fdc07e8773 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Tue, 2 Feb 2021 21:18:14 +0200 Subject: [PATCH 135/171] Update base_anim file settings --- files/settings-default.cfg | 59 ++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/files/settings-default.cfg b/files/settings-default.cfg index db947f8ed..8e365690d 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -955,33 +955,62 @@ defer aabb update = true # Loading arbitrary meshes is not advised and may cause instability. load unsupported nif files = false -[Groundcover] - -# enable separate groundcover handling -enabled = false - -# A groundcover density (0.0 <= value <= 1.0) -# 1.0 means 100% density -density = 1.0 - -# A maximum distance in cells on which groundcover is rendered. -distance = 1 - -# A minimum size of groundcover chunk in cells (0.125, 0.25, 0.5, 1.0) -min chunk size = 0.5 - +# 3rd person base animation model that looks also for the corresponding kf-file xbaseanim = meshes\xbase_anim.nif + +# 3rd person base model with textkeys-data baseanim = meshes\base_anim.nif + +# 1st person base animation model that looks also for corresponding kf-file xbaseanim1st = meshes\xbase_anim.1st.nif + +# 3rd person beast race base model with textkeys-data baseanimkna = meshes\base_animkna.nif + +# 1st person beast race base animation model baseanimkna1st = meshes\base_animkna.1st.nif + +# 3rd person female base animation model xbaseanimfemale = meshes/xbase_anim_female.nif + +# 3rd person female base model with textkeys-data baseanimfemale = meshes/base_anim_female.nif + +# 1st person female base model with textkeys-data baseanimfemale1st = meshes\base_anim_female.1st.nif + +# 3rd person werewolf skin wolfskin = meshes\wolf\skin.nif + +# 1st person werewolf skin wolfskin1st = meshes\wolf\skin.1st.nif + +# Argonian smimkna xargonianswimkna = meshes/xargonian_swimkna.nif + +# File to load xbaseanim 3rd person animations xbaseanimkf = meshes\xbase_anim.kf + +# File to load xbaseanim 3rd person animations xbaseanim1stkf = meshes\xbase_anim.1st.kf + +# File to load xbaseanim animations from xbaseanimfemalekf = meshes/xbase_anim_female.kf + +# File to load xargonianswimkna animations from xargonianswimknakf = meshes/xargonian_swimkna.kf + +[Groundcover] + +# enable separate groundcover handling +enabled = false + +# A groundcover density (0.0 <= value <= 1.0) +# 1.0 means 100% density +density = 1.0 + +# A maximum distance in cells on which groundcover is rendered. +distance = 1 + +# A minimum size of groundcover chunk in cells (0.125, 0.25, 0.5, 1.0) +min chunk size = 0.5 From 1221889cf7c099ae0b00e7752b24448a1deac9e0 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Tue, 2 Feb 2021 21:41:17 +0200 Subject: [PATCH 136/171] Limit conversion of underscores to nodes origating from osgAnimation library --- components/sceneutil/visitor.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index e632e7ce9..60f99b2fe 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -62,12 +62,15 @@ namespace SceneUtil // Take transformation for first found node in file std::string originalNodeName = Misc::StringUtils::lowerCase(trans.getName()); - // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses whitespace-separated names) - std::string underscore = "_"; - std::size_t foundUnderscore = originalNodeName.find(underscore); + if (trans.libraryName() == std::string("osgAnimation")) + { + // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses whitespace-separated names) + std::string underscore = "_"; + std::size_t foundUnderscore = originalNodeName.find(underscore); - if (foundUnderscore != std::string::npos) - std::replace(originalNodeName.begin(), originalNodeName.end(), '_', ' '); + if (foundUnderscore != std::string::npos) + std::replace(originalNodeName.begin(), originalNodeName.end(), '_', ' '); + } const std::string nodeName = originalNodeName; From 382324173b3587fcf46ca00ce47cf2b71e75d9ac Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Tue, 2 Feb 2021 21:42:25 +0200 Subject: [PATCH 137/171] \ to / --- files/settings-default.cfg | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 8e365690d..67d944d19 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -956,19 +956,19 @@ defer aabb update = true load unsupported nif files = false # 3rd person base animation model that looks also for the corresponding kf-file -xbaseanim = meshes\xbase_anim.nif +xbaseanim = meshes/xbase_anim.nif # 3rd person base model with textkeys-data -baseanim = meshes\base_anim.nif +baseanim = meshes/base_anim.nif # 1st person base animation model that looks also for corresponding kf-file -xbaseanim1st = meshes\xbase_anim.1st.nif +xbaseanim1st = meshes/xbase_anim.1st.nif # 3rd person beast race base model with textkeys-data -baseanimkna = meshes\base_animkna.nif +baseanimkna = meshes/base_animkna.nif # 1st person beast race base animation model -baseanimkna1st = meshes\base_animkna.1st.nif +baseanimkna1st = meshes/base_animkna.1st.nif # 3rd person female base animation model xbaseanimfemale = meshes/xbase_anim_female.nif @@ -977,22 +977,22 @@ xbaseanimfemale = meshes/xbase_anim_female.nif baseanimfemale = meshes/base_anim_female.nif # 1st person female base model with textkeys-data -baseanimfemale1st = meshes\base_anim_female.1st.nif +baseanimfemale1st = meshes/base_anim_female.1st.nif # 3rd person werewolf skin -wolfskin = meshes\wolf\skin.nif +wolfskin = meshes/wolf/skin.nif # 1st person werewolf skin -wolfskin1st = meshes\wolf\skin.1st.nif +wolfskin1st = meshes/wolf/skin.1st.nif # Argonian smimkna xargonianswimkna = meshes/xargonian_swimkna.nif # File to load xbaseanim 3rd person animations -xbaseanimkf = meshes\xbase_anim.kf +xbaseanimkf = meshes/xbase_anim.kf # File to load xbaseanim 3rd person animations -xbaseanim1stkf = meshes\xbase_anim.1st.kf +xbaseanim1stkf = meshes/xbase_anim.1st.kf # File to load xbaseanim animations from xbaseanimfemalekf = meshes/xbase_anim_female.kf From 6c0c28c2eb8d41ee26dd562f332ce72c6a2c76c3 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sat, 30 Jan 2021 16:03:02 +0200 Subject: [PATCH 138/171] Get collision box extents and center from btBvhTriangleMeshShape --- components/resource/bulletshapemanager.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index bcadf51c4..d1da9090d 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -86,7 +86,17 @@ public: return osg::ref_ptr(); osg::ref_ptr shape (new BulletShape); - shape->mCollisionShape = new TriangleMeshShape(mTriangleMesh.release(), true); + btBvhTriangleMeshShape* triangleMeshShape = new TriangleMeshShape(mTriangleMesh.release(), true); + btVector3 aabbMin = triangleMeshShape->getLocalAabbMin(); + btVector3 aabbMax = triangleMeshShape->getLocalAabbMax(); + shape->mCollisionBox.extents[0] = (aabbMax[0] - aabbMin[0]) / 2.0f; + shape->mCollisionBox.extents[1] = (aabbMax[1] - aabbMin[1]) / 2.0f; + shape->mCollisionBox.extents[2] = (aabbMax[2] - aabbMin[2]) / 2.0f; + shape->mCollisionBox.center = osg::Vec3f( (aabbMax[0] + aabbMin[0]) / 2.0f, + (aabbMax[1] + aabbMin[1]) / 2.0f, + (aabbMax[2] + aabbMin[2]) / 2.0f ); + shape->mCollisionShape = triangleMeshShape; + return shape; } From b28d8251aaa7fbf0e5ccab4fede1c34e1e62ad00 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sat, 30 Jan 2021 15:27:47 +0200 Subject: [PATCH 139/171] Clone animation tracks --- components/sceneutil/osgacontroller.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 8f447621f..766d0cb79 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -103,10 +103,14 @@ namespace SceneUtil } OsgAnimationController::OsgAnimationController(const OsgAnimationController ©, const osg::CopyOp ©op) : SceneUtil::KeyframeController(copy, copyop) - , mMergedAnimationTracks(copy.mMergedAnimationTracks) , mEmulatedAnimations(copy.mEmulatedAnimations) { mLinker = nullptr; + for (const auto& mergedAnimationTrack : copy.mMergedAnimationTracks) + { + Resource::Animation* copiedAnimationTrack = dynamic_cast(mergedAnimationTrack.get()->clone(copyop)); + mMergedAnimationTracks.emplace_back(copiedAnimationTrack); + } } osg::Vec3f OsgAnimationController::getTranslation(float time) const From bae27e81993813d7de75fe9eb466fa7c50a9a6ad Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Tue, 2 Feb 2021 18:00:14 +0200 Subject: [PATCH 140/171] dynamic_cast to static_cast --- components/sceneutil/osgacontroller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 766d0cb79..b2d819117 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -108,7 +108,7 @@ namespace SceneUtil mLinker = nullptr; for (const auto& mergedAnimationTrack : copy.mMergedAnimationTracks) { - Resource::Animation* copiedAnimationTrack = dynamic_cast(mergedAnimationTrack.get()->clone(copyop)); + Resource::Animation* copiedAnimationTrack = static_cast(mergedAnimationTrack.get()->clone(copyop)); mMergedAnimationTracks.emplace_back(copiedAnimationTrack); } } From 384112746c3e7c7999853afb77076c23be4e2de1 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 3 Feb 2021 14:25:09 +0200 Subject: [PATCH 141/171] Add option for custom collision node with non-nif files --- apps/openmw/mwworld/scene.cpp | 4 +-- components/resource/bulletshapemanager.cpp | 32 +++++++++++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index fcf2c4b38..57da414dd 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -115,6 +115,8 @@ namespace std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS()); const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); + if (!physics.getObject(ptr)) + ptr.getClass().insertObject (ptr, model, rotation, physics); if (!onlyPhysics) { bool useAnim = ptr.getClass().useAnim(); @@ -134,8 +136,6 @@ namespace // Restore effect particles MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); } - if (!physics.getObject(ptr)) - ptr.getClass().insertObject (ptr, model, rotation, physics); } void addObject(const MWWorld::Ptr& ptr, const MWPhysics::PhysicsSystem& physics, DetourNavigator::Navigator& navigator) diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index d1da9090d..aceb9bb35 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -8,6 +8,7 @@ #include +#include #include #include @@ -145,11 +146,34 @@ osg::ref_ptr BulletShapeManager::getShape(const std::string & osg::ref_ptr constNode (mSceneManager->getTemplate(normalized)); osg::ref_ptr node (const_cast(constNode.get())); // const-trickery required because there is no const version of NodeVisitor - NodeToShapeVisitor visitor; - node->accept(visitor); - shape = visitor.getShape(); + + // Check first if there's a custom collision node + SceneUtil::FindByNameVisitor nameFinder("Collision"); + node->accept(nameFinder); + if (nameFinder.mFoundNode) + { + NodeToShapeVisitor visitor; + nameFinder.mFoundNode->accept(visitor); + shape = visitor.getShape(); + for (unsigned int i = 0; i < nameFinder.mFoundNode->getNumParents(); ++i) + { + nameFinder.mFoundNode->getParent(i)->removeChild(nameFinder.mFoundNode); + } + + /* // CleanObjectRootVisitor is an alternative method + SceneUtil::CleanObjectRootVisitor cleanerVisitor; + nameFinder.mFoundNode->accept(cleanerVisitor);*/ + } + + // Generate a collision shape from the mesh if (!shape) - return osg::ref_ptr(); + { + NodeToShapeVisitor visitor; + node->accept(visitor); + shape = visitor.getShape(); + if (!shape) + return osg::ref_ptr(); + } } mCache->addEntryToObjectCache(normalized, shape); From 5c32460153497359ef3815c6b79e4437c3667480 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 3 Feb 2021 14:25:50 +0200 Subject: [PATCH 142/171] Add underscore-separated node-names to reserved-list --- components/resource/scenemanager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 71f11e382..70320760f 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -390,7 +390,8 @@ namespace Resource { const char* reserved[] = {"Head", "Neck", "Chest", "Groin", "Right Hand", "Left Hand", "Right Wrist", "Left Wrist", "Shield Bone", "Right Forearm", "Left Forearm", "Right Upper Arm", "Left Upper Arm", "Right Foot", "Left Foot", "Right Ankle", "Left Ankle", "Right Knee", "Left Knee", "Right Upper Leg", "Left Upper Leg", "Right Clavicle", - "Left Clavicle", "Weapon Bone", "Tail", "Bip01", "Root Bone", "BoneOffset", "AttachLight", "Arrow", "Camera"}; + "Left Clavicle", "Weapon Bone", "Tail", "Bip01", "Root Bone", "BoneOffset", "AttachLight", "Arrow", "Camera", "Collision", "Right_Wrist", "Left_Wrist", + "Shield_Bone", "Right_Forearm", "Left_Forearm", "Right_Upper_Arm", "Left_Clavicle", "Weapon_Bone", "Root_Bone"}; reservedNames = std::vector(reserved, reserved + sizeof(reserved)/sizeof(reserved[0])); From 0639f8b7c6d5536f8a0ab03a058ab16bfb54c8bb Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 3 Feb 2021 18:45:22 +0000 Subject: [PATCH 143/171] Make the dummy texture for the character preview shadow-friendly --- apps/openmw/mwrender/characterpreview.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 14735050c..262b03229 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -184,6 +184,9 @@ namespace MWRender osg::ref_ptr dummyTexture = new osg::Texture2D(); dummyTexture->setInternalFormat(GL_RED); dummyTexture->setTextureSize(1, 1); + // This might clash with a shadow map, so make sure it doesn't cast shadows + dummyTexture->setShadowComparison(true); + dummyTexture->setShadowCompareFunc(osg::Texture::ShadowCompareFunc::ALWAYS); stateset->setTextureAttributeAndModes(7, dummyTexture, osg::StateAttribute::ON); stateset->setTextureAttribute(7, noBlendAlphaEnv, osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("noAlpha", true)); From e91d1a2b42aa3b28e5f86acc83201702c08cc2b7 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 3 Feb 2021 21:16:26 +0200 Subject: [PATCH 144/171] Fix earlier broken commit --- apps/openmw/mwworld/scene.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 57da414dd..fcf2c4b38 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -115,8 +115,6 @@ namespace std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS()); const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); - if (!physics.getObject(ptr)) - ptr.getClass().insertObject (ptr, model, rotation, physics); if (!onlyPhysics) { bool useAnim = ptr.getClass().useAnim(); @@ -136,6 +134,8 @@ namespace // Restore effect particles MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); } + if (!physics.getObject(ptr)) + ptr.getClass().insertObject (ptr, model, rotation, physics); } void addObject(const MWWorld::Ptr& ptr, const MWPhysics::PhysicsSystem& physics, DetourNavigator::Navigator& navigator) From 45fde84f4ff005d476a05b628a27329ce01d976a Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 3 Feb 2021 21:16:54 +0200 Subject: [PATCH 145/171] Use nodemasks and visitors for detecting custom collision shapes --- components/resource/bulletshapemanager.cpp | 13 +++++-------- components/resource/scenemanager.cpp | 9 +++++++++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index aceb9bb35..ad37eda0d 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -148,21 +148,18 @@ osg::ref_ptr BulletShapeManager::getShape(const std::string & osg::ref_ptr node (const_cast(constNode.get())); // const-trickery required because there is no const version of NodeVisitor // Check first if there's a custom collision node + unsigned int visitAllNodesMask = 0xffffffff; SceneUtil::FindByNameVisitor nameFinder("Collision"); + nameFinder.setTraversalMask(visitAllNodesMask); + nameFinder.setNodeMaskOverride(visitAllNodesMask); node->accept(nameFinder); if (nameFinder.mFoundNode) { NodeToShapeVisitor visitor; + visitor.setTraversalMask(visitAllNodesMask); + visitor.setNodeMaskOverride(visitAllNodesMask); nameFinder.mFoundNode->accept(visitor); shape = visitor.getShape(); - for (unsigned int i = 0; i < nameFinder.mFoundNode->getNumParents(); ++i) - { - nameFinder.mFoundNode->getParent(i)->removeChild(nameFinder.mFoundNode); - } - - /* // CleanObjectRootVisitor is an alternative method - SceneUtil::CleanObjectRootVisitor cleanerVisitor; - nameFinder.mFoundNode->accept(cleanerVisitor);*/ } // Generate a collision shape from the mesh diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 70320760f..19cc96433 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -373,6 +374,14 @@ namespace Resource errormsg << "Error loading " << normalizedFilename << ": " << result.message() << " code " << result.status() << std::endl; throw std::runtime_error(errormsg.str()); } + + // Recognize and hide collision node + unsigned int hiddenNodeMask = 0; + SceneUtil::FindByNameVisitor nameFinder("Collision"); + result.getNode()->accept(nameFinder); + if (nameFinder.mFoundNode) + nameFinder.mFoundNode->setNodeMask(hiddenNodeMask); + return result.getNode(); } } From 88ca4a1db6e10eeeecd0d9f97fbe61dd5b8a9411 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 4 Feb 2021 00:18:25 +0100 Subject: [PATCH 146/171] Count navmesh cache hit rate --- components/detournavigator/navmeshtilescache.cpp | 12 +++++++++++- components/detournavigator/navmeshtilescache.hpp | 2 ++ components/resource/stats.cpp | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp index cff93ac0e..1b90ea89e 100644 --- a/components/detournavigator/navmeshtilescache.cpp +++ b/components/detournavigator/navmeshtilescache.cpp @@ -53,13 +53,16 @@ namespace DetourNavigator } NavMeshTilesCache::NavMeshTilesCache(const std::size_t maxNavMeshDataSize) - : mMaxNavMeshDataSize(maxNavMeshDataSize), mUsedNavMeshDataSize(0), mFreeNavMeshDataSize(0) {} + : mMaxNavMeshDataSize(maxNavMeshDataSize), mUsedNavMeshDataSize(0), mFreeNavMeshDataSize(0), + mHitCount(0), mGetCount(0){} NavMeshTilesCache::Value NavMeshTilesCache::get(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, const RecastMesh& recastMesh, const std::vector& offMeshConnections) { const std::lock_guard lock(mMutex); + ++mGetCount; + const auto agentValues = mValues.find(agentHalfExtents); if (agentValues == mValues.end()) return Value(); @@ -74,6 +77,8 @@ namespace DetourNavigator acquireItemUnsafe(tile->second); + ++mHitCount; + return Value(*this, tile->second); } @@ -123,17 +128,22 @@ namespace DetourNavigator std::size_t navMeshCacheSize = 0; std::size_t usedNavMeshTiles = 0; std::size_t cachedNavMeshTiles = 0; + std::size_t hitCount = 0; + std::size_t getCount = 0; { const std::lock_guard lock(mMutex); navMeshCacheSize = mUsedNavMeshDataSize; usedNavMeshTiles = mBusyItems.size(); cachedNavMeshTiles = mFreeItems.size(); + hitCount = mHitCount; + getCount = mGetCount; } stats.setAttribute(frameNumber, "NavMesh CacheSize", navMeshCacheSize); stats.setAttribute(frameNumber, "NavMesh UsedTiles", usedNavMeshTiles); stats.setAttribute(frameNumber, "NavMesh CachedTiles", cachedNavMeshTiles); + stats.setAttribute(frameNumber, "NavMesh CacheHitRate", static_cast(hitCount) / getCount * 100.0); } void NavMeshTilesCache::removeLeastRecentlyUsed() diff --git a/components/detournavigator/navmeshtilescache.hpp b/components/detournavigator/navmeshtilescache.hpp index 064d9e185..a6dd1ed9a 100644 --- a/components/detournavigator/navmeshtilescache.hpp +++ b/components/detournavigator/navmeshtilescache.hpp @@ -194,6 +194,8 @@ namespace DetourNavigator std::size_t mMaxNavMeshDataSize; std::size_t mUsedNavMeshDataSize; std::size_t mFreeNavMeshDataSize; + std::size_t mHitCount; + std::size_t mGetCount; std::list mBusyItems; std::list mFreeItems; std::map> mValues; diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 3649af089..690814f91 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -394,6 +394,7 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) "NavMesh CacheSize", "NavMesh UsedTiles", "NavMesh CachedTiles", + "NavMesh CacheHitRate", "", "Mechanics Actors", "Mechanics Objects", From 489107c5eed4de2ce0cef3a88d1cedf0cb811311 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 4 Feb 2021 00:44:15 +0100 Subject: [PATCH 147/171] Count navmesh cache key once in item size Key is stored only in NavMeshTilesCache::Item, TileMap uses KeyView with a pointer to a vector. --- .../detournavigator/navmeshtilescache.cpp | 22 +++++++++---------- .../detournavigator/navmeshtilescache.cpp | 2 +- .../detournavigator/navmeshtilescache.hpp | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp index e8e7820d9..5bc7af646 100644 --- a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp +++ b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp @@ -68,7 +68,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; - const std::size_t maxSize = navMeshDataSize + 2 * navMeshKeySize; + const std::size_t maxSize = navMeshDataSize + navMeshKeySize; NavMeshTilesCache cache(maxSize); const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, @@ -81,7 +81,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; - const std::size_t maxSize = 2 * (navMeshDataSize + 2 * navMeshKeySize); + const std::size_t maxSize = 2 * (navMeshDataSize + navMeshKeySize); NavMeshTilesCache cache(maxSize); const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData anotherNavMeshData {anotherData, 1}; @@ -97,7 +97,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; - const std::size_t maxSize = navMeshDataSize + 2 * navMeshKeySize; + const std::size_t maxSize = navMeshDataSize + navMeshKeySize; NavMeshTilesCache cache(maxSize); cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); @@ -142,7 +142,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshWithWaterKeySize; - const std::size_t maxSize = navMeshDataSize + 2 * navMeshKeySize; + const std::size_t maxSize = navMeshDataSize + navMeshKeySize; NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; @@ -163,7 +163,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; - const std::size_t maxSize = navMeshDataSize + 2 * navMeshKeySize; + const std::size_t maxSize = navMeshDataSize + navMeshKeySize; NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; @@ -182,7 +182,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshWithWaterKeySize; - const std::size_t maxSize = 2 * (navMeshDataSize + 2 * navMeshKeySize); + const std::size_t maxSize = 2 * (navMeshDataSize + navMeshKeySize); NavMeshTilesCache cache(maxSize); const std::vector leastRecentlySetWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; @@ -214,7 +214,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshWithWaterKeySize; - const std::size_t maxSize = 2 * (navMeshDataSize + 2 * navMeshKeySize); + const std::size_t maxSize = 2 * (navMeshDataSize + navMeshKeySize); NavMeshTilesCache cache(maxSize); const std::vector leastRecentlyUsedWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; @@ -258,7 +258,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; - const std::size_t maxSize = 2 * (navMeshDataSize + 2 * navMeshKeySize); + const std::size_t maxSize = 2 * (navMeshDataSize + navMeshKeySize); NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; @@ -277,7 +277,7 @@ namespace const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize1 = cRecastMeshKeySize; const std::size_t navMeshKeySize2 = cRecastMeshWithWaterKeySize; - const std::size_t maxSize = 2 * navMeshDataSize + 2 * navMeshKeySize1 + 2 * navMeshKeySize2; + const std::size_t maxSize = 2 * navMeshDataSize + navMeshKeySize1 + navMeshKeySize2; NavMeshTilesCache cache(maxSize); const std::vector anotherWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; @@ -306,7 +306,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; - const std::size_t maxSize = navMeshDataSize + 2 * navMeshKeySize; + const std::size_t maxSize = navMeshDataSize + navMeshKeySize; NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; @@ -330,7 +330,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; - const std::size_t maxSize = navMeshDataSize + 2 * navMeshKeySize; + const std::size_t maxSize = navMeshDataSize + navMeshKeySize; NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp index 1b90ea89e..d9879fcbb 100644 --- a/components/detournavigator/navmeshtilescache.cpp +++ b/components/detournavigator/navmeshtilescache.cpp @@ -97,7 +97,7 @@ namespace DetourNavigator return Value(); auto navMeshKey = makeNavMeshKey(recastMesh, offMeshConnections); - const auto itemSize = navMeshSize + 2 * navMeshKey.size(); + const auto itemSize = navMeshSize + navMeshKey.size(); if (itemSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) return Value(); diff --git a/components/detournavigator/navmeshtilescache.hpp b/components/detournavigator/navmeshtilescache.hpp index a6dd1ed9a..338ead3aa 100644 --- a/components/detournavigator/navmeshtilescache.hpp +++ b/components/detournavigator/navmeshtilescache.hpp @@ -208,7 +208,7 @@ namespace DetourNavigator static std::size_t getSize(const Item& item) { - return static_cast(item.mNavMeshData.mSize) + 2 * item.mNavMeshKey.size(); + return static_cast(item.mNavMeshData.mSize) + item.mNavMeshKey.size(); } }; } From ad1f8c1e8445acdb2f40f7fcee767b587e5df11c Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 4 Feb 2021 00:14:29 +0100 Subject: [PATCH 148/171] Sort water and off mesh connections for recast mesh Inconsisten order of these objects in navmesh cache key leads to cache misses due to key inequality. --- components/bullethelpers/operators.hpp | 15 +++++++++++++++ components/detournavigator/offmeshconnection.hpp | 7 +++++++ .../detournavigator/offmeshconnectionsmanager.hpp | 2 ++ components/detournavigator/recastmesh.hpp | 8 ++++++++ components/detournavigator/recastmeshbuilder.cpp | 1 + 5 files changed, 33 insertions(+) diff --git a/components/bullethelpers/operators.hpp b/components/bullethelpers/operators.hpp index ea88deddf..dd2ec8017 100644 --- a/components/bullethelpers/operators.hpp +++ b/components/bullethelpers/operators.hpp @@ -68,4 +68,19 @@ inline std::ostream& operator <<(std::ostream& stream, BroadphaseNativeTypes val } } +inline bool operator <(const btVector3& lhs, const btVector3& rhs) +{ + return std::tie(lhs.x(), lhs.y(), lhs.z()) < std::tie(rhs.x(), rhs.y(), rhs.z()); +} + +inline bool operator <(const btMatrix3x3& lhs, const btMatrix3x3& rhs) +{ + return std::tie(lhs[0], lhs[1], lhs[2]) < std::tie(rhs[0], rhs[1], rhs[2]); +} + +inline bool operator <(const btTransform& lhs, const btTransform& rhs) +{ + return std::tie(lhs.getBasis(), lhs.getOrigin()) < std::tie(rhs.getBasis(), rhs.getOrigin()); +} + #endif diff --git a/components/detournavigator/offmeshconnection.hpp b/components/detournavigator/offmeshconnection.hpp index ca999dbdb..01bae0273 100644 --- a/components/detournavigator/offmeshconnection.hpp +++ b/components/detournavigator/offmeshconnection.hpp @@ -5,6 +5,8 @@ #include +#include + namespace DetourNavigator { struct OffMeshConnection @@ -13,6 +15,11 @@ namespace DetourNavigator osg::Vec3f mEnd; AreaType mAreaType; }; + + inline bool operator<(const OffMeshConnection& lhs, const OffMeshConnection& rhs) + { + return std::tie(lhs.mStart, lhs.mEnd, lhs.mAreaType) < std::tie(rhs.mStart, rhs.mEnd, rhs.mAreaType); + } } #endif diff --git a/components/detournavigator/offmeshconnectionsmanager.hpp b/components/detournavigator/offmeshconnectionsmanager.hpp index de707f3a8..1ad96e3b9 100644 --- a/components/detournavigator/offmeshconnectionsmanager.hpp +++ b/components/detournavigator/offmeshconnectionsmanager.hpp @@ -85,6 +85,8 @@ namespace DetourNavigator std::for_each(byId.first, byId.second, [&] (const auto& v) { result.push_back(v.second); }); }); + std::sort(result.begin(), result.end()); + return result; } diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index f3259903f..29f37822e 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -5,9 +5,12 @@ #include "chunkytrimesh.hpp" #include "bounds.hpp" +#include + #include #include #include +#include #include @@ -87,6 +90,11 @@ namespace DetourNavigator ChunkyTriMesh mChunkyTriMesh; Bounds mBounds; }; + + inline bool operator<(const RecastMesh::Water& lhs, const RecastMesh::Water& rhs) + { + return std::tie(lhs.mCellSize, lhs.mTransform) < std::tie(rhs.mCellSize, rhs.mTransform); + } } #endif diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index ee014b932..f8456acf0 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -155,6 +155,7 @@ namespace DetourNavigator std::shared_ptr RecastMeshBuilder::create(std::size_t generation, std::size_t revision) { optimizeRecastMesh(mIndices, mVertices); + std::sort(mWater.begin(), mWater.end()); return std::make_shared(generation, revision, mIndices, mVertices, mAreaTypes, mWater, mSettings.get().mTrianglesPerChunk); } From 3a2cea52714b26c69dfd89672dd1ad6a9886acac Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 4 Feb 2021 00:15:54 +0100 Subject: [PATCH 149/171] Use raw recast mesh data and off mesh connections for navmesh key Serialization into a vector of chars produces inconsistent results that leads to reduced cache hit rate. Using a structured object is a more clear solution and allows to remove serialization and nontrivial key compare logic with more straigt forward structured object comparison. --- .../detournavigator/navmeshtilescache.cpp | 123 ++----------- .../detournavigator/navmeshtilescache.hpp | 168 ++++++++++-------- components/detournavigator/recastmesh.hpp | 6 + 3 files changed, 110 insertions(+), 187 deletions(-) diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp index d9879fcbb..84c658653 100644 --- a/components/detournavigator/navmeshtilescache.cpp +++ b/components/detournavigator/navmeshtilescache.cpp @@ -9,52 +9,21 @@ namespace DetourNavigator { namespace { - inline std::vector makeNavMeshKey(const RecastMesh& recastMesh, - const std::vector& offMeshConnections) + inline std::size_t getSize(const RecastMesh& recastMesh, + const std::vector& offMeshConnections) { const std::size_t indicesSize = recastMesh.getIndices().size() * sizeof(int); const std::size_t verticesSize = recastMesh.getVertices().size() * sizeof(float); const std::size_t areaTypesSize = recastMesh.getAreaTypes().size() * sizeof(AreaType); const std::size_t waterSize = recastMesh.getWater().size() * sizeof(RecastMesh::Water); const std::size_t offMeshConnectionsSize = offMeshConnections.size() * sizeof(OffMeshConnection); - - std::vector result(indicesSize + verticesSize + areaTypesSize + waterSize + offMeshConnectionsSize); - unsigned char* dst = result.data(); - - if (indicesSize > 0) - { - std::memcpy(dst, recastMesh.getIndices().data(), indicesSize); - dst += indicesSize; - } - - if (verticesSize > 0) - { - std::memcpy(dst, recastMesh.getVertices().data(), verticesSize); - dst += verticesSize; - } - - if (areaTypesSize > 0) - { - std::memcpy(dst, recastMesh.getAreaTypes().data(), areaTypesSize); - dst += areaTypesSize; - } - - if (waterSize > 0) - { - std::memcpy(dst, recastMesh.getWater().data(), waterSize); - dst += waterSize; - } - - if (offMeshConnectionsSize > 0) - std::memcpy(dst, offMeshConnections.data(), offMeshConnectionsSize); - - return result; + return indicesSize + verticesSize + areaTypesSize + waterSize + offMeshConnectionsSize; } } NavMeshTilesCache::NavMeshTilesCache(const std::size_t maxNavMeshDataSize) : mMaxNavMeshDataSize(maxNavMeshDataSize), mUsedNavMeshDataSize(0), mFreeNavMeshDataSize(0), - mHitCount(0), mGetCount(0){} + mHitCount(0), mGetCount(0) {} NavMeshTilesCache::Value NavMeshTilesCache::get(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, const RecastMesh& recastMesh, const std::vector& offMeshConnections) @@ -71,7 +40,7 @@ namespace DetourNavigator if (tileValues == agentValues->second.end()) return Value(); - const auto tile = tileValues->second.mMap.find(RecastMeshKeyView(recastMesh, offMeshConnections)); + const auto tile = tileValues->second.mMap.find(NavMeshKeyView(recastMesh, offMeshConnections)); if (tile == tileValues->second.mMap.end()) return Value(); @@ -96,8 +65,11 @@ namespace DetourNavigator if (navMeshSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) return Value(); - auto navMeshKey = makeNavMeshKey(recastMesh, offMeshConnections); - const auto itemSize = navMeshSize + navMeshKey.size(); + NavMeshKey navMeshKey { + RecastMeshData {recastMesh.getIndices(), recastMesh.getVertices(), recastMesh.getAreaTypes(), recastMesh.getWater()}, + offMeshConnections + }; + const auto itemSize = navMeshSize + getSize(recastMesh, offMeshConnections); if (itemSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) return Value(); @@ -105,7 +77,7 @@ namespace DetourNavigator while (!mFreeItems.empty() && mUsedNavMeshDataSize + itemSize > mMaxNavMeshDataSize) removeLeastRecentlyUsed(); - const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, std::move(navMeshKey)); + const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, std::move(navMeshKey), itemSize); const auto emplaced = mValues[agentHalfExtents][changedTile].mMap.emplace(iterator->mNavMeshKey, iterator); if (!emplaced.second) @@ -162,8 +134,8 @@ namespace DetourNavigator if (value == tileValues->second.mMap.end()) return; - mUsedNavMeshDataSize -= getSize(item); - mFreeNavMeshDataSize -= getSize(item); + mUsedNavMeshDataSize -= item.mSize; + mFreeNavMeshDataSize -= item.mSize; tileValues->second.mMap.erase(value); mFreeItems.pop_back(); @@ -184,7 +156,7 @@ namespace DetourNavigator return; mBusyItems.splice(mBusyItems.end(), mFreeItems, iterator); - mFreeNavMeshDataSize -= getSize(*iterator); + mFreeNavMeshDataSize -= iterator->mSize; } void NavMeshTilesCache::releaseItem(ItemIterator iterator) @@ -195,71 +167,6 @@ namespace DetourNavigator const std::lock_guard lock(mMutex); mFreeItems.splice(mFreeItems.begin(), mBusyItems, iterator); - mFreeNavMeshDataSize += getSize(*iterator); - } - - namespace - { - struct CompareBytes - { - const unsigned char* mRhsIt; - const unsigned char* const mRhsEnd; - - template - int operator ()(const std::vector& lhs) - { - const auto lhsBegin = reinterpret_cast(lhs.data()); - const auto lhsEnd = reinterpret_cast(lhs.data() + lhs.size()); - const auto lhsSize = static_cast(lhsEnd - lhsBegin); - const auto rhsSize = static_cast(mRhsEnd - mRhsIt); - - if (lhsBegin == nullptr || mRhsIt == nullptr) - { - if (lhsSize < rhsSize) - return -1; - else if (lhsSize > rhsSize) - return 1; - else - return 0; - } - - const auto size = std::min(lhsSize, rhsSize); - - if (const auto result = std::memcmp(lhsBegin, mRhsIt, size)) - return result; - - if (lhsSize > rhsSize) - return 1; - - mRhsIt += size; - - return 0; - } - }; - } - - int NavMeshTilesCache::RecastMeshKeyView::compare(const std::vector& other) const - { - CompareBytes compareBytes {other.data(), other.data() + other.size()}; - - if (const auto result = compareBytes(mRecastMesh.get().getIndices())) - return result; - - if (const auto result = compareBytes(mRecastMesh.get().getVertices())) - return result; - - if (const auto result = compareBytes(mRecastMesh.get().getAreaTypes())) - return result; - - if (const auto result = compareBytes(mRecastMesh.get().getWater())) - return result; - - if (const auto result = compareBytes(mOffMeshConnections.get())) - return result; - - if (compareBytes.mRhsIt < compareBytes.mRhsEnd) - return -1; - - return 0; + mFreeNavMeshDataSize += iterator->mSize; } } diff --git a/components/detournavigator/navmeshtilescache.hpp b/components/detournavigator/navmeshtilescache.hpp index 338ead3aa..25f4dc187 100644 --- a/components/detournavigator/navmeshtilescache.hpp +++ b/components/detournavigator/navmeshtilescache.hpp @@ -27,6 +27,89 @@ namespace DetourNavigator int mSize; }; + struct RecastMeshData + { + std::vector mIndices; + std::vector mVertices; + std::vector mAreaTypes; + std::vector mWater; + }; + + inline bool operator <(const RecastMeshData& lhs, const RecastMeshData& rhs) + { + return std::tie(lhs.mIndices, lhs.mVertices, lhs.mAreaTypes, lhs.mWater) + < std::tie(rhs.mIndices, rhs.mVertices, rhs.mAreaTypes, rhs.mWater); + } + + inline bool operator <(const RecastMeshData& lhs, const RecastMesh& rhs) + { + return std::tie(lhs.mIndices, lhs.mVertices, lhs.mAreaTypes, lhs.mWater) + < std::tie(rhs.getIndices(), rhs.getVertices(), rhs.getAreaTypes(), rhs.getWater()); + } + + inline bool operator <(const RecastMesh& lhs, const RecastMeshData& rhs) + { + return std::tie(lhs.getIndices(), lhs.getVertices(), lhs.getAreaTypes(), lhs.getWater()) + < std::tie(rhs.mIndices, rhs.mVertices, rhs.mAreaTypes, rhs.mWater); + } + + struct NavMeshKey + { + RecastMeshData mRecastMesh; + std::vector mOffMeshConnections; + }; + + inline bool operator <(const NavMeshKey& lhs, const NavMeshKey& rhs) + { + return std::tie(lhs.mRecastMesh, lhs.mOffMeshConnections) + < std::tie(rhs.mRecastMesh, rhs.mOffMeshConnections); + } + + struct NavMeshKeyRef + { + std::reference_wrapper mRef; + + explicit NavMeshKeyRef(const NavMeshKey& ref) : mRef(ref) {} + }; + + inline bool operator <(const NavMeshKeyRef& lhs, const NavMeshKeyRef& rhs) + { + return lhs.mRef.get() < rhs.mRef.get(); + } + + struct NavMeshKeyView + { + std::reference_wrapper mRecastMesh; + std::reference_wrapper> mOffMeshConnections; + + NavMeshKeyView(const RecastMesh& recastMesh, const std::vector& offMeshConnections) + : mRecastMesh(recastMesh), mOffMeshConnections(offMeshConnections) {} + }; + + inline bool operator <(const NavMeshKeyView& lhs, const NavMeshKey& rhs) + { + return std::tie(lhs.mRecastMesh.get(), lhs.mOffMeshConnections.get()) + < std::tie(rhs.mRecastMesh, rhs.mOffMeshConnections); + } + + inline bool operator <(const NavMeshKey& lhs, const NavMeshKeyView& rhs) + { + return std::tie(lhs.mRecastMesh, lhs.mOffMeshConnections) + < std::tie(rhs.mRecastMesh.get(), rhs.mOffMeshConnections.get()); + } + + template + inline bool operator <(const NavMeshKeyRef& lhs, const R& rhs) + { + return lhs.mRef.get() < rhs; + } + + template + inline bool operator <(const L& lhs, const NavMeshKeyRef& rhs) + { + return lhs < rhs.mRef.get(); + } + class NavMeshTilesCache { public: @@ -35,14 +118,16 @@ namespace DetourNavigator std::atomic mUseCount; osg::Vec3f mAgentHalfExtents; TilePosition mChangedTile; - std::vector mNavMeshKey; + NavMeshKey mNavMeshKey; NavMeshData mNavMeshData; + std::size_t mSize; - Item(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, std::vector&& navMeshKey) + Item(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, NavMeshKey&& navMeshKey, std::size_t size) : mUseCount(0) , mAgentHalfExtents(agentHalfExtents) , mChangedTile(changedTile) - , mNavMeshKey(std::move(navMeshKey)) + , mNavMeshKey(navMeshKey) + , mSize(size) {} }; @@ -115,79 +200,9 @@ namespace DetourNavigator void reportStats(unsigned int frameNumber, osg::Stats& stats) const; private: - class KeyView - { - public: - KeyView() = default; - - virtual ~KeyView() = default; - - KeyView(const std::vector& value) - : mValue(&value) {} - - const std::vector& getValue() const - { - assert(mValue); - return *mValue; - } - - virtual int compare(const std::vector& other) const - { - assert(mValue); - - const auto valueSize = mValue->size(); - const auto otherSize = other.size(); - - if (const auto result = std::memcmp(mValue->data(), other.data(), std::min(valueSize, otherSize))) - return result; - - if (valueSize < otherSize) - return -1; - - if (valueSize > otherSize) - return 1; - - return 0; - } - - virtual bool isLess(const KeyView& other) const - { - assert(mValue); - return other.compare(*mValue) > 0; - } - - friend bool operator <(const KeyView& lhs, const KeyView& rhs) - { - return lhs.isLess(rhs); - } - - private: - const std::vector* mValue = nullptr; - }; - - class RecastMeshKeyView : public KeyView - { - public: - RecastMeshKeyView(const RecastMesh& recastMesh, const std::vector& offMeshConnections) - : mRecastMesh(recastMesh), mOffMeshConnections(offMeshConnections) {} - - int compare(const std::vector& other) const override; - - bool isLess(const KeyView& other) const override - { - return compare(other.getValue()) < 0; - } - - virtual ~RecastMeshKeyView() = default; - - private: - std::reference_wrapper mRecastMesh; - std::reference_wrapper> mOffMeshConnections; - }; - struct TileMap { - std::map mMap; + std::map> mMap; }; mutable std::mutex mMutex; @@ -205,11 +220,6 @@ namespace DetourNavigator void acquireItemUnsafe(ItemIterator iterator); void releaseItem(ItemIterator iterator); - - static std::size_t getSize(const Item& item) - { - return static_cast(item.mNavMeshData.mSize) + item.mNavMeshKey.size(); - } }; } diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index 29f37822e..746422ac8 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -95,6 +95,12 @@ namespace DetourNavigator { return std::tie(lhs.mCellSize, lhs.mTransform) < std::tie(rhs.mCellSize, rhs.mTransform); } + + inline bool operator <(const RecastMesh& lhs, const RecastMesh& rhs) + { + return std::tie(lhs.getIndices(), lhs.getVertices(), lhs.getAreaTypes(), lhs.getWater()) + < std::tie(rhs.getIndices(), rhs.getVertices(), rhs.getAreaTypes(), rhs.getWater()); + } } #endif From 68fe6b91145350c12487332cc26483f032f2ec64 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 4 Feb 2021 01:12:52 +0100 Subject: [PATCH 150/171] Use only item size to check whether item fits cache Item size has to be counted anyway and there is no reason to check only navmesh data first. --- components/detournavigator/navmeshtilescache.cpp | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp index 84c658653..b6048da58 100644 --- a/components/detournavigator/navmeshtilescache.cpp +++ b/components/detournavigator/navmeshtilescache.cpp @@ -55,27 +55,20 @@ namespace DetourNavigator const RecastMesh& recastMesh, const std::vector& offMeshConnections, NavMeshData&& value) { - const auto navMeshSize = static_cast(value.mSize); + const auto itemSize = static_cast(value.mSize) + getSize(recastMesh, offMeshConnections); const std::lock_guard lock(mMutex); - if (navMeshSize > mMaxNavMeshDataSize) + if (itemSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) return Value(); - if (navMeshSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) - return Value(); + while (!mFreeItems.empty() && mUsedNavMeshDataSize + itemSize > mMaxNavMeshDataSize) + removeLeastRecentlyUsed(); NavMeshKey navMeshKey { RecastMeshData {recastMesh.getIndices(), recastMesh.getVertices(), recastMesh.getAreaTypes(), recastMesh.getWater()}, offMeshConnections }; - const auto itemSize = navMeshSize + getSize(recastMesh, offMeshConnections); - - if (itemSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) - return Value(); - - while (!mFreeItems.empty() && mUsedNavMeshDataSize + itemSize > mMaxNavMeshDataSize) - removeLeastRecentlyUsed(); const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, std::move(navMeshKey), itemSize); const auto emplaced = mValues[agentHalfExtents][changedTile].mMap.emplace(iterator->mNavMeshKey, iterator); From 61e014a7657092bf8ea1e426665ea3934169c560 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 4 Feb 2021 21:25:38 +0100 Subject: [PATCH 151/171] Allow negative values for ai stats --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/stat.cpp | 4 +++- apps/openmw/mwmechanics/stat.hpp | 2 +- apps/openmw/mwscript/aiextensions.cpp | 6 ++---- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa74164d9..b5b66ee01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -98,6 +98,7 @@ Bug #5758: Paralyzed actors behavior is inconsistent with vanilla Bug #5762: Movement solver is insufficiently robust Bug #5821: NPCs from mods getting removed if mod order was changed + Bug #5835: OpenMW doesn't accept negative values for NPC's hello, alarm, fight, and flee Feature #390: 3rd person look "over the shoulder" Feature #1536: Show more information about level on menu Feature #2386: Distant Statics in the form of Object Paging diff --git a/apps/openmw/mwmechanics/stat.cpp b/apps/openmw/mwmechanics/stat.cpp index 7f71cf9b1..c87de2ccb 100644 --- a/apps/openmw/mwmechanics/stat.cpp +++ b/apps/openmw/mwmechanics/stat.cpp @@ -18,8 +18,10 @@ namespace MWMechanics } template - T Stat::getModified() const + T Stat::getModified(bool capped) const { + if(!capped) + return mModified; return std::max(static_cast(0), mModified); } diff --git a/apps/openmw/mwmechanics/stat.hpp b/apps/openmw/mwmechanics/stat.hpp index 5f49da48e..fb9dca922 100644 --- a/apps/openmw/mwmechanics/stat.hpp +++ b/apps/openmw/mwmechanics/stat.hpp @@ -28,7 +28,7 @@ namespace MWMechanics const T& getBase() const; - T getModified() const; + T getModified(bool capped = true) const; T getCurrentModified() const; T getModifier() const; T getCurrentModifier() const; diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 499c2f672..223ae3a15 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -241,7 +241,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push(ptr.getClass().getCreatureStats (ptr).getAiSetting (mIndex).getModified()); + runtime.push(ptr.getClass().getCreatureStats (ptr).getAiSetting (mIndex).getModified(false)); } }; template @@ -276,9 +276,7 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWMechanics::Stat stat = ptr.getClass().getCreatureStats(ptr).getAiSetting(mIndex); - stat.setModified(value, 0); - ptr.getClass().getCreatureStats(ptr).setAiSetting(mIndex, stat); + ptr.getClass().getCreatureStats(ptr).setAiSetting(mIndex, value); ptr.getClass().setBaseAISetting(ptr.getCellRef().getRefId(), mIndex, value); } }; From d5844b098295389c3b757a5a3c54f0627780d59b Mon Sep 17 00:00:00 2001 From: unelsson Date: Thu, 4 Feb 2021 23:14:21 +0200 Subject: [PATCH 152/171] Use accompanying txt file for textkeys in osgAnimation formats --- components/resource/keyframemanager.cpp | 68 ++++++++++++++++++++----- components/resource/keyframemanager.hpp | 10 +++- 2 files changed, 65 insertions(+), 13 deletions(-) diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index b6417597d..923ce4326 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -18,7 +18,9 @@ namespace Resource { - RetrieveAnimationsVisitor::RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr animationManager) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mTarget(target), mAnimationManager(animationManager) {} + RetrieveAnimationsVisitor::RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr animationManager, + const std::string& normalized, const VFS::Manager* vfs) : + osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mTarget(target), mAnimationManager(animationManager), mNormalized(normalized), mVFS(vfs) {} void RetrieveAnimationsVisitor::apply(osg::Node& node) { @@ -39,27 +41,19 @@ namespace Resource osg::ref_ptr mergedAnimationTrack = new Resource::Animation; std::string animationName = animation->getName(); - std::string start = animationName + std::string(": start"); - std::string stop = animationName + std::string(": stop"); + mergedAnimationTrack->setName(animationName); const osgAnimation::ChannelList& channels = animation->getChannels(); for (const auto& channel: channels) { mergedAnimationTrack->addChannel(channel.get()->clone()); // is ->clone needed? } - mergedAnimationTrack->setName(animation->getName()); + callback->addMergedAnimationTrack(mergedAnimationTrack); float startTime = animation->getStartTime(); float stopTime = startTime + animation->getDuration(); - // mTextKeys is a nif-thing, used by OpenMW's animation system - // Format is likely "AnimationName: [Keyword_optional] [Start OR Stop]" - // AnimationNames are keywords like idle2, idle3... AiPackages and various mechanics control which animations are played - // Keywords can be stuff like Loop, Equip, Unequip, Block, InventoryHandtoHand, InventoryWeaponOneHand, PickProbe, Slash, Thrust, Chop... even "Slash Small Follow" - mTarget.mTextKeys.emplace(startTime, std::move(start)); - mTarget.mTextKeys.emplace(stopTime, std::move(stop)); - SceneUtil::EmulatedAnimation emulatedAnimation; emulatedAnimation.mStartTime = startTime; emulatedAnimation.mStopTime = stopTime; @@ -67,12 +61,62 @@ namespace Resource emulatedAnimations.emplace_back(emulatedAnimation); } } + + // mTextKeys is a nif-thing, used by OpenMW's animation system + // Format is likely "AnimationName: [Keyword_optional] [Start OR Stop]" + // AnimationNames are keywords like idle2, idle3... AiPackages and various mechanics control which animations are played + // Keywords can be stuff like Loop, Equip, Unequip, Block, InventoryHandtoHand, InventoryWeaponOneHand, PickProbe, Slash, Thrust, Chop... even "Slash Small Follow" + // osgAnimation formats should have a .txt file with the same name, each line holding a textkey and whitespace separated time value + // e.g. idle: start 0.0333 + try + { + Files::IStreamPtr textKeysFile = mVFS->get(changeFileExtension(mNormalized, "txt")); + std::string line; + while ( getline (*textKeysFile, line) ) + { + Log(Debug::Warning) << "add textkey ***" << parseTextKey(line) << "***" << parseTimeSignature(line) << "***"; + mTarget.mTextKeys.emplace(parseTimeSignature(line), std::move(parseTextKey(line))); + } + } + catch (std::exception& e) + { + Log(Debug::Warning) << "No textkey file found for " << mNormalized; + } + callback->setEmulatedAnimations(emulatedAnimations); mTarget.mKeyframeControllers.emplace(node.getName(), callback); } traverse(node); } + + std::string RetrieveAnimationsVisitor::parseTextKey(const std::string& line) + { + size_t spacePos = line.find_last_of(' '); + if (spacePos != std::string::npos) + return line.substr(0, spacePos); + return ""; + } + + double RetrieveAnimationsVisitor::parseTimeSignature(const std::string& line) + { + size_t spacePos = line.find_last_of(' '); + double time = 0.0; + if (spacePos != std::string::npos && spacePos + 1 < line.size()) + time = std::stod(line.substr(spacePos + 1)); + return time; + } + + std::string RetrieveAnimationsVisitor::changeFileExtension(const std::string file, const std::string ext) + { + size_t extPos = file.find_last_of('.'); + if (extPos != std::string::npos && extPos+1 < file.size()) + { + return file.substr(0, extPos + 1) + ext; + } + return file; + } + } namespace Resource @@ -110,7 +154,7 @@ namespace Resource osg::ref_ptr bam = dynamic_cast (scene->getUpdateCallback()); if (bam) { - Resource::RetrieveAnimationsVisitor rav(*loaded.get(), bam); + Resource::RetrieveAnimationsVisitor rav(*loaded.get(), bam, normalized, mVFS); scene->accept(rav); } } diff --git a/components/resource/keyframemanager.hpp b/components/resource/keyframemanager.hpp index 3e992ac5e..87a20b97a 100644 --- a/components/resource/keyframemanager.hpp +++ b/components/resource/keyframemanager.hpp @@ -15,13 +15,21 @@ namespace Resource class RetrieveAnimationsVisitor : public osg::NodeVisitor { public: - RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr animationManager); + RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr animationManager, + const std::string& normalized, const VFS::Manager* vfs); virtual void apply(osg::Node& node) override; private: + + std::string changeFileExtension(const std::string file, const std::string ext); + std::string parseTextKey(const std::string& line); + double parseTimeSignature(const std::string& line); + SceneUtil::KeyframeHolder& mTarget; osg::ref_ptr mAnimationManager; + std::string mNormalized; + const VFS::Manager* mVFS; }; } From 303f1912a6af88e49f96fc8260621a8e7519e48a Mon Sep 17 00:00:00 2001 From: unelsson Date: Thu, 4 Feb 2021 23:14:52 +0200 Subject: [PATCH 153/171] less debug spam --- components/resource/keyframemanager.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 923ce4326..0c33428e0 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -74,7 +74,6 @@ namespace Resource std::string line; while ( getline (*textKeysFile, line) ) { - Log(Debug::Warning) << "add textkey ***" << parseTextKey(line) << "***" << parseTimeSignature(line) << "***"; mTarget.mTextKeys.emplace(parseTimeSignature(line), std::move(parseTextKey(line))); } } From e42b67ee50b8d5950e183db51d750198d87db7f2 Mon Sep 17 00:00:00 2001 From: Noah Gooder Date: Fri, 5 Feb 2021 17:59:36 +0000 Subject: [PATCH 154/171] Modified actors.cpp and Authors.md --- AUTHORS.md | 1 + apps/openmw/mwmechanics/actors.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS.md b/AUTHORS.md index e6ff67293..53efdd286 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -215,6 +215,7 @@ Programmers Yohaulticetl Yuri Krupenin zelurker + Noah Gooder Documentation ------------- diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index cb3570d21..c0a137158 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -951,7 +951,7 @@ namespace MWMechanics if (actor.getClass().hasInventoryStore(actor)) actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Poison); } - else if (effects.get(ESM::MagicEffect::CureParalyzation).getModifier() > 0) + if (effects.get(ESM::MagicEffect::CureParalyzation).getModifier() > 0) { creatureStats.getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze); creatureStats.getSpells().purgeEffect(ESM::MagicEffect::Paralyze); From 3007cb14a9fa10ef2d17f53a2519030135d737db Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 5 Feb 2021 19:00:35 +0100 Subject: [PATCH 155/171] Also allow negative AI values in dialogue --- CHANGELOG.md | 1 + apps/openmw/mwdialogue/filter.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5b66ee01..d25984a83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -99,6 +99,7 @@ Bug #5762: Movement solver is insufficiently robust Bug #5821: NPCs from mods getting removed if mod order was changed Bug #5835: OpenMW doesn't accept negative values for NPC's hello, alarm, fight, and flee + Bug #5836: OpenMW dialogue/greeting/voice filter doesn't accept negative Ai values for NPC's hello, alarm, fight, and flee Feature #390: 3rd person look "over the shoulder" Feature #1536: Show more information about level on menu Feature #2386: Distant Statics in the form of Object Paging diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index a3c326ab8..334a9db39 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -316,7 +316,7 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con case SelectWrapper::Function_AiSetting: return mActor.getClass().getCreatureStats (mActor).getAiSetting ( - (MWMechanics::CreatureStats::AiSetting)select.getArgument()).getModified(); + (MWMechanics::CreatureStats::AiSetting)select.getArgument()).getModified(false); case SelectWrapper::Function_PcAttribute: From 3e273a759acc8e238de5ce2ceb03d877405a6f0f Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 6 Feb 2021 16:41:46 +0000 Subject: [PATCH 156/171] Clarify method name now we're using it differently --- components/sceneutil/mwshadowtechnique.cpp | 6 +++--- components/sceneutil/mwshadowtechnique.hpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 7476bc219..ba903f6db 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -1426,7 +1426,7 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) if (numValidShadows>0) { - selectStateSetForRenderingShadow(*vdd, cv.getTraversalNumber()); + prepareStateSetForRenderingShadow(*vdd, cv.getTraversalNumber()); } // OSG_NOTICE<<"End of shadow setup Projection matrix "<<*cv.getProjectionMatrix()< stateset = vdd.getStateSet(traversalNumber); diff --git a/components/sceneutil/mwshadowtechnique.hpp b/components/sceneutil/mwshadowtechnique.hpp index 5125247dd..7b934b798 100644 --- a/components/sceneutil/mwshadowtechnique.hpp +++ b/components/sceneutil/mwshadowtechnique.hpp @@ -231,7 +231,7 @@ namespace SceneUtil { virtual void cullShadowCastingScene(osgUtil::CullVisitor* cv, osg::Camera* camera) const; - virtual osg::StateSet* selectStateSetForRenderingShadow(ViewDependentData& vdd, unsigned int traversalNumber) const; + virtual osg::StateSet* prepareStateSetForRenderingShadow(ViewDependentData& vdd, unsigned int traversalNumber) const; protected: virtual ~MWShadowTechnique(); From 6aa75c287aa86e7e1c44cd7cd15ae89cef0894c8 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 7 Feb 2021 00:15:01 +0100 Subject: [PATCH 157/171] Don't check magicka when casting free spells --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/spellutil.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa74164d9..3e29a292f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -98,6 +98,7 @@ Bug #5758: Paralyzed actors behavior is inconsistent with vanilla Bug #5762: Movement solver is insufficiently robust Bug #5821: NPCs from mods getting removed if mod order was changed + Bug #5841: Can't Cast Zero Cost Spells When Magicka is < 0 Feature #390: 3rd person look "over the shoulder" Feature #1536: Show more information about level on menu Feature #2386: Distant Statics in the form of Object Paging diff --git a/apps/openmw/mwmechanics/spellutil.cpp b/apps/openmw/mwmechanics/spellutil.cpp index 8b2f5c46c..0c667e680 100644 --- a/apps/openmw/mwmechanics/spellutil.cpp +++ b/apps/openmw/mwmechanics/spellutil.cpp @@ -123,7 +123,7 @@ namespace MWMechanics if (spell->mData.mType != ESM::Spell::ST_Spell) return 100; - if (checkMagicka && stats.getMagicka().getCurrent() < spell->mData.mCost) + if (checkMagicka && spell->mData.mCost > 0 && stats.getMagicka().getCurrent() < spell->mData.mCost) return 0; if (spell->mData.mFlags & ESM::Spell::F_Always) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 442672d2b..d21c17a51 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3009,7 +3009,7 @@ namespace MWWorld // Check mana bool godmode = (isPlayer && mGodMode); MWMechanics::DynamicStat magicka = stats.getMagicka(); - if (magicka.getCurrent() < spell->mData.mCost && !godmode) + if (spell->mData.mCost > 0 && magicka.getCurrent() < spell->mData.mCost && !godmode) { message = "#{sMagicInsufficientSP}"; fail = true; From 31b5150e0db3f2094d4ac4a67990a82529c69a83 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 7 Feb 2021 09:12:38 +0100 Subject: [PATCH 158/171] Fix implementation of Misc::swapEndiannessInplace --- components/misc/endianness.hpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/components/misc/endianness.hpp b/components/misc/endianness.hpp index 1b43e584e..8019d33ed 100644 --- a/components/misc/endianness.hpp +++ b/components/misc/endianness.hpp @@ -2,6 +2,8 @@ #define COMPONENTS_MISC_ENDIANNESS_H #include +#include +#include namespace Misc { @@ -15,20 +17,26 @@ namespace Misc if constexpr (sizeof(T) == 2) { - uint16_t& v16 = *reinterpret_cast(&v); + uint16_t v16; + std::memcpy(&v16, &v, sizeof(T)); v16 = (v16 >> 8) | (v16 << 8); + std::memcpy(&v, &v16, sizeof(T)); } if constexpr (sizeof(T) == 4) { - uint32_t& v32 = *reinterpret_cast(&v); - v32 = (v32 >> 24) | ((v32 >> 8) & 0xff00) | ((v32 & 0xff00) << 8) || v32 << 24; + uint32_t v32; + std::memcpy(&v32, &v, sizeof(T)); + v32 = (v32 >> 24) | ((v32 >> 8) & 0xff00) | ((v32 & 0xff00) << 8) | v32 << 24; + std::memcpy(&v, &v32, sizeof(T)); } if constexpr (sizeof(T) == 8) { - uint64_t& v64 = *reinterpret_cast(&v); + uint64_t v64; + std::memcpy(&v64, &v, sizeof(T)); v64 = (v64 >> 56) | ((v64 & 0x00ff'0000'0000'0000) >> 40) | ((v64 & 0x0000'ff00'0000'0000) >> 24) | ((v64 & 0x0000'00ff'0000'0000) >> 8) | ((v64 & 0x0000'0000'ff00'0000) << 8) | ((v64 & 0x0000'0000'00ff'0000) << 24) | ((v64 & 0x0000'0000'0000'ff00) << 40) | (v64 << 56); + std::memcpy(&v, &v64, sizeof(T)); } } From f1caeea4445cbe8c0b235203867a86512cd5f120 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 7 Feb 2021 11:58:23 +0100 Subject: [PATCH 159/171] Don't return negative values from GetMagicka --- apps/openmw/mwscript/statsextensions.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 58a943e1a..ab3287c9a 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -187,6 +187,9 @@ namespace MWScript .getCreatureStats(ptr) .getDynamic(mIndex) .getCurrent(); + // GetMagicka shouldn't return negative values + if(mIndex == 1 && value < 0) + value = 0; } runtime.push (value); } From 39c2c19daee5d4cff5379a077bdee4d75ab07d7a Mon Sep 17 00:00:00 2001 From: Jonas Tobias Hopusch Date: Sun, 7 Feb 2021 12:53:06 +0100 Subject: [PATCH 160/171] Update 'toggle sneak' documentation The docs now correctly say that this setting can be changed in the launcher, instead of insisting it's a config-file-only setting. Closes #5844 Signed-off-by: Jonas Tobias Hopusch --- CHANGELOG.md | 1 + docs/source/reference/modding/settings/input.rst | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d25984a83..20932c89f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -132,6 +132,7 @@ Feature #5813: Instanced groundcover support Task #5480: Drop Qt4 support Task #5520: Improve cell name autocompleter implementation + Task #5844: Update 'toggle sneak' documentation 0.46.0 ------ diff --git a/docs/source/reference/modding/settings/input.rst b/docs/source/reference/modding/settings/input.rst index 8a95686cf..d04c267a7 100644 --- a/docs/source/reference/modding/settings/input.rst +++ b/docs/source/reference/modding/settings/input.rst @@ -38,7 +38,7 @@ This setting causes the behavior of the sneak key (bound to Ctrl by default) to toggle sneaking on and off rather than requiring the key to be held down while sneaking. Players that spend significant time sneaking may find the character easier to control with this option enabled. -This setting can only be configured by editing the settings configuration file. +This setting can be toggled in the launcher under "Advanced" -> "Game Mechanics" -> "Toggle sneak". always run ---------- From af0f94f03e373f79823a86fa40e59de9fbfa0788 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 7 Feb 2021 12:55:10 +0100 Subject: [PATCH 161/171] Play damage sound when hurt by elemental shields --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/combat.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d25984a83..6c4c241b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,6 +100,7 @@ Bug #5821: NPCs from mods getting removed if mod order was changed Bug #5835: OpenMW doesn't accept negative values for NPC's hello, alarm, fight, and flee Bug #5836: OpenMW dialogue/greeting/voice filter doesn't accept negative Ai values for NPC's hello, alarm, fight, and flee + Bug #5840: GetSoundPlaying "Health Damage" doesn't play when NPC hits target with shield effect ( vanilla engine behavior ) Feature #390: 3rd person look "over the shoulder" Feature #1536: Show more information about level on menu Feature #2386: Distant Statics in the form of Object Paging diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 183845b8c..fd3f31811 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -345,6 +345,8 @@ namespace MWMechanics MWMechanics::DynamicStat health = attackerStats.getHealth(); health.setCurrent(health.getCurrent() - x); attackerStats.setHealth(health); + + MWBase::Environment::get().getSoundManager()->playSound3D(attacker, "Health Damage", 1.0f, 1.0f); } } From b78820de5563c92184d31c1d44af122b4b830364 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 7 Feb 2021 18:32:03 +0100 Subject: [PATCH 162/171] Ignore projectiles inside of MovementSolver::unstuck. It is normal for actors to be inside of a projectile collision shape. A side effect of moving actors outside of projectile collision shape is that if both the actor and the projectile are near a wall, the actor could get moved outside of the world. --- apps/openmw/mwphysics/movementsolver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 9d2957409..5f0322a1f 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -38,7 +38,7 @@ namespace MWPhysics ContactCollectionCallback(const btCollisionObject * me, osg::Vec3f velocity) : mMe(me) { m_collisionFilterGroup = me->getBroadphaseHandle()->m_collisionFilterGroup; - m_collisionFilterMask = me->getBroadphaseHandle()->m_collisionFilterMask; + m_collisionFilterMask = me->getBroadphaseHandle()->m_collisionFilterMask & ~CollisionType_Projectile; mVelocity = Misc::Convert::toBullet(velocity); } btScalar addSingleResult(btManifoldPoint & contact, const btCollisionObjectWrapper * colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper * colObj1Wrap, int partId1, int index1) override From da6223fc4b7f5bb98143b6a5bd1585ce93fd9a92 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 7 Feb 2021 23:27:45 +0000 Subject: [PATCH 163/171] Use default icon.tga when inventory icon is missing --- apps/openmw/mwgui/itemwidget.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/itemwidget.cpp b/apps/openmw/mwgui/itemwidget.cpp index 940e95a7e..7c7fa8828 100644 --- a/apps/openmw/mwgui/itemwidget.cpp +++ b/apps/openmw/mwgui/itemwidget.cpp @@ -6,6 +6,9 @@ #include // correctIconPath +#include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -106,7 +109,10 @@ namespace MWGui std::string invIcon = ptr.getClass().getInventoryIcon(ptr); if (invIcon.empty()) invIcon = "default icon.tga"; - setIcon(MWBase::Environment::get().getWindowManager()->correctIconPath(invIcon)); + invIcon = MWBase::Environment::get().getWindowManager()->correctIconPath(invIcon); + if (!MWBase::Environment::get().getResourceSystem()->getVFS()->exists(invIcon)) + invIcon = MWBase::Environment::get().getWindowManager()->correctIconPath("default icon.tga"); + setIcon(invIcon); } From 034a7a9dbca2094e3e76bcd075b06ea30ca03ddd Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 7 Feb 2021 23:45:53 +0000 Subject: [PATCH 164/171] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b88e9ee8..83ad5725e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -109,6 +109,7 @@ Feature #2686: Timestamps in openmw.log Feature #3171: OpenMW-CS: Instance drag selection Feature #4894: Consider actors as obstacles for pathfinding + Feature #4977: Use the "default icon.tga" when an item's icon is not found Feature #5043: Head Bobbing Feature #5199: Improve Scene Colors Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher From f3271cb66b1222cfbce3a588858c5b0fe004dcf7 Mon Sep 17 00:00:00 2001 From: Sergey Fukanchik Date: Tue, 9 Feb 2021 13:09:36 -0500 Subject: [PATCH 165/171] Add unit test for swapEndiannessInplace(). Part of Bug #5837 --- apps/openmw_test_suite/CMakeLists.txt | 1 + .../misc/test_endianness.cpp | 122 ++++++++++++++++++ components/misc/endianness.hpp | 2 +- 3 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 apps/openmw_test_suite/misc/test_endianness.cpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 5658a5eef..a3bb0c6f8 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -15,6 +15,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) esm/test_fixed_string.cpp misc/test_stringops.cpp + misc/test_endianness.cpp nifloader/testbulletnifloader.cpp diff --git a/apps/openmw_test_suite/misc/test_endianness.cpp b/apps/openmw_test_suite/misc/test_endianness.cpp new file mode 100644 index 000000000..240e4a49a --- /dev/null +++ b/apps/openmw_test_suite/misc/test_endianness.cpp @@ -0,0 +1,122 @@ +#include +#include "components/misc/endianness.hpp" + +struct EndiannessTest : public ::testing::Test {}; + +TEST_F(EndiannessTest, test_swap_endianness_inplace1) +{ + uint8_t zero=0x00; + uint8_t ff=0xFF; + uint8_t fortytwo=0x42; + uint8_t half=128; + + Misc::swapEndiannessInplace(zero); + EXPECT_EQ(zero, 0x00); + + Misc::swapEndiannessInplace(ff); + EXPECT_EQ(ff, 0xFF); + + Misc::swapEndiannessInplace(fortytwo); + EXPECT_EQ(fortytwo, 0x42); + + Misc::swapEndiannessInplace(half); + EXPECT_EQ(half, 128); +} + +TEST_F(EndiannessTest, test_swap_endianness_inplace2) +{ + uint16_t zero = 0x0000; + uint16_t ffff = 0xFFFF; + uint16_t n12 = 0x0102; + uint16_t fortytwo = 0x0042; + + Misc::swapEndiannessInplace(zero); + EXPECT_EQ(zero, 0x0000); + Misc::swapEndiannessInplace(zero); + EXPECT_EQ(zero, 0x0000); + + Misc::swapEndiannessInplace(ffff); + EXPECT_EQ(ffff, 0xFFFF); + Misc::swapEndiannessInplace(ffff); + EXPECT_EQ(ffff, 0xFFFF); + + Misc::swapEndiannessInplace(n12); + EXPECT_EQ(n12, 0x0201); + Misc::swapEndiannessInplace(n12); + EXPECT_EQ(n12, 0x0102); + + Misc::swapEndiannessInplace(fortytwo); + EXPECT_EQ(fortytwo, 0x4200); + Misc::swapEndiannessInplace(fortytwo); + EXPECT_EQ(fortytwo, 0x0042); +} + +TEST_F(EndiannessTest, test_swap_endianness_inplace4) +{ + uint32_t zero = 0x00000000; + uint32_t n1234 = 0x01020304; + uint32_t ffff = 0xFFFFFFFF; + + Misc::swapEndiannessInplace(zero); + EXPECT_EQ(zero, 0x00000000); + Misc::swapEndiannessInplace(zero); + EXPECT_EQ(zero, 0x00000000); + + Misc::swapEndiannessInplace(n1234); + EXPECT_EQ(n1234, 0x04030201); + Misc::swapEndiannessInplace(n1234); + EXPECT_EQ(n1234, 0x01020304); + + Misc::swapEndiannessInplace(ffff); + EXPECT_EQ(ffff, 0xFFFFFFFF); + Misc::swapEndiannessInplace(ffff); + EXPECT_EQ(ffff, 0xFFFFFFFF); +} + +TEST_F(EndiannessTest, test_swap_endianness_inplace8) +{ + uint64_t zero = 0x0000'0000'0000'0000; + uint64_t n1234 = 0x0102'0304'0506'0708; + uint64_t ffff = 0xFFFF'FFFF'FFFF'FFFF; + + Misc::swapEndiannessInplace(zero); + EXPECT_EQ(zero, 0x0000'0000'0000'0000); + Misc::swapEndiannessInplace(zero); + EXPECT_EQ(zero, 0x0000'0000'0000'0000); + + Misc::swapEndiannessInplace(ffff); + EXPECT_EQ(ffff, 0xFFFF'FFFF'FFFF'FFFF); + Misc::swapEndiannessInplace(ffff); + EXPECT_EQ(ffff, 0xFFFF'FFFF'FFFF'FFFF); + + Misc::swapEndiannessInplace(n1234); + EXPECT_EQ(n1234, 0x0807'0605'0403'0201); + Misc::swapEndiannessInplace(n1234); + EXPECT_EQ(n1234, 0x0102'0304'0506'0708); +} + +TEST_F(EndiannessTest, test_swap_endianness_inplace_float) +{ + const uint32_t original = 0x4023d70a; + const uint32_t expected = 0x0ad72340; + + float number; + memcpy(&number, &original, sizeof(original)); + + Misc::swapEndiannessInplace(number); + + EXPECT_TRUE(!memcmp(&number, &expected, sizeof(expected))); +} + +TEST_F(EndiannessTest, test_swap_endianness_inplace_double) +{ + const uint64_t original = 0x040047ae147ae147ul; + const uint64_t expected = 0x47e17a14ae470004ul; + + double number; + memcpy(&number, &original, sizeof(original)); + + Misc::swapEndiannessInplace(number); + + EXPECT_TRUE(!memcmp(&number, &expected, sizeof(expected)) ); +} diff --git a/components/misc/endianness.hpp b/components/misc/endianness.hpp index 8019d33ed..ad8aba18c 100644 --- a/components/misc/endianness.hpp +++ b/components/misc/endianness.hpp @@ -26,7 +26,7 @@ namespace Misc { uint32_t v32; std::memcpy(&v32, &v, sizeof(T)); - v32 = (v32 >> 24) | ((v32 >> 8) & 0xff00) | ((v32 & 0xff00) << 8) | v32 << 24; + v32 = (v32 >> 24) | ((v32 >> 8) & 0xff00) | ((v32 & 0xff00) << 8) | (v32 << 24); std::memcpy(&v, &v32, sizeof(T)); } if constexpr (sizeof(T) == 8) From f8f6d63d4c34d0284123e664dfcdcde6db7b1c15 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 9 Feb 2021 21:56:21 +0000 Subject: [PATCH 166/171] Optimise groundcover lighting --- files/shaders/lighting.glsl | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 5eae89029..a2dcc758a 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -13,22 +13,13 @@ void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 vie #ifndef GROUNDCOVER lambert = max(lambert, 0.0); #else + float eyeCosine = dot(normalize(viewPos), viewNormal.xyz); + if (lambert < 0.0) { - float cosine = dot(normalize(viewPos), normalize(viewNormal.xyz)); - if (lambert >= 0.0) - cosine = -cosine; - - float mult = 1.0; - float divisor = 8.0; - - if (cosine < 0.0 && cosine >= -1.0/divisor) - mult = mix(1.0, 0.3, -cosine*divisor); - else if (cosine < -1.0/divisor) - mult = 0.3; - - lambert *= mult; - lambert = abs(lambert); + lambert = -lambert; + eyeCosine = -eyeCosine; } + lambert *= clamp(-8.0 * (1.0 - 0.3) * eyeCosine + 1.0, 0.3, 1.0); #endif diffuseOut = gl_LightSource[lightIndex].diffuse.xyz * lambert; } From 2e73d2c1451f080d854b51de89ce77893ebbf67d Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 10 Feb 2021 22:13:04 +0100 Subject: [PATCH 167/171] Fallback to default cell name for door destination --- CHANGELOG.md | 1 + apps/openmw/mwbase/world.hpp | 1 + apps/openmw/mwclass/door.cpp | 27 ++++++--------------------- apps/openmw/mwworld/worldimp.cpp | 16 +++++++++++----- apps/openmw/mwworld/worldimp.hpp | 1 + 5 files changed, 20 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83ad5725e..acfdc9e50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,6 +100,7 @@ Bug #5821: NPCs from mods getting removed if mod order was changed Bug #5835: OpenMW doesn't accept negative values for NPC's hello, alarm, fight, and flee Bug #5836: OpenMW dialogue/greeting/voice filter doesn't accept negative Ai values for NPC's hello, alarm, fight, and flee + Bug #5838: Local map and other menus become blank in some locations while playing Wizards' Islands mod. Bug #5840: GetSoundPlaying "Health Damage" doesn't play when NPC hits target with shield effect ( vanilla engine behavior ) Bug #5841: Can't Cast Zero Cost Spells When Magicka is < 0 Feature #390: 3rd person look "over the shoulder" diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index a3b035a8d..194e8f695 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -185,6 +185,7 @@ namespace MWBase /// /// \note If cell==0, the cell the player is currently in will be used instead to /// generate a name. + virtual std::string getCellName(const ESM::Cell* cell) const = 0; virtual void removeRefScript (MWWorld::RefData *ref) = 0; //< Remove the script attached to ref from mLocalScripts diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index ba51d9c2b..25f7fc456 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -302,30 +302,15 @@ namespace MWClass std::string Door::getDestination (const MWWorld::LiveCellRef& door) { - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - - std::string dest; - if (door.mRef.getDestCell() != "") - { - // door leads to an interior, use interior name as tooltip - dest = door.mRef.getDestCell(); - } - else + std::string dest = door.mRef.getDestCell(); + if (dest.empty()) { // door leads to exterior, use cell name (if any), otherwise translated region name int x,y; - MWBase::Environment::get().getWorld()->positionToIndex (door.mRef.getDoorDest().pos[0], door.mRef.getDoorDest().pos[1], x, y); - const ESM::Cell* cell = store.get().find(x,y); - if (cell->mName != "") - dest = cell->mName; - else - { - const ESM::Region* region = - store.get().find(cell->mRegion); - - //name as is, not a token - return MyGUI::TextIterator::toTagsString(region->mName); - } + auto world = MWBase::Environment::get().getWorld(); + world->positionToIndex (door.mRef.getDoorDest().pos[0], door.mRef.getDoorDest().pos[1], x, y); + const ESM::Cell* cell = world->getStore().get().search(x,y); + dest = world->getCellName(cell); } return "#{sCell=" + dest + "}"; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d21c17a51..651d93ec5 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -656,13 +656,19 @@ namespace MWWorld { if (!cell) cell = mWorldScene->getCurrentCell(); + return getCellName(cell->getCell()); + } - if (!cell->getCell()->isExterior() || !cell->getCell()->mName.empty()) - return cell->getCell()->mName; - - if (const ESM::Region* region = mStore.get().search (cell->getCell()->mRegion)) - return region->mName; + std::string World::getCellName(const ESM::Cell* cell) const + { + if (cell) + { + if (!cell->isExterior() || !cell->mName.empty()) + return cell->mName; + if (const ESM::Region* region = mStore.get().search (cell->mRegion)) + return region->mName; + } return mStore.get().find ("sDefaultCellname")->mValue.getString(); } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 29d29a160..bc2baafd8 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -281,6 +281,7 @@ namespace MWWorld /// /// \note If cell==0, the cell the player is currently in will be used instead to /// generate a name. + std::string getCellName(const ESM::Cell* cell) const override; void removeRefScript (MWWorld::RefData *ref) override; //< Remove the script attached to ref from mLocalScripts From c4e909c29e4f1bbb4c463bc015daf42610ad6ec5 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 12 Feb 2021 19:36:03 +0100 Subject: [PATCH 168/171] Silence a clang warning: warning: moving a temporary object prevents copy elision [-Wpessimizing-move] --- components/resource/keyframemanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 0c33428e0..f4ab79519 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -74,7 +74,7 @@ namespace Resource std::string line; while ( getline (*textKeysFile, line) ) { - mTarget.mTextKeys.emplace(parseTimeSignature(line), std::move(parseTextKey(line))); + mTarget.mTextKeys.emplace(parseTimeSignature(line), parseTextKey(line)); } } catch (std::exception& e) From 0736fec148fa68680a54a928c7fdc186bca9bab5 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 12 Feb 2021 19:21:07 +0000 Subject: [PATCH 169/171] Give binaries static filename --- .gitlab-ci.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 810e23d38..2c45e19e6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -116,12 +116,13 @@ variables: &cs-targets - .\ActivateMSVC.ps1 - cmake --build . --config $config --target ($targets.Split(',')) - cd $config + - echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt - | if (Get-ChildItem -Recurse *.pdb) { - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' + 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.zip '*' + - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}.zip '*' after_script: - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: @@ -206,12 +207,13 @@ Windows_Ninja_CS_RelWithDebInfo: - cd MSVC2019_64 - cmake --build . --config $config --target ($targets.Split(',')) - cd $config + - echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt - | if (Get-ChildItem -Recurse *.pdb) { - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' + 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.zip '*' + - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}.zip '*' after_script: - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: From 5e5c0a1d8982fad48dbed3784da59201ba3954aa Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 13 Feb 2021 00:28:18 +0000 Subject: [PATCH 170/171] Add branch name back to filename The URL needs to contain the branch name anyway, so there's no point removing it. --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2c45e19e6..6bfe0bf23 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -122,7 +122,7 @@ variables: &cs-targets 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}.zip '*' + - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip '*' after_script: - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: @@ -213,7 +213,7 @@ Windows_Ninja_CS_RelWithDebInfo: 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}.zip '*' + - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip '*' after_script: - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: From e7afbc74d82351e59e671343f2a9f40a7603c4e5 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 27 Jan 2021 20:57:11 +0100 Subject: [PATCH 171/171] Wizard: Only allow to select Morrowind.esm master files, display an error if Morrowind.bsa is missing --- apps/wizard/existinginstallationpage.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index 2434f42cf..886fcd95d 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -95,9 +95,9 @@ void Wizard::ExistingInstallationPage::on_browseButton_clicked() { QString selectedFile = QFileDialog::getOpenFileName( this, - tr("Select master file"), + tr("Select Morrowind.esm (located in Data Files)"), QDir::currentPath(), - QString(tr("Morrowind master file (*.esm)")), + QString(tr("Morrowind master file (Morrowind.esm)")), nullptr, QFileDialog::DontResolveSymlinks); @@ -110,7 +110,18 @@ void Wizard::ExistingInstallationPage::on_browseButton_clicked() return; if (!mWizard->findFiles(QLatin1String("Morrowind"), info.absolutePath())) - return; // No valid Morrowind installation found + { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error detecting Morrowind files")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(QObject::tr( + "Morrowind.bsa is missing!
\ + Make sure your Morrowind installation is complete." + )); + msgBox.exec(); + return; + } QString path(QDir::toNativeSeparators(info.absolutePath())); QList items = installationsList->findItems(path, Qt::MatchExactly);