cyphesis/navigation/Steering.cpp

365 lines
13 KiB
C++
Raw Permalink Normal View History

/*
Copyright (C) 2014 Erik Ogenvik
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "Steering.h"
#include "Awareness.h"
#include "rulesets/MemEntity.h"
#include "common/debug.h"
#include <wfmath/point.h>
#include <wfmath/vector.h>
#include <wfmath/rotbox.h>
#include <wfmath/segment.h>
#include <iostream>
static const bool debug_flag = true;
Steering::Steering(MemEntity& avatar) :
2018-02-09 12:50:01 +01:00
mAwareness(nullptr),
mAvatar(avatar),
mDestinationEntityId(-1),
mDestinationRadius(1.0),
mSteeringEnabled(false),
mUpdateNeeded(false),
mPadding(16),
mMaxSpeed(5),
mDesiredSpeed(0.5),
2018-02-09 12:50:01 +01:00
mExpectingServerMovement(false),
mPathResult(0),
mAvatarHorizRadius(0.4)
{
auto speedGroundProp = avatar.getPropertyType<double>("speed-ground");
if (speedGroundProp) {
mMaxSpeed = speedGroundProp->data();
}
}
void Steering::setAwareness(Awareness* awareness)
{
2018-02-09 12:50:01 +01:00
auto& bbox = mAvatar.m_location.bBox();
if (bbox.isValid()) {
WFMath::CoordType squareHorizRadius = std::max(square(bbox.lowCorner().x()) +
2018-02-09 12:50:01 +01:00
square(bbox.lowCorner().z()),
square(bbox.highCorner().x()) +
2018-02-09 12:50:01 +01:00
square(bbox.highCorner().z()));
mAvatarHorizRadius = std::sqrt(squareHorizRadius);
}
mAwareness = awareness;
mTileListenerConnection.disconnect();
if (mAwareness) {
mTileListenerConnection = mAwareness->EventTileUpdated.connect(sigc::mem_fun(*this, &Steering::Awareness_TileUpdated));
setAwarenessArea();
}
}
void Steering::setDestination(int entityId, const WFMath::Point<3>& entityRelativePosition, float radius, double currentServerTimestamp)
{
2016-03-08 23:28:05 +01:00
if (mAwareness) {
mDestinationEntityId = entityId;
mEntityRelativeDestination = entityRelativePosition;
2016-03-08 23:28:05 +01:00
auto currentAvatarPos = getCurrentAvatarPosition(currentServerTimestamp);
WFMath::Point<3> finalPosition = entityRelativePosition;
updateDestination(currentServerTimestamp, entityId, finalPosition);
float distanceAvatarDestination = WFMath::Distance(currentAvatarPos, finalPosition);
float finalRadius = radius;
//Check if the destination is too far away. If so we should adjust it closer, and increase the radius.
//This depends on the AI updating the destination at regular intervals.
if (distanceAvatarDestination > (mAwareness->getTileSizeInMeters() * 10)) {
WFMath::Vector<3> vector = finalPosition - currentAvatarPos;
finalPosition = currentAvatarPos + (vector.normalize() * mAwareness->getTileSizeInMeters() * 10);
finalRadius = (radius * 10.f);
}
//Only update if destination or radius has changed, or if the current tile of the avatar isn't known.
if (mViewDestination != finalPosition || mDestinationRadius != finalRadius
2018-02-09 12:50:01 +01:00
|| !mAwareness->isPositionAware(currentAvatarPos.x(), currentAvatarPos.z())) {
mViewDestination = finalPosition;
mDestinationRadius = finalRadius;
2016-03-08 23:28:05 +01:00
mUpdateNeeded = true;
setAwarenessArea();
mAvatarPositionLastUpdate = currentAvatarPos;
}
}
}
void Steering::setAwarenessArea()
{
if (mAwareness) {
if (mViewDestination.isValid()) {
2018-02-09 12:50:01 +01:00
WFMath::Point<2> destination2d(mViewDestination.x(), mViewDestination.z());
WFMath::Point<2> entityPosition2d(mAvatar.m_location.m_pos.x(), mAvatar.m_location.m_pos.z());
WFMath::Vector<2> direction(destination2d - entityPosition2d);
2018-02-09 12:50:01 +01:00
auto theta = std::atan2(direction.y(), direction.x()); // rotation about Y
WFMath::RotMatrix<2> rm;
rm.rotation(theta);
WFMath::Point<2> start = entityPosition2d;
start -= WFMath::Vector<2>(mPadding, mPadding);
WFMath::Vector<2> size(direction.mag() + (mPadding * 2), mPadding * 2);
WFMath::RotBox<2> area;
area.size() = size;
area.corner0() = start;
area.orientation() = WFMath::RotMatrix<2>().identity();
area.rotatePoint(rm, entityPosition2d);
mAwareness->setAwarenessArea(mAvatar.getId(), area, WFMath::Segment<2>(entityPosition2d, destination2d));
}
}
}
size_t Steering::unawareAreaCount() const {
if (mAwareness) {
return mAwareness->unawareTilesInArea(mAvatar.getId());
}
return 0;
}
void Steering::setDesiredSpeed(float desiredSpeed)
{
mDesiredSpeed = desiredSpeed;
}
int Steering::getPathResult() const
{
return mPathResult;
}
void Steering::updateDestination(double currentServerTimestamp, int entityId, WFMath::Point<3>& pos) {
2016-05-22 21:55:09 +02:00
if (mAwareness && mAvatar.m_location.m_loc) {
if (entityId != mAvatar.m_location.m_loc->getIntId()) {
mAwareness->projectPosition(mDestinationEntityId, pos, currentServerTimestamp);
}
}
}
2016-03-08 23:28:05 +01:00
int Steering::updatePath(const WFMath::Point<3>& currentAvatarPosition)
{
mPath.clear();
if (!mAwareness) {
mPathResult = -7;
return mPathResult;
}
if (!mViewDestination.isValid()) {
mPathResult = -8;
return mPathResult;
}
mPathResult = mAwareness->findPath(currentAvatarPosition, mViewDestination, mDestinationRadius, mPath);
2016-03-08 23:28:05 +01:00
//debug_print("Updating path, size of new path: " << result << ". Pos: " << currentAvatarPosition);
EventPathUpdated();
mUpdateNeeded = false;
return mPathResult;
2016-03-08 23:28:05 +01:00
}
int Steering::updatePath(double currentTimestamp)
{
if (!mAwareness) {
return -1;
}
auto currentEntityPos = mAvatar.m_location.m_pos;
if (mAvatar.m_location.m_velocity.isValid()) {
currentEntityPos += (mAvatar.m_location.m_velocity * (currentTimestamp - mAvatar.m_location.timeStamp()));
}
updateDestination(currentTimestamp, mDestinationEntityId, mViewDestination);
2016-03-08 23:28:05 +01:00
return updatePath(currentEntityPos);
}
void Steering::requestUpdate()
{
mUpdateNeeded = true;
}
void Steering::startSteering()
{
mSteeringEnabled = true;
mExpectingServerMovement = false;
}
void Steering::stopSteering()
{
if (!mSteeringEnabled) {
return;
}
mSteeringEnabled = false;
mExpectingServerMovement = false;
mLastSentVelocity = WFMath::Vector<2>();
//reset path
mPath = std::list<WFMath::Point<3>>();
2016-06-07 22:51:53 +02:00
mPathResult = 0;
EventPathUpdated();
}
bool Steering::isEnabled() const
{
return mSteeringEnabled;
}
const std::list<WFMath::Point<3>>& Steering::getPath() const
{
return mPath;
}
2016-03-08 23:28:05 +01:00
WFMath::Point<3> Steering::getCurrentAvatarPosition(double currentTimestamp)
{
auto currentEntityPos = mAvatar.m_location.m_pos;
if (mAvatar.m_location.m_velocity.isValid()) {
currentEntityPos += (mAvatar.m_location.m_velocity * (currentTimestamp - mAvatar.m_location.timeStamp()));
}
return currentEntityPos;
}
SteeringResult Steering::update(double currentTimestamp)
{
2018-02-09 12:50:01 +01:00
SteeringResult result{};
if (mSteeringEnabled && mAwareness) {
2016-03-08 23:28:05 +01:00
auto currentEntityPos = getCurrentAvatarPosition(currentTimestamp);
//if (mUpdateNeeded) {
updateDestination(currentTimestamp, mDestinationEntityId, mViewDestination);
updatePath(currentEntityPos);
//}
if (!mPath.empty()) {
const auto& finalDestination = mPath.back();
2018-02-09 12:50:01 +01:00
const WFMath::Point<2> entityPosition(currentEntityPos.x(), currentEntityPos.z());
//First check if we've arrived at our actual destination.
2018-02-09 12:50:01 +01:00
if (WFMath::Distance(WFMath::Point<2>(finalDestination.x(), finalDestination.z()), entityPosition) < mAvatarHorizRadius) {
//We've arrived at our destination. If we're moving we should stop.
if (mLastSentVelocity != WFMath::Vector<2>::ZERO()) {
result.direction = WFMath::Vector<3>::ZERO();
mLastSentVelocity = WFMath::Vector<2>::ZERO();
mExpectingServerMovement = true;
}
stopSteering();
} else {
//We should send a move op if we're either not moving, or we've reached a waypoint, or we need to divert a lot.
2018-01-10 15:11:20 +01:00
WFMath::Point<2> nextWaypoint(mPath.front().x(), mPath.front().z());
while (WFMath::Distance(nextWaypoint, entityPosition) < mAvatarHorizRadius && mPath.size() > 1) {
mPath.pop_front();
2018-01-10 15:11:20 +01:00
nextWaypoint = WFMath::Point<2>(mPath.front().x(), mPath.front().z());
}
WFMath::Vector<2> distance = nextWaypoint - entityPosition;
WFMath::Vector<2> velocity = distance;
WFMath::Point<2> destination;
velocity = velocity.normalize() * mDesiredSpeed;
result.timeToNextWaypoint = distance.mag() / mMaxSpeed;
if (mPath.size() == 1) {
//if the next waypoint is the destination we should send a "move to position" update to the server, to make sure that we stop when we've arrived.
//otherwise, if there's too much lag, we might end up overshooting our destination and will have to double back
destination = nextWaypoint;
}
//Check if we need to divert in order to avoid colliding.
WFMath::Vector<2> newVelocity;
bool avoiding = mAwareness->avoidObstacles(mAvatar.getIntId(), entityPosition, velocity * mMaxSpeed, newVelocity, currentTimestamp);
if (avoiding) {
auto newMag = newVelocity.mag();
auto relativeMag = mMaxSpeed / newMag;
velocity = newVelocity;
velocity.normalize();
velocity *= relativeMag;
//Schedule a new steering op very soon
2016-03-12 23:38:45 +01:00
result.timeToNextWaypoint = 0.2f;
mUpdateNeeded = true;
}
bool shouldSend = false;
if (velocity.isValid()) {
if (mLastSentVelocity.isValid()) {
//If the entity has stopped, and we're not waiting for confirmation to a movement request we've made, we need to start moving.
if (mAvatar.m_location.velocity() == WFMath::Vector<3>::ZERO() && !mExpectingServerMovement) {
shouldSend = true;
} else {
2018-02-09 12:50:01 +01:00
auto currentTheta = std::atan2(mLastSentVelocity.y(), mLastSentVelocity.x());
auto newTheta = std::atan2(velocity.y(), velocity.x());
//If we divert too much from where we need to go we must adjust.
if (std::abs(currentTheta - newTheta) > WFMath::numeric_constants<double>::pi() / 20) {
shouldSend = true;
}
}
} else {
//If we've never sent a movement update before we should do that now.
shouldSend = true;
}
}
if (shouldSend) {
//If we're moving to a certain destination and aren't avoiding anything we should tell the server to move to the destination.
if (destination.isValid() && !avoiding) {
2018-01-10 15:11:20 +01:00
result.destination = WFMath::Point<3>(destination.x(), mAvatar.m_location.m_pos.y(), destination.y());
}
2018-01-10 15:11:20 +01:00
result.direction = WFMath::Vector<3>(velocity.x(), 0, velocity.y());
mLastSentVelocity = velocity;
mExpectingServerMovement = true;
}
}
} else {
//We are steering, but the path is empty, which means we can't find any path. If we're moving we should stop movement.
//But we won't stop steering; perhaps we'll find a path later.
if (mLastSentVelocity != WFMath::Vector<2>::ZERO()) {
result.direction = WFMath::Vector<3>::ZERO();
mLastSentVelocity = WFMath::Vector<2>::ZERO();
mExpectingServerMovement = true;
}
}
}
return result;
}
void Steering::Awareness_TileUpdated(int tx, int ty)
{
mUpdateNeeded = true;
}
bool Steering::getIsExpectingServerMovement() const
{
return mExpectingServerMovement;
}
void Steering::setIsExpectingServerMovement(bool expected)
{
mExpectingServerMovement = expected;
}