Changeset View
Changeset View
Standalone View
Standalone View
source/simulation2/components/CCmpUnitMotionFlying.cpp
- This file was added.
/* Copyright (C) 2019 Wildfire Games. | |||||
* This file is part of 0 A.D. | |||||
* | |||||
* 0 A.D. 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. | |||||
* | |||||
* 0 A.D. 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 0 A.D. If not, see <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
#include "precompiled.h" | |||||
#include "CCmpUnitMotionFlying.h" | |||||
constexpr float SHORT_FINAL = 2.5f; | |||||
constexpr double M_PI_NINTH = M_PI / 9.0; | |||||
constexpr double M_TWO_PI = M_PI * 2.0; | |||||
constexpr double M_PI_EIGHTEENTH = M_PI / 18.0; | |||||
std::string CCmpUnitMotionFlying::GetSchema() | |||||
{ | |||||
return | |||||
"<element name='MaxSpeed'>" | |||||
"<ref name='nonNegativeDecimal'/>" | |||||
"</element>" | |||||
"<element name='TakeoffSpeed'>" | |||||
"<ref name='nonNegativeDecimal'/>" | |||||
"</element>" | |||||
"<element name='LandingSpeed'>" | |||||
"<ref name='nonNegativeDecimal'/>" | |||||
"</element>" | |||||
"<element name='AccelRate'>" | |||||
"<ref name='nonNegativeDecimal'/>" | |||||
"</element>" | |||||
"<element name='SlowingRate'>" | |||||
"<ref name='nonNegativeDecimal'/>" | |||||
"</element>" | |||||
"<element name='BrakingRate'>" | |||||
"<ref name='nonNegativeDecimal'/>" | |||||
"</element>" | |||||
"<element name='TurnRate'>" | |||||
"<ref name='nonNegativeDecimal'/>" | |||||
"</element>" | |||||
"<element name='OvershootTime'>" | |||||
"<ref name='nonNegativeDecimal'/>" | |||||
"</element>" | |||||
"<element name='FlyingHeight'>" | |||||
"<data type='decimal'/>" | |||||
"</element>" | |||||
"<element name='ClimbRate'>" | |||||
"<ref name='nonNegativeDecimal'/>" | |||||
"</element>" | |||||
"<optional>" | |||||
"<element name='StationaryDistance' a:help='Allows the object to be stationnary when reaching a target. Value defines the maximum distance at which a target is considered reached'>" | |||||
"<ref name='positiveDecimal'/>" | |||||
"</element>" | |||||
"</optional>" | |||||
"<element name='DiesInWater'>" | |||||
"<data type='boolean'/>" | |||||
"</element>" | |||||
"<element name='PassabilityClass'>" | |||||
"<text/>" | |||||
"</element>"; | |||||
} | |||||
void CCmpUnitMotionFlying::Serialize(ISerializer& serializer) | |||||
{ | |||||
} | |||||
void CCmpUnitMotionFlying::Deserialize(const CParamNode& paramNode, IDeserializer& deserializer) | |||||
{ | |||||
} | |||||
void CCmpUnitMotionFlying::ClassInit(CComponentManager& componentManager) | |||||
{ | |||||
componentManager.SubscribeToMessageType(MT_Update); | |||||
} | |||||
void CCmpUnitMotionFlying::Init(const CParamNode ¶mNode) | |||||
{ | |||||
m_HasTarget = false; | |||||
m_ReachedTarget = false; | |||||
m_TargetX = fixed::Zero(); | |||||
m_TargetZ = fixed::Zero(); | |||||
m_TargetMinRange = fixed::Zero(); | |||||
m_TargetMaxRange = fixed::Zero(); | |||||
m_Speed = fixed::Zero(); | |||||
m_Landing = false; | |||||
m_OnGround = true; | |||||
m_Pitch = fixed::Zero(); | |||||
m_Roll = fixed::Zero(); | |||||
m_WaterDeath = false; | |||||
m_BrakingRate = paramNode.GetChild("BrakingRate").ToFixed(); | |||||
m_ClimbRate = paramNode.GetChild("ClimbRate").ToFixed(); | |||||
m_TurnRate = paramNode.GetChild("TurnRate").ToFixed(); | |||||
m_MaxSpeed = paramNode.GetChild("MaxSpeed").ToFixed(); | |||||
m_DiesInWater = paramNode.GetChild("MaxSpeed").ToBool(); | |||||
m_FlyingHeight =paramNode.GetChild("FlyingHeight").ToFixed(); | |||||
m_LandingSpeed = paramNode.GetChild("LandingSpeed").ToFixed(); | |||||
m_SlowingRate = paramNode.GetChild("SlowingRate").ToFixed(); | |||||
m_OvershootTime = paramNode.GetChild("OvershootTime").ToFixed(); | |||||
m_TakeOffSpeed = paramNode.GetChild("TakeoffSpeed").ToFixed(); | |||||
m_AccelRate = paramNode.GetChild("AccelRate").ToFixed(); | |||||
m_PassabilityClassName = paramNode.GetChild("PassabilityClass").ToUTF8(); | |||||
if (paramNode.GetChild("StationaryDistance").IsOk()) | |||||
m_StationaryDistance = paramNode.GetChild("StationaryDistance").ToFixed(); | |||||
else | |||||
m_StationaryDistance = fixed::FromInt(-1); | |||||
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); | |||||
if (cmpPathfinder) | |||||
m_PassabilityClass = cmpPathfinder->GetPassabilityClass(m_PassabilityClassName); | |||||
} | |||||
bool CCmpUnitMotionFlying::MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange) | |||||
{ | |||||
m_HasTarget = true; | |||||
m_Landing = false; | |||||
m_ReachedTarget = false; | |||||
m_TargetX = x; | |||||
m_TargetZ = z; | |||||
m_TargetMinRange = minRange; | |||||
m_TargetMaxRange = maxRange; | |||||
return true; | |||||
}; | |||||
bool CCmpUnitMotionFlying::MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) | |||||
{ | |||||
CmpPtr<ICmpPosition> cmpTargetPosition(GetEntityHandle()); | |||||
if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld()) | |||||
return false; | |||||
CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D(); | |||||
return MoveToPointRange(targetPos.X, targetPos.Y, minRange, maxRange); | |||||
} | |||||
fixed CCmpUnitMotionFlying::GetWalkSpeed() const | |||||
{ | |||||
return m_MaxSpeed; | |||||
} | |||||
void CCmpUnitMotionFlying::SetSpeedMultiplier(fixed UNUSED(multiplier)) | |||||
{ | |||||
// Ignore this, the speed is always the walk speed. | |||||
} | |||||
fixed CCmpUnitMotionFlying::GetRunMultiplier() const | |||||
{ | |||||
return fixed::FromInt(1); | |||||
} | |||||
bool CCmpUnitMotionFlying::IsMoveRequested() const | |||||
{ | |||||
return m_HasTarget; | |||||
} | |||||
entity_pos_t CCmpUnitMotionFlying::GetUnitClearance() const | |||||
{ | |||||
return entity_pos_t::Zero(); | |||||
} | |||||
void CCmpUnitMotionFlying::MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z) | |||||
{ | |||||
} | |||||
fixed CCmpUnitMotionFlying::GetCurrentSpeed() const | |||||
{ | |||||
return m_Speed; | |||||
} | |||||
fixed CCmpUnitMotionFlying::GetSpeed() const | |||||
{ | |||||
return GetCurrentSpeed(); | |||||
} | |||||
fixed CCmpUnitMotionFlying::GetSpeedMultiplier() const | |||||
{ | |||||
return m_Speed / m_MaxSpeed; | |||||
} | |||||
std::string CCmpUnitMotionFlying::GetPassabilityClassName() const | |||||
{ | |||||
return m_PassabilityClassName; | |||||
} | |||||
pass_class_t CCmpUnitMotionFlying::GetPassabilityClass() const | |||||
{ | |||||
return m_PassabilityClass; | |||||
} | |||||
void CCmpUnitMotionFlying::FaceTowardsPoint(fixed x, fixed z) | |||||
{ | |||||
// Ignore this - angle is controlled by the target-seeking code instead | |||||
} | |||||
void CCmpUnitMotionFlying::SetFacePointAfterMove(bool facePointAfterMove) | |||||
{ | |||||
// Ignore this - angle is controlled by the target-seeking code instead | |||||
} | |||||
void CCmpUnitMotionFlying::StopMoving() | |||||
{ | |||||
// Invert. | |||||
if (!m_WaterDeath) | |||||
m_Landing = !m_Landing; | |||||
} | |||||
void CCmpUnitMotionFlying::SetDebugOverlay(bool enabled) | |||||
{ | |||||
// TODO: Implement. | |||||
} | |||||
void CCmpUnitMotionFlying::Deinit() | |||||
{ | |||||
} | |||||
void CCmpUnitMotionFlying::Move(fixed turnLength) | |||||
{ | |||||
if (!m_HasTarget) | |||||
return; | |||||
CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity()); | |||||
if (!cmpTerrain) | |||||
return; | |||||
CmpPtr<ICmpWaterManager> cmpWaterManager(GetSystemEntity()); | |||||
if (!cmpWaterManager) | |||||
return; | |||||
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | |||||
if (!cmpPosition || !cmpPosition->IsInWorld()) | |||||
return; | |||||
fixed sinus = fixed::Zero(); | |||||
fixed cosinus = fixed::Zero(); | |||||
CFixedVector3D pos = cmpPosition->GetPosition(); | |||||
fixed ground = std::max(cmpTerrain->GetGroundLevel(pos.X, pos.Z), cmpWaterManager->GetWaterLevel(pos.X, pos.Z)); | |||||
fixed angle = cmpPosition->GetRotation().Y; | |||||
fixed newangle = angle; | |||||
bool canTurn = true; | |||||
if (m_Landing) | |||||
{ | |||||
if (m_Speed > fixed::Zero() && m_OnGround) | |||||
{ | |||||
if (pos.Y <= cmpWaterManager->GetWaterLevel(pos.X, pos.Z) && m_DiesInWater) | |||||
m_WaterDeath = true; | |||||
m_Pitch = fixed::Zero(); | |||||
// Deaccelerate forwards...at a very reduced pace. | |||||
if (m_WaterDeath) | |||||
m_Speed = std::max(fixed::Zero(), m_Speed - turnLength.Multiply(m_BrakingRate.Multiply(fixed::FromInt(10)))); | |||||
else | |||||
m_Speed = std::max(fixed::Zero(), m_Speed - turnLength.Multiply(m_BrakingRate)); | |||||
canTurn = false; | |||||
// Clamp to ground if below it, or descend if above | |||||
pos.Y = pos.Y < ground ? ground : std::max(ground, pos.Y - turnLength.Multiply(m_ClimbRate)); | |||||
} | |||||
else if (m_Speed.IsZero() && m_OnGround) | |||||
{ | |||||
CmpPtr<ICmpHealth> cmpHealth(GetEntityHandle()); | |||||
if (m_WaterDeath && cmpHealth) | |||||
cmpHealth->Kill(); | |||||
else | |||||
{ | |||||
m_Pitch = fixed::Zero(); | |||||
CmpPtr<ICmpGarrisonHolder> cmpGarrisonHolder(GetEntityHandle()); | |||||
if (cmpGarrisonHolder) | |||||
cmpGarrisonHolder->AllowGarrisoning(true, "UnitMotionFlying"); | |||||
canTurn = false; | |||||
m_HasTarget = false; | |||||
m_Landing = false; | |||||
// summon planes back from the edge of the map | |||||
u32 terrainSize = cmpTerrain->GetMapSize(); | |||||
CmpPtr<ICmpRangeManager> cmpRangeManager(GetSystemEntity()); | |||||
if (cmpRangeManager->GetLosCircular()) | |||||
{ | |||||
fixed mapRadius = fixed::FromDouble(terrainSize / 2.0); | |||||
fixed x = pos.X - mapRadius; | |||||
fixed z = pos.Z - mapRadius; | |||||
fixed div = (mapRadius - fixed::FromInt(12)) / (x.Square() + z.Square()).Sqrt(); | |||||
if (div < fixed::FromInt(1)) | |||||
{ | |||||
pos.X = mapRadius + x.Multiply(div); | |||||
pos.Z = mapRadius + z.Multiply(div); | |||||
newangle += fixed::Pi(); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
pos.X = std::max(std::min(pos.X, fixed::FromInt(terrainSize - 12)), fixed::FromInt(12)); | |||||
pos.Z = std::max(std::min(pos.Z, fixed::FromInt(terrainSize - 12)), fixed::FromInt(12)); | |||||
newangle += fixed::Pi(); | |||||
} | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
// Final Approach | |||||
// We need to slow down to land! | |||||
m_Speed = std::max(m_LandingSpeed, m_Speed - turnLength.Multiply(m_SlowingRate)); | |||||
canTurn = false; | |||||
fixed targetHeight = ground; | |||||
auto heightRatio = (pos.Y - targetHeight) / m_FlyingHeight; | |||||
// Steep, then gradual descent. | |||||
if (heightRatio > fixed::FromInt(1) / fixed::FromDouble(SHORT_FINAL)) | |||||
m_Pitch = - fixed::FromDouble(M_PI_EIGHTEENTH); | |||||
else | |||||
m_Pitch = fixed::FromDouble(M_PI_EIGHTEENTH); | |||||
fixed descentRate = (heightRatio.Multiply(m_ClimbRate) + fixed::FromDouble(SHORT_FINAL)).Multiply(fixed::FromDouble(SHORT_FINAL)); | |||||
if (pos.Y < targetHeight) | |||||
pos.Y = std::max(targetHeight, pos.Y + turnLength.Multiply(descentRate)); | |||||
else if (pos.Y > targetHeight) | |||||
pos.Y = std::max(targetHeight, pos.Y - turnLength.Multiply(descentRate)); | |||||
if (targetHeight == pos.Y) | |||||
{ | |||||
m_OnGround = true; | |||||
if (targetHeight == cmpWaterManager->GetWaterLevel(pos.X, pos.Z) && m_DiesInWater) | |||||
m_WaterDeath = true; | |||||
} | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
if (m_StationaryDistance > fixed::Zero() && EuclidDistance(pos.X, pos.Z, m_TargetX, m_TargetZ) <= m_StationaryDistance.Square()) | |||||
return; | |||||
// If we haven't reached max speed yet then we're still on the ground; | |||||
// otherwise we're taking off or flying. | |||||
// m_OnGround in case of a go-around after landing (but not fully stopped). | |||||
if (m_Speed < m_TakeOffSpeed && m_OnGround) | |||||
{ | |||||
CmpPtr<ICmpGarrisonHolder> cmpGarrisonHolder(GetEntityHandle()); | |||||
if (cmpGarrisonHolder) | |||||
cmpGarrisonHolder->AllowGarrisoning(false, "UnitMotionFlying"); | |||||
m_Pitch = fixed::Zero(); | |||||
// Accelerate forwards | |||||
m_Speed = std::min(m_MaxSpeed, m_Speed + turnLength.Multiply(m_AccelRate)); | |||||
canTurn = false; | |||||
// Clamp to ground if below it, or descend if above | |||||
pos.Y = pos.Y < ground ? ground : std::max(ground, pos.Y - turnLength.Multiply(m_ClimbRate)); | |||||
} | |||||
else | |||||
{ | |||||
m_OnGround = false; | |||||
// Climb/sink to max height above ground | |||||
m_Speed = std::min(m_MaxSpeed, m_Speed + turnLength.Multiply(m_AccelRate)); | |||||
fixed targetHeight = ground + m_FlyingHeight; | |||||
if ((pos.Y - targetHeight).Absolute() > m_FlyingHeight / fixed::FromInt(5)) | |||||
{ | |||||
m_Pitch = fixed::FromDouble(M_PI_NINTH); | |||||
canTurn = false; | |||||
} | |||||
else | |||||
m_Pitch = fixed::Zero(); | |||||
if (pos.Y < targetHeight) | |||||
pos.Y = std::min(targetHeight, pos.Y + turnLength.Multiply(m_ClimbRate)); | |||||
else if (pos.Y > targetHeight) | |||||
{ | |||||
pos.Y = std::max(targetHeight, pos.Y - turnLength.Multiply(m_ClimbRate)); | |||||
m_Pitch = fixed::FromInt(-1).Multiply(m_Pitch); | |||||
} | |||||
} | |||||
} | |||||
// If we're in range of the target then tell people that we've reached it | |||||
// (TODO: quantisation breaks this) | |||||
fixed distFromTarget = EuclidDistance(pos.X, pos.Z, m_TargetX, m_TargetZ); | |||||
if (!m_ReachedTarget && m_TargetMinRange.Square() <= distFromTarget && distFromTarget <= m_TargetMaxRange.Square()) | |||||
{ | |||||
m_ReachedTarget = true; | |||||
CMessageMotionUpdate msg(CMessageMotionUpdate::LIKELY_SUCCESS); | |||||
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); | |||||
} | |||||
sincos_approx(angle, sinus, cosinus); | |||||
// If we're facing away from the target, and are still fairly close to it, | |||||
// then carry on going straight so we overshoot in a straight line | |||||
bool isBehindTarget = ((m_TargetX - pos.X).Multiply(sinus) + (m_TargetZ - pos.Z).Multiply(cosinus) < fixed::Zero()); | |||||
// Overshoot the target: carry on straight | |||||
if (isBehindTarget && distFromTarget < m_MaxSpeed.Multiply(m_OvershootTime).Square()) | |||||
canTurn = false; | |||||
if (canTurn) | |||||
{ | |||||
// Turn towards the target | |||||
fixed targetAngle = atan2_approx(m_TargetX - pos.X, m_TargetZ - pos.Z); | |||||
fixed delta = targetAngle - angle; | |||||
// Wrap delta to -pi..pi | |||||
delta = (delta + fixed::Pi()) % fixed::FromDouble(M_TWO_PI); // range -2pi..2pi | |||||
if (delta < fixed::Zero()) | |||||
delta += fixed::FromDouble(M_TWO_PI); // range 0..2pi | |||||
delta -= fixed::Pi(); // range -pi..pi | |||||
// Clamp to max rate | |||||
fixed deltaClamped = std::min(std::max(delta, - m_TurnRate.Multiply(turnLength)), m_TurnRate.Multiply(turnLength)); | |||||
// Calculate new orientation, in a peculiar way in order to make sure the | |||||
// result gets close to targetAngle (rather than being n*2*pi out) | |||||
newangle = targetAngle + deltaClamped - delta; | |||||
if (newangle - angle > fixed::FromDouble(M_PI_EIGHTEENTH)) | |||||
m_Roll = fixed::FromDouble(M_PI_NINTH); | |||||
else if (newangle - angle < - fixed::FromDouble(M_PI_EIGHTEENTH)) | |||||
m_Roll = -fixed::FromDouble(M_PI_NINTH); | |||||
else | |||||
m_Roll = newangle - angle; | |||||
} | |||||
else | |||||
m_Roll = fixed::Zero(); | |||||
sincos_approx(angle, sinus, cosinus); | |||||
fixed temp = m_Speed.Multiply(turnLength); | |||||
pos.X += temp.Multiply(sinus); | |||||
pos.Z += temp.Multiply(cosinus); | |||||
cmpPosition->SetHeightFixed(pos.Y); | |||||
cmpPosition->TurnTo(newangle); | |||||
cmpPosition->SetXZRotation(m_Pitch, m_Roll); | |||||
cmpPosition->MoveTo(pos.X, pos.Z); | |||||
} | |||||
fixed CCmpUnitMotionFlying::EuclidDistance(fixed x1, fixed z1, fixed x2, fixed z2) | |||||
{ | |||||
int dx = (x1 - x2).ToInt_RoundToNearest(); | |||||
int dz = (z1 - z2).ToInt_RoundToNearest(); | |||||
return fixed::FromInt(dx + dz); | |||||
} | |||||
void CCmpUnitMotionFlying::HandleMessage(const CMessage& msg, bool UNUSED(global)) | |||||
{ | |||||
switch (msg.GetType()) | |||||
{ | |||||
case MT_Update: | |||||
{ | |||||
Move(static_cast<const CMessageUpdate&> (msg).turnLength); | |||||
break; | |||||
} | |||||
} | |||||
} |
Wildfire Games · Phabricator