From c495c21923dcc08a8360555b274eebb1e93347ba Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 27 Aug 2020 11:48:59 +0000 Subject: [PATCH] Merge branch 'movement_refactoring' into 'master' Refactoring related to "smooth movement" See merge request OpenMW/openmw!285 (cherry picked from commit 6eaf0a389d5aed3b74ab1a7cf89574612f964bdf) e847b4c8 Split getSpeed() to getMaxSpeed() and getCurrentSpeed() a96c46bc Refactor calculation of movement.mSpeedFactor 03ee9090 Use getMaxSpeed instead of getCurrentSpeed where it makes sense. a178af5c Create helper functions `normalizeAngle` and `rotateVec2f` --- apps/openmw/mwclass/actor.cpp | 9 ++++++ apps/openmw/mwclass/actor.hpp | 5 +++- apps/openmw/mwclass/creature.cpp | 7 +---- apps/openmw/mwclass/creature.hpp | 2 +- apps/openmw/mwclass/npc.cpp | 7 +---- apps/openmw/mwclass/npc.hpp | 4 +-- apps/openmw/mwmechanics/actors.cpp | 12 ++++---- apps/openmw/mwmechanics/aipackage.cpp | 6 ++-- apps/openmw/mwmechanics/character.cpp | 41 +++++++++------------------ apps/openmw/mwmechanics/movement.hpp | 6 ++++ apps/openmw/mwmechanics/obstacle.cpp | 2 +- apps/openmw/mwmechanics/steering.cpp | 2 +- apps/openmw/mwrender/camera.cpp | 24 ++++------------ apps/openmw/mwworld/class.cpp | 7 ++++- apps/openmw/mwworld/class.hpp | 19 +++++++------ components/misc/mathutil.hpp | 27 ++++++++++++++++++ 16 files changed, 97 insertions(+), 83 deletions(-) create mode 100644 components/misc/mathutil.hpp diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp index 4eb9728a1a..61d6e73474 100644 --- a/apps/openmw/mwclass/actor.cpp +++ b/apps/openmw/mwclass/actor.cpp @@ -91,4 +91,13 @@ namespace MWClass { return true; } + + float Actor::getCurrentSpeed(const MWWorld::Ptr& ptr) const + { + const MWMechanics::Movement& movementSettings = ptr.getClass().getMovementSettings(ptr); + float moveSpeed = this->getMaxSpeed(ptr) * movementSettings.mSpeedFactor; + if (movementSettings.mIsStrafing) + moveSpeed *= 0.75f; + return moveSpeed; + } } diff --git a/apps/openmw/mwclass/actor.hpp b/apps/openmw/mwclass/actor.hpp index 7cdee8061f..6ccd552f94 100644 --- a/apps/openmw/mwclass/actor.hpp +++ b/apps/openmw/mwclass/actor.hpp @@ -31,7 +31,7 @@ namespace MWClass virtual void block(const MWWorld::Ptr &ptr) const; virtual osg::Vec3f getRotationVector(const MWWorld::Ptr& ptr) const; - ///< Return desired rotations, as euler angles. + ///< Return desired rotations, as euler angles. Sets getMovementSettings(ptr).mRotation to zero. virtual float getEncumbrance(const MWWorld::Ptr& ptr) const; ///< Returns total weight of objects inside this object (including modifications from magic @@ -42,6 +42,9 @@ namespace MWClass virtual bool isActor() const; + /// Return current movement speed. + virtual float getCurrentSpeed(const MWWorld::Ptr& ptr) const; + // not implemented Actor(const Actor&); Actor& operator= (const Actor&); diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 15e0fa6abb..5c5524a122 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -500,7 +500,7 @@ namespace MWClass registerClass (typeid (ESM::Creature).name(), instance); } - float Creature::getSpeed(const MWWorld::Ptr &ptr) const + float Creature::getMaxSpeed(const MWWorld::Ptr &ptr) const { const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); @@ -532,11 +532,6 @@ namespace MWClass else moveSpeed = getWalkSpeed(ptr); - const MWMechanics::Movement& movementSettings = ptr.getClass().getMovementSettings(ptr); - if (movementSettings.mIsStrafing) - moveSpeed *= 0.75f; - moveSpeed *= movementSettings.mSpeedFactor; - return moveSpeed; } diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 2d7aa5a19e..ca991052b3 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -94,7 +94,7 @@ namespace MWClass virtual MWMechanics::Movement& getMovementSettings (const MWWorld::Ptr& ptr) const; ///< Return desired movement. - float getSpeed (const MWWorld::Ptr& ptr) const; + float getMaxSpeed (const MWWorld::Ptr& ptr) const; static void registerSelf(); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 0c00d3bd12..b89d79fc9b 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -936,7 +936,7 @@ namespace MWClass return ref->mBase->mScript; } - float Npc::getSpeed(const MWWorld::Ptr& ptr) const + float Npc::getMaxSpeed(const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead()) @@ -979,11 +979,6 @@ namespace MWClass if(npcdata->mNpcStats.isWerewolf() && running && npcdata->mNpcStats.getDrawState() == MWMechanics::DrawState_Nothing) moveSpeed *= gmst.fWereWolfRunMult->mValue.getFloat(); - const MWMechanics::Movement& movementSettings = ptr.getClass().getMovementSettings(ptr); - if (movementSettings.mIsStrafing) - moveSpeed *= 0.75f; - moveSpeed *= movementSettings.mSpeedFactor; - return moveSpeed; } diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index d52afcd82a..1ed4e8caea 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -84,8 +84,8 @@ namespace MWClass virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual float getSpeed (const MWWorld::Ptr& ptr) const; - ///< Return movement speed. + virtual float getMaxSpeed (const MWWorld::Ptr& ptr) const; + ///< Return maximal movement speed. virtual float getJump(const MWWorld::Ptr &ptr) const; ///< Return jump velocity (not accounting for movement) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index b91b01e4a3..14a2e17c91 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -472,9 +472,6 @@ namespace MWMechanics void Actors::updateMovementSpeed(const MWWorld::Ptr& actor) { - float previousSpeedFactor = actor.getClass().getMovementSettings(actor).mSpeedFactor; - float newSpeedFactor = 1.f; - CreatureStats &stats = actor.getClass().getCreatureStats(actor); MWMechanics::AiSequence& seq = stats.getAiSequence(); @@ -484,10 +481,13 @@ namespace MWMechanics osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3(); float distance = (targetPos - actorPos).length(); if (distance < DECELERATE_DISTANCE) - newSpeedFactor = std::max(0.7f, 0.1f * previousSpeedFactor * (distance/64.f + 2.f)); + { + float speedCoef = std::max(0.7f, 0.1f * (distance/64.f + 2.f)); + auto& movement = actor.getClass().getMovementSettings(actor); + movement.mPosition[0] *= speedCoef; + movement.mPosition[1] *= speedCoef; + } } - - actor.getClass().getMovementSettings(actor).mSpeedFactor = newSpeedFactor; } void Actors::updateGreetingState(const MWWorld::Ptr& actor, Actor& actorState, bool turnOnly) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index f7c07bde00..53e366579c 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -144,7 +144,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& mTimer = 0; } - const float actorTolerance = 2 * actor.getClass().getSpeed(actor) * duration + const float actorTolerance = 2 * actor.getClass().getMaxSpeed(actor) * duration + 1.2 * std::max(halfExtents.x(), halfExtents.y()); const float pointTolerance = std::max(MIN_TOLERANCE, actorTolerance); @@ -300,7 +300,7 @@ bool MWMechanics::AiPackage::checkWayIsClearForActor(const osg::Vec3f& startPoin if (canActorMoveByZAxis(actor)) return true; - const float actorSpeed = actor.getClass().getSpeed(actor); + const float actorSpeed = actor.getClass().getMaxSpeed(actor); const float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / getAngularVelocity(actorSpeed) * 2; // *2 - for reliability const float distToTarget = osg::Vec2f(endPoint.x(), endPoint.y()).length(); @@ -360,7 +360,7 @@ bool MWMechanics::AiPackage::isNearInactiveCell(osg::Vec3f position) bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const osg::Vec3f& dest) { // get actor's shortest radius for moving in circle - float speed = actor.getClass().getSpeed(actor); + float speed = actor.getClass().getMaxSpeed(actor); speed += speed * 0.1f; // 10% real speed inaccuracy float radius = speed / getAngularVelocity(speed); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 2ff8241f43..5d102f69ea 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -21,6 +21,7 @@ #include +#include #include #include @@ -51,15 +52,6 @@ namespace { -// Wraps a value to (-PI, PI] -void wrap(float& rad) -{ - if (rad>0) - rad = std::fmod(rad+osg::PI, 2.0f*osg::PI)-osg::PI; - else - rad = std::fmod(rad-osg::PI, 2.0f*osg::PI)+osg::PI; -} - std::string getBestAttack (const ESM::Weapon* weapon) { int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; @@ -1958,23 +1950,19 @@ void CharacterController::update(float duration, bool animationOnly) osg::Vec3f rot = cls.getRotationVector(mPtr); osg::Vec3f vec(movementSettings.asVec3()); - vec.normalize(); - float analogueMult = 1.0f; if (isPlayer) { // TODO: Move this code to mwinput. // Joystick analogue movement. - float xAxis = std::abs(movementSettings.mPosition[0]); - float yAxis = std::abs(movementSettings.mPosition[1]); - analogueMult = std::max(xAxis, yAxis); + movementSettings.mSpeedFactor = std::max(std::abs(vec.x()), std::abs(vec.y())); // Due to the half way split between walking/running, we multiply speed by 2 while walking, unless a keyboard was used. - if(!isrunning && !sneak && !flying && analogueMult <= 0.5f) - analogueMult *= 2.f; - - movementSettings.mSpeedFactor = analogueMult; - } + if(!isrunning && !sneak && !flying && movementSettings.mSpeedFactor <= 0.5f) + movementSettings.mSpeedFactor *= 2.f; + } else + movementSettings.mSpeedFactor = std::min(vec.length(), 1.f); + vec.normalize(); float effectiveRotation = rot.z(); static const bool turnToMovementDirection = Settings::Manager::getBool("turn to movement direction", "Game"); @@ -2007,7 +1995,7 @@ void CharacterController::update(float duration, bool animationOnly) else mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 4); - speed = cls.getSpeed(mPtr); + speed = cls.getCurrentSpeed(mPtr); vec.x() *= speed; vec.y() *= speed; @@ -2077,7 +2065,7 @@ void CharacterController::update(float duration, bool animationOnly) } } fatigueLoss *= duration; - fatigueLoss *= analogueMult; + fatigueLoss *= movementSettings.mSpeedFactor; DynamicStat fatigue = cls.getCreatureStats(mPtr).getFatigue(); if (!godmode) @@ -2908,13 +2896,10 @@ void CharacterController::updateHeadTracking(float duration) zAngleRadians = std::atan2(direction.x(), direction.y()) - std::atan2(actorDirection.x(), actorDirection.y()); xAngleRadians = -std::asin(direction.z()); - wrap(zAngleRadians); - wrap(xAngleRadians); - - xAngleRadians = std::min(xAngleRadians, osg::DegreesToRadians(40.f)); - xAngleRadians = std::max(xAngleRadians, osg::DegreesToRadians(-40.f)); - zAngleRadians = std::min(zAngleRadians, osg::DegreesToRadians(30.f)); - zAngleRadians = std::max(zAngleRadians, osg::DegreesToRadians(-30.f)); + const double xLimit = osg::DegreesToRadians(40.0); + const double zLimit = osg::DegreesToRadians(30.0); + zAngleRadians = osg::clampBetween(Misc::normalizeAngle(zAngleRadians), -xLimit, xLimit); + xAngleRadians = osg::clampBetween(Misc::normalizeAngle(xAngleRadians), -zLimit, zLimit); } float factor = duration*5; diff --git a/apps/openmw/mwmechanics/movement.hpp b/apps/openmw/mwmechanics/movement.hpp index 86b970e602..57e106cdec 100644 --- a/apps/openmw/mwmechanics/movement.hpp +++ b/apps/openmw/mwmechanics/movement.hpp @@ -8,8 +8,14 @@ namespace MWMechanics /// Desired movement for an actor struct Movement { + // Desired movement. Direction is relative to the current orientation. + // Length of the vector controls desired speed. 0 - stay, 0.5 - half-speed, 1.0 - max speed. float mPosition[3]; + // Desired rotation delta (euler angles). float mRotation[3]; + + // Controlled by CharacterController, should not be changed from other places. + // These fields can not be private fields in CharacterController, because Actor::getCurrentSpeed uses it. float mSpeedFactor; bool mIsStrafing; diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 715dfecd24..88325ee7c7 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -122,7 +122,7 @@ namespace MWMechanics if (mWalkState != WalkState::Evade) { - const float distSameSpot = DIST_SAME_SPOT * actor.getClass().getSpeed(actor) * duration; + const float distSameSpot = DIST_SAME_SPOT * actor.getClass().getCurrentSpeed(actor) * duration; const float prevDistance = (destination - mPrev).length(); const float currentDistance = (destination - position).length(); const float movedDistance = prevDistance - currentDistance; diff --git a/apps/openmw/mwmechanics/steering.cpp b/apps/openmw/mwmechanics/steering.cpp index b08a90220e..d442085ea2 100644 --- a/apps/openmw/mwmechanics/steering.cpp +++ b/apps/openmw/mwmechanics/steering.cpp @@ -32,7 +32,7 @@ bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, f if (absDiff < epsilonRadians) return true; - float limit = getAngularVelocity(actor.getClass().getSpeed(actor)) * MWBase::Environment::get().getFrameDuration(); + float limit = getAngularVelocity(actor.getClass().getMaxSpeed(actor)) * MWBase::Environment::get().getFrameDuration(); if (absDiff > limit) diff = osg::sign(diff) * limit; diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 40cc9895df..0d6ed262b5 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -200,7 +201,7 @@ namespace MWRender updateFocalPointOffset(duration); updatePosition(); - float speed = mTrackingPtr.getClass().getSpeed(mTrackingPtr); + float speed = mTrackingPtr.getClass().getCurrentSpeed(mTrackingPtr); speed /= (1.f + speed / 500.f); float maxDelta = 300.f * duration; mSmoothedSpeed += osg::clampBetween(speed - mSmoothedSpeed, -maxDelta, maxDelta); @@ -249,7 +250,7 @@ namespace MWRender { if (!mStandingPreviewAllowed) return; - float speed = mTrackingPtr.getClass().getSpeed(mTrackingPtr); + float speed = mTrackingPtr.getClass().getCurrentSpeed(mTrackingPtr); bool combat = mTrackingPtr.getClass().isActor() && mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).getDrawState() != MWMechanics::DrawState_Nothing; bool standingStill = speed == 0 && !combat && !mFirstPersonView; @@ -396,12 +397,7 @@ namespace MWRender void Camera::setYaw(float angle) { - if (angle > osg::PI) { - angle -= osg::PI*2; - } else if (angle < -osg::PI) { - angle += osg::PI*2; - } - mYaw = angle; + mYaw = Misc::normalizeAngle(angle); } void Camera::setPitch(float angle) @@ -538,16 +534,8 @@ namespace MWRender return; } - mDeferredRotation.x() = -ptr.getRefData().getPosition().rot[0] - mPitch; - mDeferredRotation.z() = -ptr.getRefData().getPosition().rot[2] - mYaw; - if (mDeferredRotation.x() > osg::PI) - mDeferredRotation.x() -= 2 * osg::PI; - if (mDeferredRotation.x() < -osg::PI) - mDeferredRotation.x() += 2 * osg::PI; - if (mDeferredRotation.z() > osg::PI) - mDeferredRotation.z() -= 2 * osg::PI; - if (mDeferredRotation.z() < -osg::PI) - mDeferredRotation.z() += 2 * osg::PI; + mDeferredRotation.x() = Misc::normalizeAngle(-ptr.getRefData().getPosition().rot[0] - mPitch); + mDeferredRotation.z() = Misc::normalizeAngle(-ptr.getRefData().getPosition().rot[2] - mYaw); } } diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index ad8766d06c..950c8a6d49 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -159,7 +159,12 @@ namespace MWWorld return ""; } - float Class::getSpeed (const Ptr& ptr) const + float Class::getMaxSpeed (const Ptr& ptr) const + { + return 0; + } + + float Class::getCurrentSpeed (const Ptr& ptr) const { return 0; } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index e82712220d..445f2e9862 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -172,8 +172,15 @@ namespace MWWorld ///< Return name of the script attached to ptr (default implementation: return an empty /// string). - virtual float getSpeed (const Ptr& ptr) const; - ///< Return movement speed. + virtual float getWalkSpeed(const Ptr& ptr) const; + virtual float getRunSpeed(const Ptr& ptr) const; + virtual float getSwimSpeed(const Ptr& ptr) const; + + /// Return maximal movement speed for the current state. + virtual float getMaxSpeed(const Ptr& ptr) const; + + /// Return current movement speed. + virtual float getCurrentSpeed(const Ptr& ptr) const; virtual float getJump(const MWWorld::Ptr &ptr) const; ///< Return jump velocity (not accounting for movement) @@ -182,7 +189,7 @@ namespace MWWorld ///< Return desired movement. virtual osg::Vec3f getRotationVector (const Ptr& ptr) const; - ///< Return desired rotations, as euler angles. + ///< Return desired rotations, as euler angles. Sets getMovementSettings(ptr).mRotation to zero. virtual std::pair, bool> getEquipmentSlots (const ConstPtr& ptr) const; ///< \return first: Return IDs of the slot this object can be equipped in; second: can object @@ -364,12 +371,6 @@ namespace MWWorld virtual void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const; virtual void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const; - - virtual float getWalkSpeed(const Ptr& ptr) const; - - virtual float getRunSpeed(const Ptr& ptr) const; - - virtual float getSwimSpeed(const Ptr& ptr) const; }; } diff --git a/components/misc/mathutil.hpp b/components/misc/mathutil.hpp new file mode 100644 index 0000000000..2f7f446b56 --- /dev/null +++ b/components/misc/mathutil.hpp @@ -0,0 +1,27 @@ +#ifndef MISC_MATHUTIL_H +#define MISC_MATHUTIL_H + +#include +#include + +namespace Misc +{ + + /// Normalizes given angle to the range [-PI, PI]. E.g. PI*3/2 -> -PI/2. + inline double normalizeAngle(double angle) + { + double fullTurns = angle / (2 * osg::PI) + 0.5; + return (fullTurns - floor(fullTurns) - 0.5) * (2 * osg::PI); + } + + /// Rotates given 2d vector counterclockwise. Angle is in radians. + inline osg::Vec2f rotateVec2f(osg::Vec2f vec, float angle) + { + float s = std::sin(angle); + float c = std::cos(angle); + return osg::Vec2f(vec.x() * c + vec.y() * -s, vec.x() * s + vec.y() * c); + } + +} + +#endif