mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-30 16:15:31 +00:00
Smooth turning; smooth stopping; combat headtracking
This commit is contained in:
parent
acdf4cb5e3
commit
71ba94a89a
7 changed files with 82 additions and 21 deletions
|
@ -473,6 +473,10 @@ namespace MWMechanics
|
||||||
|
|
||||||
void Actors::updateMovementSpeed(const MWWorld::Ptr& actor)
|
void Actors::updateMovementSpeed(const MWWorld::Ptr& actor)
|
||||||
{
|
{
|
||||||
|
static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game");
|
||||||
|
if (smoothMovement)
|
||||||
|
return;
|
||||||
|
|
||||||
CreatureStats &stats = actor.getClass().getCreatureStats(actor);
|
CreatureStats &stats = actor.getClass().getCreatureStats(actor);
|
||||||
MWMechanics::AiSequence& seq = stats.getAiSequence();
|
MWMechanics::AiSequence& seq = stats.getAiSequence();
|
||||||
|
|
||||||
|
@ -481,9 +485,10 @@ namespace MWMechanics
|
||||||
osg::Vec3f targetPos = seq.getActivePackage().getDestination();
|
osg::Vec3f targetPos = seq.getActivePackage().getDestination();
|
||||||
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)
|
||||||
{
|
{
|
||||||
float speedCoef = std::max(0.7f, 0.1f * (distance/64.f + 2.f));
|
float speedCoef = std::max(0.7f, 0.2f + 0.8f * distance / DECELERATE_DISTANCE);
|
||||||
auto& movement = actor.getClass().getMovementSettings(actor);
|
auto& movement = actor.getClass().getMovementSettings(actor);
|
||||||
movement.mPosition[0] *= speedCoef;
|
movement.mPosition[0] *= speedCoef;
|
||||||
movement.mPosition[1] *= speedCoef;
|
movement.mPosition[1] *= speedCoef;
|
||||||
|
@ -1769,14 +1774,12 @@ namespace MWMechanics
|
||||||
|
|
||||||
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
|
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
|
||||||
bool firstPersonPlayer = isPlayer && world->isFirstPerson();
|
bool firstPersonPlayer = isPlayer && world->isFirstPerson();
|
||||||
|
bool inCombatOrPursue = stats.getAiSequence().isInCombat() || stats.getAiSequence().hasPackage(AiPackageTypeId::Pursue);
|
||||||
|
|
||||||
// 1. Unconsious actor can not track target
|
// 1. Unconsious actor can not track target
|
||||||
// 2. Actors in combat and pursue mode do not bother to headtrack
|
// 2. Actors in combat and pursue mode do not bother to headtrack
|
||||||
// 3. Player character does not use headtracking in the 1st-person view
|
// 3. Player character does not use headtracking in the 1st-person view
|
||||||
if (!stats.getKnockedDown() &&
|
if (!stats.getKnockedDown() && !firstPersonPlayer && !inCombatOrPursue)
|
||||||
!stats.getAiSequence().isInCombat() &&
|
|
||||||
!stats.getAiSequence().hasPackage(AiPackageTypeId::Pursue) &&
|
|
||||||
!firstPersonPlayer)
|
|
||||||
{
|
{
|
||||||
for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it)
|
for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it)
|
||||||
{
|
{
|
||||||
|
@ -1786,6 +1789,17 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!stats.getKnockedDown() && !isPlayer && inCombatOrPursue)
|
||||||
|
{
|
||||||
|
// Actors in combat and pursue mode always look at their target.
|
||||||
|
for (const auto& package : stats.getAiSequence())
|
||||||
|
{
|
||||||
|
headTrackTarget = package->getTarget();
|
||||||
|
if (!headTrackTarget.isEmpty())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctrl->setHeadTrackTarget(headTrackTarget);
|
ctrl->setHeadTrackTarget(headTrackTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ bool MWMechanics::AiBreathe::execute (const MWWorld::Ptr& actor, CharacterContro
|
||||||
actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
|
actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
|
||||||
|
|
||||||
actorClass.getMovementSettings(actor).mPosition[1] = 1;
|
actorClass.getMovementSettings(actor).mPosition[1] = 1;
|
||||||
smoothTurn(actor, -180, 0);
|
smoothTurn(actor, -osg::PI / 2, 0);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include <components/esm/loadmgef.hpp>
|
#include <components/esm/loadmgef.hpp>
|
||||||
#include <components/detournavigator/navigator.hpp>
|
#include <components/detournavigator/navigator.hpp>
|
||||||
#include <components/misc/coordinateconverter.hpp>
|
#include <components/misc/coordinateconverter.hpp>
|
||||||
|
#include <components/settings/settings.hpp>
|
||||||
|
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
@ -87,6 +88,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
|
||||||
//... But AI processing distance may increase in the future.
|
//... But AI processing distance may increase in the future.
|
||||||
if (isNearInactiveCell(position))
|
if (isNearInactiveCell(position))
|
||||||
{
|
{
|
||||||
|
actor.getClass().getMovementSettings(actor).mPosition[0] = 0;
|
||||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
||||||
world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest);
|
world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest);
|
||||||
return false;
|
return false;
|
||||||
|
@ -169,12 +171,34 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
|
||||||
}
|
}
|
||||||
|
|
||||||
// turn to next path point by X,Z axes
|
// turn to next path point by X,Z axes
|
||||||
zTurn(actor, mPathFinder.getZAngleToNext(position.x(), position.y()));
|
float zAngleToNext = mPathFinder.getZAngleToNext(position.x(), position.y());
|
||||||
|
zTurn(actor, zAngleToNext);
|
||||||
smoothTurn(actor, mPathFinder.getXAngleToNext(position.x(), position.y(), position.z()), 0);
|
smoothTurn(actor, mPathFinder.getXAngleToNext(position.x(), position.y(), position.z()), 0);
|
||||||
|
|
||||||
const auto destination = mPathFinder.getPath().empty() ? dest : mPathFinder.getPath().front();
|
const auto destination = mPathFinder.getPath().empty() ? dest : mPathFinder.getPath().front();
|
||||||
mObstacleCheck.update(actor, destination, duration);
|
mObstacleCheck.update(actor, destination, duration);
|
||||||
|
|
||||||
|
static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game");
|
||||||
|
if (smoothMovement)
|
||||||
|
{
|
||||||
|
const float smoothTurnReservedDist = 150;
|
||||||
|
auto& movement = actor.getClass().getMovementSettings(actor);
|
||||||
|
float distToNextSqr = osg::Vec2f(destination.x() - position.x(), destination.y() - position.y()).length2();
|
||||||
|
float diffAngle = zAngleToNext - actor.getRefData().getPosition().rot[2];
|
||||||
|
if (std::cos(diffAngle) < -0.1)
|
||||||
|
movement.mPosition[0] = movement.mPosition[1] = 0;
|
||||||
|
else if (distToNextSqr > smoothTurnReservedDist * smoothTurnReservedDist)
|
||||||
|
{ // Go forward (and slowly turn towards the next path point)
|
||||||
|
movement.mPosition[0] = 0;
|
||||||
|
movement.mPosition[1] = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // Next path point is near, so use diagonal movement to follow the path precisely.
|
||||||
|
movement.mPosition[0] = std::sin(diffAngle);
|
||||||
|
movement.mPosition[1] = std::max(std::cos(diffAngle), 0.f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// handle obstacles on the way
|
// handle obstacles on the way
|
||||||
evadeObstacles(actor);
|
evadeObstacles(actor);
|
||||||
|
|
||||||
|
|
|
@ -96,6 +96,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
void stopMovement(const MWWorld::Ptr& actor)
|
void stopMovement(const MWWorld::Ptr& actor)
|
||||||
{
|
{
|
||||||
|
actor.getClass().getMovementSettings(actor).mPosition[0] = 0;
|
||||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,6 +88,24 @@ namespace
|
||||||
const auto halfExtents = world->getHalfExtents(actor);
|
const auto halfExtents = world->getHalfExtents(actor);
|
||||||
return 2.0 * halfExtents.z();
|
return 2.0 * halfExtents.z();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns true if turn in `p2` is less than 10 degrees and all the 3 points are almost on one line.
|
||||||
|
bool isAlmostStraight(const osg::Vec3f& p1, const osg::Vec3f& p2, const osg::Vec3f& p3, float pointTolerance) {
|
||||||
|
osg::Vec3f v1 = p1 - p2;
|
||||||
|
osg::Vec3f v3 = p3 - p2;
|
||||||
|
v1.z() = v3.z() = 0;
|
||||||
|
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 distance from p2 to the line (p1, p3) is less or equal than `pointTolerance`.
|
||||||
|
bool checkDist = std::abs(crossProduct) <= pointTolerance * (p3 - p1).length() * 2;
|
||||||
|
|
||||||
|
return checkAngle && checkDist;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
|
@ -286,6 +304,11 @@ namespace MWMechanics
|
||||||
while (mPath.size() > 1 && sqrDistanceIgnoreZ(mPath.front(), position) < pointTolerance * pointTolerance)
|
while (mPath.size() > 1 && sqrDistanceIgnoreZ(mPath.front(), position) < pointTolerance * pointTolerance)
|
||||||
mPath.pop_front();
|
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 (mPath.size() == 1 && sqrDistanceIgnoreZ(mPath.front(), position) < destinationTolerance * destinationTolerance)
|
if (mPath.size() == 1 && sqrDistanceIgnoreZ(mPath.front(), position) < destinationTolerance * destinationTolerance)
|
||||||
mPath.pop_front();
|
mPath.pop_front();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
#include "steering.hpp"
|
#include "steering.hpp"
|
||||||
|
|
||||||
|
#include <components/misc/mathutil.hpp>
|
||||||
|
#include <components/settings/settings.hpp>
|
||||||
|
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
#include "../mwworld/ptr.hpp"
|
#include "../mwworld/ptr.hpp"
|
||||||
|
|
||||||
|
@ -12,19 +15,8 @@ namespace MWMechanics
|
||||||
|
|
||||||
bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, float epsilonRadians)
|
bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, float epsilonRadians)
|
||||||
{
|
{
|
||||||
float currentAngle (actor.getRefData().getPosition().rot[axis]);
|
MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor);
|
||||||
float diff (targetAngleRadians - currentAngle);
|
float diff = Misc::normalizeAngle(targetAngleRadians - actor.getRefData().getPosition().rot[axis]);
|
||||||
if (std::abs(diff) >= osg::DegreesToRadians(180.f))
|
|
||||||
{
|
|
||||||
if (diff >= 0)
|
|
||||||
{
|
|
||||||
diff = diff - osg::DegreesToRadians(360.f);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
diff = osg::DegreesToRadians(360.f) + diff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
float absDiff = std::abs(diff);
|
float absDiff = std::abs(diff);
|
||||||
|
|
||||||
// The turning animation actually moves you slightly, so the angle will be wrong again.
|
// The turning animation actually moves you slightly, so the angle will be wrong again.
|
||||||
|
@ -33,10 +25,14 @@ bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, f
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
float limit = getAngularVelocity(actor.getClass().getMaxSpeed(actor)) * MWBase::Environment::get().getFrameDuration();
|
float limit = getAngularVelocity(actor.getClass().getMaxSpeed(actor)) * MWBase::Environment::get().getFrameDuration();
|
||||||
|
static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game");
|
||||||
|
if (smoothMovement)
|
||||||
|
limit *= std::min(absDiff / osg::PI + 0.1, 0.5);
|
||||||
|
|
||||||
if (absDiff > limit)
|
if (absDiff > limit)
|
||||||
diff = osg::sign(diff) * limit;
|
diff = osg::sign(diff) * limit;
|
||||||
|
|
||||||
actor.getClass().getMovementSettings(actor).mRotation[axis] = diff;
|
movement.mRotation[axis] = diff;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -325,6 +325,9 @@ uncapped damage fatigue = false
|
||||||
# Turn lower body to movement direction. 'true' makes diagonal movement more realistic.
|
# Turn lower body to movement direction. 'true' makes diagonal movement more realistic.
|
||||||
turn to movement direction = false
|
turn to movement direction = false
|
||||||
|
|
||||||
|
# Makes all movements of NPCs and player more smooth.
|
||||||
|
smooth movement = false
|
||||||
|
|
||||||
# Makes player swim a bit upward from the line of sight.
|
# Makes player swim a bit upward from the line of sight.
|
||||||
swim upward correction = false
|
swim upward correction = false
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue