From 6e1c67a9aea864df8dff68ecb0fa3eb5c73c8ed0 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 9 Apr 2021 19:11:26 +0200 Subject: [PATCH 1/2] Account for waterwalking when updating position. Otherwise we might trace down the actor at waterlevel at the wrong coordinate. Triggered by multimark mod with waterwalking effect. --- apps/openmw/mwphysics/mtphysics.cpp | 2 +- apps/openmw/mwphysics/physicssystem.cpp | 47 +++++++++++++------------ apps/openmw/mwphysics/physicssystem.hpp | 4 +-- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 4be8b2396f..4957ef422a 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -317,7 +317,7 @@ namespace MWPhysics // init for (auto& data : actorsData) - data.updatePosition(); + data.updatePosition(mCollisionWorld); mPrevStepCount = numSteps; mRemainingSteps = numSteps; mTimeAccum = timeAccum; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index ac8dd92f86..4087ba7e16 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -60,6 +60,22 @@ #include "movementsolver.hpp" #include "mtphysics.hpp" +namespace +{ + bool canMoveToWaterSurface(const MWPhysics::Actor* physicActor, const float waterlevel, btCollisionWorld* world) + { + if (!physicActor) + return false; + const float halfZ = physicActor->getHalfExtents().z(); + const osg::Vec3f actorPosition = physicActor->getPosition(); + const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ); + const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ); + MWPhysics::ActorTracer tracer; + tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, world); + return (tracer.mFraction >= 1.0f); + } +} + namespace MWPhysics { PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr parentNode) @@ -347,16 +363,7 @@ namespace MWPhysics bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel) { - const Actor* physicActor = getActor(actor); - if (!physicActor) - return false; - const float halfZ = physicActor->getHalfExtents().z(); - const osg::Vec3f actorPosition = physicActor->getPosition(); - const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ); - const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ); - ActorTracer tracer; - tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, mCollisionWorld.get()); - return (tracer.mFraction >= 1.0f); + return ::canMoveToWaterSurface(getActor(actor), waterlevel, mCollisionWorld.get()); } osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::ConstPtr &actor) const @@ -772,16 +779,10 @@ namespace MWPhysics const MWMechanics::MagicEffects& effects = character.getClass().getCreatureStats(character).getMagicEffects(); bool waterCollision = false; - bool moveToWaterSurface = false; if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude()) { - if (!world->isUnderwater(character.getCell(), osg::Vec3f(character.getRefData().getPosition().asVec3()))) + if (physicActor->getCollisionMode() || !world->isUnderwater(character.getCell(), osg::Vec3f(character.getRefData().getPosition().asVec3()))) waterCollision = true; - else if (physicActor->getCollisionMode() && canMoveToWaterSurface(character, waterlevel)) - { - moveToWaterSurface = true; - waterCollision = true; - } } physicActor->setCanWaterWalk(waterCollision); @@ -794,7 +795,7 @@ namespace MWPhysics if (!willSimulate) standingOn = physicActor->getStandingOnPtr(); - actorsFrameData.emplace_back(std::move(physicActor), standingOn, moveToWaterSurface, movement, slowFall, waterlevel); + actorsFrameData.emplace_back(std::move(physicActor), standingOn, waterCollision, movement, slowFall, waterlevel); } mMovementQueue.clear(); return actorsFrameData; @@ -937,9 +938,9 @@ namespace MWPhysics } ActorFrameData::ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, - bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel) + bool waterCollision, osg::Vec3f movement, float slowFall, float waterlevel) : mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn), - mDidJump(false), mNeedLand(false), mMoveToWaterSurface(moveToWaterSurface), + mDidJump(false), mNeedLand(false), mWaterCollision(waterCollision), mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(movement), mPosition(), mRefpos() { const MWBase::World *world = MWBase::Environment::get().getWorld(); @@ -953,7 +954,7 @@ namespace MWPhysics mWasOnGround = actor->getOnGround(); } - void ActorFrameData::updatePosition() + void ActorFrameData::updatePosition(btCollisionWorld* world) { mActorRaw->updateWorldPosition(); // If physics runs "fast enough", position are interpolated without simulation @@ -961,10 +962,10 @@ namespace MWPhysics // regardless of simulation speed. mActorRaw->applyOffsetChange(); mPosition = mActorRaw->getPosition(); - if (mMoveToWaterSurface) + if (mWaterCollision && mPosition.z() < mWaterlevel && canMoveToWaterSurface(mActorRaw, mWaterlevel, world)) { mPosition.z() = mWaterlevel; - MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition.x(), mPosition.y(), mPosition.z()); + MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition.x(), mPosition.y(), mPosition.z(), false); } mOldHeight = mPosition.z(); mRefpos = mActorRaw->getPtr().getRefData().getPosition(); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 5ccbf1a380..ce10b4246c 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -79,7 +79,7 @@ namespace MWPhysics struct ActorFrameData { ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel); - void updatePosition(); + void updatePosition(btCollisionWorld* world); std::weak_ptr mActor; Actor* mActorRaw; MWWorld::Ptr mStandingOn; @@ -90,7 +90,7 @@ namespace MWPhysics bool mDidJump; bool mFloatToSurface; bool mNeedLand; - bool mMoveToWaterSurface; + bool mWaterCollision; float mWaterlevel; float mSlowFall; float mOldHeight; From 8874a5be22710c06b26f25c83051dddf373d6f67 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 9 Apr 2021 23:06:36 +0200 Subject: [PATCH 2/2] Change (again) the way SetPos behave. Instead of registering the desired change of position and rely on physics simulation to apply it to the world, immediately change the position in the world without reset the simulation. --- apps/openmw/mwbase/world.hpp | 4 +-- apps/openmw/mwphysics/actor.cpp | 12 +------ .../mwscript/transformationextensions.cpp | 21 +++++++++--- apps/openmw/mwworld/worldimp.cpp | 32 ++++--------------- apps/openmw/mwworld/worldimp.hpp | 7 ++-- 5 files changed, 27 insertions(+), 49 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 3d55ad987b..6cee36b402 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -281,13 +281,13 @@ namespace MWBase virtual void deleteObject (const MWWorld::Ptr& ptr) = 0; virtual void undeleteObject (const MWWorld::Ptr& ptr) = 0; - virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z, bool moveToActive=false) = 0; + virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z, bool movePhysics=true, bool moveToActive=false) = 0; ///< @return an updated Ptr in case the Ptr's cell changes virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0; ///< @return an updated Ptr - virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec) = 0; + virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec, bool moveToActive) = 0; ///< @return an updated Ptr virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0; diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 776212ede9..f9adc9bc69 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -3,8 +3,6 @@ #include #include -#include -#include #include #include #include @@ -181,6 +179,7 @@ bool Actor::setPosition(const osg::Vec3f& position) if (mSkipSimulation) return false; bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged; + updateWorldPosition(); applyOffsetChange(); mPreviousPosition = mPosition; mPosition = position; @@ -197,15 +196,6 @@ void Actor::applyOffsetChange() { if (mPositionOffset.length() == 0) return; - if (mPositionOffset.z() != 0) - { - // Often, offset are set in sequence x, y, z - // We don't want actors to be moved under the ground - // Check terrain height at new coordinate and update z offset if necessary - const auto pos = mWorldPosition + mPositionOffset; - const auto terrainHeight = mPtr.getCell()->isExterior() ? MWBase::Environment::get().getWorld()->getTerrainHeightAt(pos) : -std::numeric_limits::max(); - mPositionOffset.z() = std::max(pos.z(), terrainHeight) - mWorldPosition.z(); - } mWorldPosition += mPositionOffset; mPosition += mPositionOffset; mPreviousPosition += mPositionOffset; diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 6b92378c70..ba211503e1 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -32,7 +32,7 @@ namespace MWScript std::vector actors; MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors); for (auto& actor : actors) - MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff); + MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff, false); } template @@ -284,6 +284,17 @@ namespace MWScript } else if(axis == "z") { + // We should not place actors under ground + if (ptr.getClass().isActor()) + { + float terrainHeight = -std::numeric_limits::max(); + if (ptr.getCell()->isExterior()) + terrainHeight = MWBase::Environment::get().getWorld()->getTerrainHeightAt(curPos); + + if (pos < terrainHeight) + pos = terrainHeight; + } + newPos[2] = pos; } else @@ -292,7 +303,7 @@ namespace MWScript } dynamic_cast(runtime.getContext()).updatePtr(ptr, - MWBase::Environment::get().getWorld()->moveObjectBy(ptr, newPos - curPos)); + MWBase::Environment::get().getWorld()->moveObjectBy(ptr, newPos - curPos, true)); } }; @@ -428,7 +439,7 @@ namespace MWScript } else { - ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z, true); + ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z, true, true); } dynamic_cast(runtime.getContext()).updatePtr(base,ptr); @@ -715,7 +726,7 @@ namespace MWScript // This approach can be used to create elevators. moveStandingActors(ptr, diff); dynamic_cast(runtime.getContext()).updatePtr(ptr, - MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff)); + MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false)); } }; @@ -751,7 +762,7 @@ namespace MWScript // This approach can be used to create elevators. moveStandingActors(ptr, diff); dynamic_cast(runtime.getContext()).updatePtr(ptr, - MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff)); + MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false)); } }; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 3b50dd6aac..5b9afa4fe2 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -862,19 +862,6 @@ namespace MWWorld if (reference == getPlayerPtr()) throw std::runtime_error("can not disable player object"); - // A common pattern to teleport NPC in scripts is a sequence of SetPos/Disable/Enable - // Disable/Enable create a new physics actor, and so the SetPos call is lost - // Call moveObject so that the newly created physics actor will have up-to-date position - if (reference.getClass().isActor()) - { - auto* physactor = mPhysics->getActor(reference); - if (physactor) - { - physactor->applyOffsetChange(); - const auto position = physactor->getSimulationPosition(); - moveObject(reference, position.x(), position.y(), position.z(), true); - } - } reference.getRefData().disable(); if (reference.getCellRef().getRefNum().hasContentFile()) @@ -1251,7 +1238,7 @@ namespace MWWorld return newPtr; } - MWWorld::Ptr World::moveObjectImp(const Ptr& ptr, float x, float y, float z, bool movePhysics, bool moveToActive) + MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z, bool movePhysics, bool moveToActive) { int cellX, cellY; positionToIndex(x, y, cellX, cellY); @@ -1266,21 +1253,14 @@ namespace MWWorld return moveObject(ptr, cell, x, y, z, movePhysics); } - MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z, bool moveToActive) - { - return moveObjectImp(ptr, x, y, z, true, moveToActive); - } - - MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, osg::Vec3f vec) + MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, osg::Vec3f vec, bool moveToActive) { auto* actor = mPhysics->getActor(ptr); if (actor) - { actor->adjustPosition(vec); - return ptr; - } + osg::Vec3f newpos = ptr.getRefData().getPosition().asVec3() + vec; - return moveObject(ptr, newpos.x(), newpos.y(), newpos.z()); + return moveObject(ptr, newpos.x(), newpos.y(), newpos.z(), false, moveToActive && ptr != getPlayerPtr()); } void World::scaleObject (const Ptr& ptr, float scale) @@ -1546,7 +1526,7 @@ namespace MWWorld auto* physactor = mPhysics->getActor(actor); assert(physactor); const auto position = physactor->getSimulationPosition(); - moveObjectImp(actor, position.x(), position.y(), position.z(), false); + moveObject(actor, position.x(), position.y(), position.z(), false, false); } } @@ -1556,7 +1536,7 @@ namespace MWWorld auto* physactor = mPhysics->getActor(*player); assert(physactor); const auto position = physactor->getSimulationPosition(); - moveObjectImp(*player, position.x(), position.y(), position.z(), false); + moveObject(*player, position.x(), position.y(), position.z(), false, false); } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 929e035e9e..33b23e0653 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -138,9 +138,6 @@ namespace MWWorld void rotateObjectImp (const Ptr& ptr, const osg::Vec3f& rot, MWBase::RotationFlags flags); - Ptr moveObjectImp (const Ptr& ptr, float x, float y, float z, bool movePhysics=true, bool moveToActive=false); - ///< @return an updated Ptr in case the Ptr's cell changes - Ptr copyObjectToCell(const ConstPtr &ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos); void updateSoundListener(); @@ -376,13 +373,13 @@ namespace MWWorld void undeleteObject (const Ptr& ptr) override; - MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z, bool moveToActive=false) override; + MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z, bool movePhysics=true, bool moveToActive=false) override; ///< @return an updated Ptr in case the Ptr's cell changes MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true) override; ///< @return an updated Ptr - MWWorld::Ptr moveObjectBy(const Ptr& ptr, osg::Vec3f vec) override; + MWWorld::Ptr moveObjectBy(const Ptr& ptr, osg::Vec3f vec, bool moveToActive) override; ///< @return an updated Ptr void scaleObject (const Ptr& ptr, float scale) override;