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`
pull/593/head
psi29a 4 years ago
parent 51bec5948f
commit f90a049702

@ -91,4 +91,13 @@ namespace MWClass
{ {
return true; 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;
}
} }

@ -31,7 +31,7 @@ namespace MWClass
virtual void block(const MWWorld::Ptr &ptr) const; virtual void block(const MWWorld::Ptr &ptr) const;
virtual osg::Vec3f getRotationVector(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; virtual float getEncumbrance(const MWWorld::Ptr& ptr) const;
///< Returns total weight of objects inside this object (including modifications from magic ///< Returns total weight of objects inside this object (including modifications from magic
@ -42,6 +42,9 @@ namespace MWClass
virtual bool isActor() const; virtual bool isActor() const;
/// Return current movement speed.
virtual float getCurrentSpeed(const MWWorld::Ptr& ptr) const;
// not implemented // not implemented
Actor(const Actor&); Actor(const Actor&);
Actor& operator= (const Actor&); Actor& operator= (const Actor&);

@ -500,7 +500,7 @@ namespace MWClass
registerClass (typeid (ESM::Creature).name(), instance); 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); const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
@ -532,11 +532,6 @@ namespace MWClass
else else
moveSpeed = getWalkSpeed(ptr); moveSpeed = getWalkSpeed(ptr);
const MWMechanics::Movement& movementSettings = ptr.getClass().getMovementSettings(ptr);
if (movementSettings.mIsStrafing)
moveSpeed *= 0.75f;
moveSpeed *= movementSettings.mSpeedFactor;
return moveSpeed; return moveSpeed;
} }

@ -94,7 +94,7 @@ namespace MWClass
virtual MWMechanics::Movement& getMovementSettings (const MWWorld::Ptr& ptr) const; virtual MWMechanics::Movement& getMovementSettings (const MWWorld::Ptr& ptr) const;
///< Return desired movement. ///< Return desired movement.
float getSpeed (const MWWorld::Ptr& ptr) const; float getMaxSpeed (const MWWorld::Ptr& ptr) const;
static void registerSelf(); static void registerSelf();

@ -936,7 +936,7 @@ namespace MWClass
return ref->mBase->mScript; 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); const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead()) if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead())
@ -979,11 +979,6 @@ namespace MWClass
if(npcdata->mNpcStats.isWerewolf() && running && npcdata->mNpcStats.getDrawState() == MWMechanics::DrawState_Nothing) if(npcdata->mNpcStats.isWerewolf() && running && npcdata->mNpcStats.getDrawState() == MWMechanics::DrawState_Nothing)
moveSpeed *= gmst.fWereWolfRunMult->mValue.getFloat(); moveSpeed *= gmst.fWereWolfRunMult->mValue.getFloat();
const MWMechanics::Movement& movementSettings = ptr.getClass().getMovementSettings(ptr);
if (movementSettings.mIsStrafing)
moveSpeed *= 0.75f;
moveSpeed *= movementSettings.mSpeedFactor;
return moveSpeed; return moveSpeed;
} }

@ -84,8 +84,8 @@ namespace MWClass
virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; virtual std::string getScript (const MWWorld::ConstPtr& ptr) const;
///< Return name of the script attached to ptr ///< Return name of the script attached to ptr
virtual float getSpeed (const MWWorld::Ptr& ptr) const; virtual float getMaxSpeed (const MWWorld::Ptr& ptr) const;
///< Return movement speed. ///< Return maximal movement speed.
virtual float getJump(const MWWorld::Ptr &ptr) const; virtual float getJump(const MWWorld::Ptr &ptr) const;
///< Return jump velocity (not accounting for movement) ///< Return jump velocity (not accounting for movement)

@ -472,9 +472,6 @@ namespace MWMechanics
void Actors::updateMovementSpeed(const MWWorld::Ptr& actor) void Actors::updateMovementSpeed(const MWWorld::Ptr& actor)
{ {
float previousSpeedFactor = actor.getClass().getMovementSettings(actor).mSpeedFactor;
float newSpeedFactor = 1.f;
CreatureStats &stats = actor.getClass().getCreatureStats(actor); CreatureStats &stats = actor.getClass().getCreatureStats(actor);
MWMechanics::AiSequence& seq = stats.getAiSequence(); MWMechanics::AiSequence& seq = stats.getAiSequence();
@ -484,10 +481,13 @@ namespace MWMechanics
osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3(); osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
float distance = (targetPos - actorPos).length(); float distance = (targetPos - actorPos).length();
if (distance < DECELERATE_DISTANCE) 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) void Actors::updateGreetingState(const MWWorld::Ptr& actor, Actor& actorState, bool turnOnly)

@ -144,7 +144,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
mTimer = 0; 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()); + 1.2 * std::max(halfExtents.x(), halfExtents.y());
const float pointTolerance = std::max(MIN_TOLERANCE, actorTolerance); const float pointTolerance = std::max(MIN_TOLERANCE, actorTolerance);
@ -300,7 +300,7 @@ bool MWMechanics::AiPackage::checkWayIsClearForActor(const osg::Vec3f& startPoin
if (canActorMoveByZAxis(actor)) if (canActorMoveByZAxis(actor))
return true; 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 maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / getAngularVelocity(actorSpeed) * 2; // *2 - for reliability
const float distToTarget = osg::Vec2f(endPoint.x(), endPoint.y()).length(); 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) bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const osg::Vec3f& dest)
{ {
// get actor's shortest radius for moving in circle // 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 speed += speed * 0.1f; // 10% real speed inaccuracy
float radius = speed / getAngularVelocity(speed); float radius = speed / getAngularVelocity(speed);

@ -21,6 +21,7 @@
#include <iostream> #include <iostream>
#include <components/misc/mathutil.hpp>
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
@ -51,15 +52,6 @@
namespace 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) std::string getBestAttack (const ESM::Weapon* weapon)
{ {
int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; 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 rot = cls.getRotationVector(mPtr);
osg::Vec3f vec(movementSettings.asVec3()); osg::Vec3f vec(movementSettings.asVec3());
vec.normalize();
float analogueMult = 1.0f;
if (isPlayer) if (isPlayer)
{ {
// TODO: Move this code to mwinput. // TODO: Move this code to mwinput.
// Joystick analogue movement. // Joystick analogue movement.
float xAxis = std::abs(movementSettings.mPosition[0]); movementSettings.mSpeedFactor = std::max(std::abs(vec.x()), std::abs(vec.y()));
float yAxis = std::abs(movementSettings.mPosition[1]);
analogueMult = std::max(xAxis, yAxis);
// Due to the half way split between walking/running, we multiply speed by 2 while walking, unless a keyboard was used. // 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) if(!isrunning && !sneak && !flying && movementSettings.mSpeedFactor <= 0.5f)
analogueMult *= 2.f; movementSettings.mSpeedFactor *= 2.f;
} else
movementSettings.mSpeedFactor = analogueMult; movementSettings.mSpeedFactor = std::min(vec.length(), 1.f);
} vec.normalize();
float effectiveRotation = rot.z(); float effectiveRotation = rot.z();
static const bool turnToMovementDirection = Settings::Manager::getBool("turn to movement direction", "Game"); static const bool turnToMovementDirection = Settings::Manager::getBool("turn to movement direction", "Game");
@ -2007,7 +1995,7 @@ void CharacterController::update(float duration, bool animationOnly)
else else
mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 4); mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 4);
speed = cls.getSpeed(mPtr); speed = cls.getCurrentSpeed(mPtr);
vec.x() *= speed; vec.x() *= speed;
vec.y() *= speed; vec.y() *= speed;
@ -2077,7 +2065,7 @@ void CharacterController::update(float duration, bool animationOnly)
} }
} }
fatigueLoss *= duration; fatigueLoss *= duration;
fatigueLoss *= analogueMult; fatigueLoss *= movementSettings.mSpeedFactor;
DynamicStat<float> fatigue = cls.getCreatureStats(mPtr).getFatigue(); DynamicStat<float> fatigue = cls.getCreatureStats(mPtr).getFatigue();
if (!godmode) if (!godmode)
@ -2908,13 +2896,10 @@ void CharacterController::updateHeadTracking(float duration)
zAngleRadians = std::atan2(direction.x(), direction.y()) - std::atan2(actorDirection.x(), actorDirection.y()); zAngleRadians = std::atan2(direction.x(), direction.y()) - std::atan2(actorDirection.x(), actorDirection.y());
xAngleRadians = -std::asin(direction.z()); xAngleRadians = -std::asin(direction.z());
wrap(zAngleRadians); const double xLimit = osg::DegreesToRadians(40.0);
wrap(xAngleRadians); const double zLimit = osg::DegreesToRadians(30.0);
zAngleRadians = osg::clampBetween(Misc::normalizeAngle(zAngleRadians), -xLimit, xLimit);
xAngleRadians = std::min(xAngleRadians, osg::DegreesToRadians(40.f)); xAngleRadians = osg::clampBetween(Misc::normalizeAngle(xAngleRadians), -zLimit, zLimit);
xAngleRadians = std::max(xAngleRadians, osg::DegreesToRadians(-40.f));
zAngleRadians = std::min(zAngleRadians, osg::DegreesToRadians(30.f));
zAngleRadians = std::max(zAngleRadians, osg::DegreesToRadians(-30.f));
} }
float factor = duration*5; float factor = duration*5;

@ -8,8 +8,14 @@ namespace MWMechanics
/// Desired movement for an actor /// Desired movement for an actor
struct Movement 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]; float mPosition[3];
// Desired rotation delta (euler angles).
float mRotation[3]; 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; float mSpeedFactor;
bool mIsStrafing; bool mIsStrafing;

@ -122,7 +122,7 @@ namespace MWMechanics
if (mWalkState != WalkState::Evade) 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 prevDistance = (destination - mPrev).length();
const float currentDistance = (destination - position).length(); const float currentDistance = (destination - position).length();
const float movedDistance = prevDistance - currentDistance; const float movedDistance = prevDistance - currentDistance;

@ -32,7 +32,7 @@ bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, f
if (absDiff < epsilonRadians) if (absDiff < epsilonRadians)
return true; 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) if (absDiff > limit)
diff = osg::sign(diff) * limit; diff = osg::sign(diff) * limit;

@ -2,6 +2,7 @@
#include <osg/Camera> #include <osg/Camera>
#include <components/misc/mathutil.hpp>
#include <components/sceneutil/positionattitudetransform.hpp> #include <components/sceneutil/positionattitudetransform.hpp>
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
@ -200,7 +201,7 @@ namespace MWRender
updateFocalPointOffset(duration); updateFocalPointOffset(duration);
updatePosition(); updatePosition();
float speed = mTrackingPtr.getClass().getSpeed(mTrackingPtr); float speed = mTrackingPtr.getClass().getCurrentSpeed(mTrackingPtr);
speed /= (1.f + speed / 500.f); speed /= (1.f + speed / 500.f);
float maxDelta = 300.f * duration; float maxDelta = 300.f * duration;
mSmoothedSpeed += osg::clampBetween(speed - mSmoothedSpeed, -maxDelta, maxDelta); mSmoothedSpeed += osg::clampBetween(speed - mSmoothedSpeed, -maxDelta, maxDelta);
@ -249,7 +250,7 @@ namespace MWRender
{ {
if (!mStandingPreviewAllowed) if (!mStandingPreviewAllowed)
return; return;
float speed = mTrackingPtr.getClass().getSpeed(mTrackingPtr); float speed = mTrackingPtr.getClass().getCurrentSpeed(mTrackingPtr);
bool combat = mTrackingPtr.getClass().isActor() && bool combat = mTrackingPtr.getClass().isActor() &&
mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).getDrawState() != MWMechanics::DrawState_Nothing; mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).getDrawState() != MWMechanics::DrawState_Nothing;
bool standingStill = speed == 0 && !combat && !mFirstPersonView; bool standingStill = speed == 0 && !combat && !mFirstPersonView;
@ -396,12 +397,7 @@ namespace MWRender
void Camera::setYaw(float angle) void Camera::setYaw(float angle)
{ {
if (angle > osg::PI) { mYaw = Misc::normalizeAngle(angle);
angle -= osg::PI*2;
} else if (angle < -osg::PI) {
angle += osg::PI*2;
}
mYaw = angle;
} }
void Camera::setPitch(float angle) void Camera::setPitch(float angle)
@ -538,16 +534,8 @@ namespace MWRender
return; return;
} }
mDeferredRotation.x() = -ptr.getRefData().getPosition().rot[0] - mPitch; mDeferredRotation.x() = Misc::normalizeAngle(-ptr.getRefData().getPosition().rot[0] - mPitch);
mDeferredRotation.z() = -ptr.getRefData().getPosition().rot[2] - mYaw; mDeferredRotation.z() = Misc::normalizeAngle(-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;
} }
} }

@ -159,7 +159,12 @@ namespace MWWorld
return ""; 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; return 0;
} }

@ -172,8 +172,15 @@ namespace MWWorld
///< Return name of the script attached to ptr (default implementation: return an empty ///< Return name of the script attached to ptr (default implementation: return an empty
/// string). /// string).
virtual float getSpeed (const Ptr& ptr) const; virtual float getWalkSpeed(const Ptr& ptr) const;
///< Return movement speed. 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; virtual float getJump(const MWWorld::Ptr &ptr) const;
///< Return jump velocity (not accounting for movement) ///< Return jump velocity (not accounting for movement)
@ -182,7 +189,7 @@ namespace MWWorld
///< Return desired movement. ///< Return desired movement.
virtual osg::Vec3f getRotationVector (const Ptr& ptr) const; 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<std::vector<int>, bool> getEquipmentSlots (const ConstPtr& ptr) const; virtual std::pair<std::vector<int>, bool> getEquipmentSlots (const ConstPtr& ptr) const;
///< \return first: Return IDs of the slot this object can be equipped in; second: can object ///< \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 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 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;
}; };
} }

@ -0,0 +1,27 @@
#ifndef MISC_MATHUTIL_H
#define MISC_MATHUTIL_H
#include <osg/Math>
#include <osg/Vec2f>
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
Loading…
Cancel
Save