/*
* Copyright 2010-2016 OpenXcom Developers.
*
* This file is part of OpenXcom.
*
* OpenXcom 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 3 of the License, or
* (at your option) any later version.
*
* OpenXcom 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 OpenXcom. If not, see <http://www.gnu.org/licenses/>.
*/
#include "MovingTarget.h"
#include "../fmath.h"
#include "SerializationHelper.h"
#include "../Engine/Options.h"
namespace OpenXcom
{
/**
* Initializes a moving target with blank coordinates.
*/
MovingTarget::MovingTarget() : Target(), _dest(0), _speedLon(0.0), _speedLat(0.0), _speedRadian(0.0), _meetPointLon(0.0), _meetPointLat(0.0), _speed(0), _meetCalculated(false)
{
}
/**
* Make sure to cleanup the target's destination followers.
*/
MovingTarget::~MovingTarget()
{
setDestination(0);
}
/**
* Loads the moving target from a YAML file.
* @param node YAML node.
*/
void MovingTarget::load(const YAML::Node &node)
{
Target::load(node);
_speedLon = node["speedLon"].as<double>(_speedLon);
_speedLat = node["speedLat"].as<double>(_speedLat);
_speedRadian = node["speedRadian"].as<double>(_speedRadian);
_speed = node["speed"].as<int>(_speed);
}
/**
* Saves the moving target to a YAML file.
* @return YAML node.
*/
YAML::Node MovingTarget::save() const
{
YAML::Node node = Target::save();
if (_dest != 0)
{
node["dest"] = _dest->saveId();
}
node["speedLon"] = serializeDouble(_speedLon);
node["speedLat"] = serializeDouble(_speedLat);
node["speedRadian"] = serializeDouble(_speedRadian);
node["speed"] = _speed;
return node;
}
/**
* Returns the destination the moving target is heading to.
* @return Pointer to destination.
*/
Target *MovingTarget::getDestination() const
{
return _dest;
}
/**
* Changes the destination the moving target is heading to.
* @param dest Pointer to destination.
*/
void MovingTarget::setDestination(Target *dest)
{
_meetCalculated = false;
// Remove moving target from old destination's followers
if (_dest != 0)
{
for (std::vector<MovingTarget*>::iterator i = _dest->getFollowers()->begin(); i != _dest->getFollowers()->end(); ++i)
{
if ((*i) == this)
{
_dest->getFollowers()->erase(i);
break;
}
}
}
_dest = dest;
// Add moving target to new destination's followers
if (_dest != 0)
{
_dest->getFollowers()->push_back(this);
}
// Recalculate meeting point for any followers
for (std::vector<MovingTarget*>::iterator i = getFollowers()->begin(); i != getFollowers()->end(); ++i)
{
(*i)->resetMeetPoint();
}
calculateSpeed();
}
/**
* Returns the speed of the moving target.
* @return Speed in knots.
*/
int MovingTarget::getSpeed() const
{
return _speed;
}
/**
* Returns the radial speed of the moving target.
* @return Speed in 1 / 5 sec.
*/
double MovingTarget::getSpeedRadian() const
{
return _speedRadian;
}
/**
* Converts a speed in degrees to a speed in radians.
* Each nautical mile is 1/60th of a degree.
* Each hour contains 720 5-seconds.
* @param speed Speed in degrees.
* @return Speed in radians.
*/
double MovingTarget::calculateRadianSpeed(int speed)
{
return Nautical(speed) / 720.0;
}
/**
* Changes the speed of the moving target
* and converts it from standard knots (nautical miles per hour)
* into radians per 5 in-game seconds.
* @param speed Speed in knots.
*/
void MovingTarget::setSpeed(int speed)
{
_speed = speed;
_speedRadian = calculateRadianSpeed(_speed);
// Recalculate meeting point for any followers
for (std::vector<MovingTarget *>::iterator i = getFollowers()->begin(); i != getFollowers()->end(); ++i)
{
(*i)->resetMeetPoint();
}
calculateSpeed();
}
/**
* Calculates the speed vector based on the
* great circle distance to destination and
* current raw speed.
*/
void MovingTarget::calculateSpeed()
{
calculateMeetPoint();
if (_dest != 0)
{
double dLon, dLat, length;
dLon = sin(_meetPointLon - _lon) * cos(_meetPointLat);
dLat = cos(_lat) * sin(_meetPointLat) - sin(_lat) * cos(_meetPointLat) * cos(_meetPointLon - _lon);
length = sqrt(dLon * dLon + dLat * dLat);
_speedLat = dLat / length * _speedRadian;
_speedLon = dLon / length * _speedRadian / cos(_lat + _speedLat);
// Check for invalid speeds when a division by zero occurs due to near-zero values
if (!(_speedLon == _speedLon) || !(_speedLat == _speedLat))
{
_speedLon = 0;
_speedLat = 0;
}
}
else
{
_speedLon = 0;
_speedLat = 0;
}
}
/**
* Checks if the moving target has reached its destination.
* @return True if it has, False otherwise.
*/
bool MovingTarget::reachedDestination() const
{
if (_dest == 0)
{
return false;
}
return ( AreSame(_dest->getLongitude(), _lon) && AreSame(_dest->getLatitude(), _lat) );
}
/**
* Executes a movement cycle for the moving target.
*/
void MovingTarget::move()
{
calculateSpeed();
if (_dest != 0)
{
if (getDistance(_meetPointLon, _meetPointLat) > _speedRadian)
{
setLongitude(_lon + _speedLon);
setLatitude(_lat + _speedLat);
}
else
{
if (getDistance(_dest) > _speedRadian)
{
setLongitude(_meetPointLon);
setLatitude(_meetPointLat);
}
else
{
setLongitude(_dest->getLongitude());
setLatitude(_dest->getLatitude());
}
resetMeetPoint();
}
}
}
/**
* Calculate meeting point with the target.
*/
void MovingTarget::calculateMeetPoint()
{
if (!Options::meetingPoint) _meetCalculated = false;
if (_meetCalculated) return;
// Initialize
if (_dest != 0)
{
_meetPointLat = _dest->getLatitude();
_meetPointLon = _dest->getLongitude();
}
else
{
_meetPointLat = _lat;
_meetPointLon = _lon;
}
if (!_dest || !Options::meetingPoint || reachedDestination()) return;
MovingTarget *t = dynamic_cast<MovingTarget*>(_dest);
if (!t || !t->getDestination()) return;
// Speed ratio
if (AreSame(t->getSpeedRadian(), 0.0)) return;
const double speedRatio = _speedRadian/ t->getSpeedRadian();
// The direction pseudovector
double nx = cos(t->getLatitude())*sin(t->getLongitude())*sin(t->getDestination()->getLatitude()) -
sin(t->getLatitude())*cos(t->getDestination()->getLatitude())*sin(t->getDestination()->getLongitude()),
ny = sin(t->getLatitude())*cos(t->getDestination()->getLatitude())*cos(t->getDestination()->getLongitude()) -
cos(t->getLatitude())*cos(t->getLongitude())*sin(t->getDestination()->getLatitude()),
nz = cos(t->getLatitude())*cos(t->getDestination()->getLatitude())*sin(t->getDestination()->getLongitude() - t->getLongitude());
// Normalize and multiplex with radian speed
double nk = _speedRadian/sqrt(nx*nx+ny*ny+nz*nz);
nx *= nk;
ny *= nk;
nz *= nk;
// Finding the meeting point. Don't search further than halfway across the
// globe (distance from interceptor's current point >= 1), as that may
// cause the interceptor to go the wrong way later.
for (double path = 0, distance = 1;
path < M_PI && distance - path*speedRatio > 0 && path*speedRatio < 1;
path += _speedRadian)
{
_meetPointLat += nx*sin(_meetPointLon) - ny*cos(_meetPointLon);
if (std::abs(_meetPointLat) < M_PI_2) _meetPointLon += nz - (nx*cos(_meetPointLon) + ny*sin(_meetPointLon))*tan(_meetPointLat); else _meetPointLon += M_PI;
distance = acos(cos(_lat) * cos(_meetPointLat) * cos(_meetPointLon - _lon) + sin(_lat) * sin(_meetPointLat));
}
// Correction overflowing angles
double lonSign = Sign(_meetPointLon);
double latSign = Sign(_meetPointLat);
while (std::abs(_meetPointLon) > M_PI) _meetPointLon -= lonSign * 2 * M_PI;
while (std::abs(_meetPointLat) > M_PI) _meetPointLat -= latSign * 2 * M_PI;
if (std::abs(_meetPointLat) > M_PI_2) { _meetPointLat = latSign * std::abs(2 * M_PI - std::abs(_meetPointLat)); _meetPointLon -= lonSign * M_PI; }
_meetCalculated = true;
}
/**
* Returns the latitude of the meeting point.
* @return Angle in rad.
*/
double MovingTarget::getMeetLatitude() const
{
return _meetPointLat;
}
/**
* Returns the longitude of the meeting point.
* @return Angle in rad.
*/
double MovingTarget::getMeetLongitude() const
{
return _meetPointLon;
}
/**
* Forces the meeting point to be recalculated in the event
* that the target has changed direction.
*/
void MovingTarget::resetMeetPoint()
{
_meetCalculated = false;
}
bool MovingTarget::isMeetCalculated() const
{
return _meetCalculated;
}
}
↑ V1053 Calling the 'setDestination' virtual function in the destructor may lead to unexpected result at runtime.