1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-02-09 18:40:14 +00:00

Update logic of "NPCs avoid collisions"

This commit is contained in:
Petr Mikheev 2020-10-03 22:55:21 +02:00
parent 25b966d58d
commit 2835ea1e21

View File

@ -1668,8 +1668,9 @@ namespace MWMechanics
void Actors::predictAndAvoidCollisions() void Actors::predictAndAvoidCollisions()
{ {
const float minGap = 10.f; const float minGap = 10.f;
const float maxDistToCheck = 100.f; const float maxDistForPartialAvoiding = 200.f;
const float maxTimeToCheck = 1.f; const float maxDistForStrictAvoiding = 100.f;
const float maxTimeToCheck = 2.0f;
static const bool giveWayWhenIdle = Settings::Manager::getBool("NPCs give way", "Game"); static const bool giveWayWhenIdle = Settings::Manager::getBool("NPCs give way", "Game");
MWWorld::Ptr player = getPlayer(); MWWorld::Ptr player = getPlayer();
@ -1680,9 +1681,15 @@ namespace MWMechanics
if (ptr == player) if (ptr == player)
continue; // Don't interfere with player controls. continue; // Don't interfere with player controls.
float maxSpeed = ptr.getClass().getMaxSpeed(ptr);
if (maxSpeed == 0.0)
continue; // Can't move, so there is no sense to predict collisions.
Movement& movement = ptr.getClass().getMovementSettings(ptr); Movement& movement = ptr.getClass().getMovementSettings(ptr);
osg::Vec2f origMovement(movement.mPosition[0], movement.mPosition[1]); osg::Vec2f origMovement(movement.mPosition[0], movement.mPosition[1]);
bool isMoving = origMovement.length2() > 0.01; bool isMoving = origMovement.length2() > 0.01;
if (movement.mPosition[1] < 0)
continue; // Actors can not see others when move backward.
// Moving NPCs always should avoid collisions. // Moving NPCs always should avoid collisions.
// Standing NPCs give way to moving ones if they are not in combat (or pursue) mode and either // Standing NPCs give way to moving ones if they are not in combat (or pursue) mode and either
@ -1710,11 +1717,11 @@ namespace MWMechanics
if (!shouldAvoidCollision) if (!shouldAvoidCollision)
continue; continue;
float maxSpeed = ptr.getClass().getMaxSpeed(ptr);
osg::Vec2f baseSpeed = origMovement * maxSpeed; osg::Vec2f baseSpeed = origMovement * maxSpeed;
osg::Vec3f basePos = ptr.getRefData().getPosition().asVec3(); osg::Vec3f basePos = ptr.getRefData().getPosition().asVec3();
float baseRotZ = ptr.getRefData().getPosition().rot[2]; float baseRotZ = ptr.getRefData().getPosition().rot[2];
osg::Vec3f halfExtents = world->getHalfExtents(ptr); osg::Vec3f halfExtents = world->getHalfExtents(ptr);
float maxDistToCheck = isMoving ? maxDistForPartialAvoiding : maxDistForStrictAvoiding;
float timeToCollision = maxTimeToCheck; float timeToCollision = maxTimeToCheck;
osg::Vec2f movementCorrection(0, 0); osg::Vec2f movementCorrection(0, 0);
@ -1730,9 +1737,10 @@ namespace MWMechanics
osg::Vec3f otherHalfExtents = world->getHalfExtents(otherPtr); osg::Vec3f otherHalfExtents = world->getHalfExtents(otherPtr);
osg::Vec3f deltaPos = otherPtr.getRefData().getPosition().asVec3() - basePos; osg::Vec3f deltaPos = otherPtr.getRefData().getPosition().asVec3() - basePos;
osg::Vec2f relPos = Misc::rotateVec2f(osg::Vec2f(deltaPos.x(), deltaPos.y()), baseRotZ); osg::Vec2f relPos = Misc::rotateVec2f(osg::Vec2f(deltaPos.x(), deltaPos.y()), baseRotZ);
float dist = deltaPos.length();
// Ignore actors which are not close enough or come from behind. // Ignore actors which are not close enough or come from behind.
if (deltaPos.length2() > maxDistToCheck * maxDistToCheck || relPos.y() < 0) if (dist > maxDistToCheck || relPos.y() < 0)
continue; continue;
// Don't check for a collision if vertical distance is greater then the actor's height. // Don't check for a collision if vertical distance is greater then the actor's height.
@ -1767,16 +1775,20 @@ namespace MWMechanics
timeToCollision = t; timeToCollision = t;
angleToApproachingActor = std::atan2(deltaPos.x(), deltaPos.y()); angleToApproachingActor = std::atan2(deltaPos.x(), deltaPos.y());
osg::Vec2f posAtT = relPos + relSpeed * t; osg::Vec2f posAtT = relPos + relSpeed * t;
float coef = (posAtT.x() * relSpeed.x() + posAtT.y() * relSpeed.y()) / (collisionDist * maxSpeed); float coef = (posAtT.x() * relSpeed.x() + posAtT.y() * relSpeed.y()) / (collisionDist * collisionDist * maxSpeed);
coef *= osg::clampBetween((maxDistForPartialAvoiding - dist) / (maxDistForPartialAvoiding - maxDistForStrictAvoiding), 0.f, 1.f);
movementCorrection = posAtT * coef; movementCorrection = posAtT * coef;
// Step to the side rather than backward. Otherwise player will be able to push the NPC far away from it's original location. if (otherPtr.getClass().getCreatureStats(otherPtr).isDead())
movementCorrection.y() = std::max(0.f, movementCorrection.y()); // In case of dead body still try to go around (it looks natural), but reduce the correction twice.
movementCorrection.y() *= 0.5f;
} }
if (timeToCollision < maxTimeToCheck) if (timeToCollision < maxTimeToCheck)
{ {
// Try to evade the nearest collision. // Try to evade the nearest collision.
osg::Vec2f newMovement = origMovement + movementCorrection; osg::Vec2f newMovement = origMovement + movementCorrection;
// Step to the side rather than backward. Otherwise player will be able to push the NPC far away from it's original location.
newMovement.y() = std::max(newMovement.y(), 0.f);
if (isMoving) if (isMoving)
{ // Keep the original speed. { // Keep the original speed.
newMovement.normalize(); newMovement.normalize();