Downward lookahead.

Added downward lookahead capability.
This commit is contained in:
Matija Hustić
2015-10-11 19:18:43 +02:00
parent 12b4f8ccf4
commit cd8a15271c

View File

@@ -35,19 +35,19 @@ namespace OpenRA.Mods.Common.Effects
public readonly bool Shadow = false; public readonly bool Shadow = false;
[Desc("Minimum vertical launch angle (pitch).")] [Desc("Minimum vertical launch angle (pitch).")]
public readonly WAngle MinimumLaunchAngle = new WAngle(-128); public readonly WAngle MinimumLaunchAngle = WAngle.Zero;
[Desc("Maximum vertical launch angle (pitch).")] [Desc("Maximum vertical launch angle (pitch).")]
public readonly WAngle MaximumLaunchAngle = new WAngle(64); public readonly WAngle MaximumLaunchAngle = new WAngle(64);
[Desc("Minimum launch speed in WDist / tick")] [Desc("Minimum launch speed in WDist / tick")]
public readonly WDist MinimumLaunchSpeed = WDist.Zero; public readonly WDist MinimumLaunchSpeed = new WDist(75);
[Desc("Maximum launch speed in WDist / tick")] [Desc("Maximum launch speed in WDist / tick")]
public readonly WDist MaximumLaunchSpeed = new WDist(8); public readonly WDist MaximumLaunchSpeed = new WDist(200);
[Desc("Maximum projectile speed in WDist / tick")] [Desc("Maximum projectile speed in WDist / tick")]
public readonly WDist MaximumSpeed = new WDist(512); public readonly WDist MaximumSpeed = new WDist(384);
[Desc("Projectile acceleration when propulsion activated.")] [Desc("Projectile acceleration when propulsion activated.")]
public readonly WDist Acceleration = new WDist(5); public readonly WDist Acceleration = new WDist(5);
@@ -128,6 +128,7 @@ namespace OpenRA.Mods.Common.Effects
public IEffect Create(ProjectileArgs args) { return new Missile(this, args); } public IEffect Create(ProjectileArgs args) { return new Missile(this, args); }
} }
// TODO: double check square roots!!!
public class Missile : IEffect, ISync public class Missile : IEffect, ISync
{ {
enum States enum States
@@ -153,6 +154,7 @@ namespace OpenRA.Mods.Common.Effects
States state; States state;
bool targetPassedBy; bool targetPassedBy;
bool lockOn = false; bool lockOn = false;
bool allowPassBy; // TODO: use this also with high minimum launch angle settings
WPos targetPosition; WPos targetPosition;
WVec offset; WVec offset;
@@ -191,7 +193,7 @@ namespace OpenRA.Mods.Common.Effects
DetermineLaunchSpeedAndAngle(world, out speed, out vFacing); DetermineLaunchSpeedAndAngle(world, out speed, out vFacing);
velocity = new WVec(0, -info.MaximumLaunchSpeed.Length, 0) velocity = new WVec(0, -speed, 0)
.Rotate(new WRot(WAngle.FromFacing(vFacing), WAngle.Zero, WAngle.Zero)) .Rotate(new WRot(WAngle.FromFacing(vFacing), WAngle.Zero, WAngle.Zero))
.Rotate(new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(hFacing))); .Rotate(new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(hFacing)));
@@ -218,40 +220,23 @@ namespace OpenRA.Mods.Common.Effects
static int LoopRadius(int speed, int rot) static int LoopRadius(int speed, int rot)
{ {
// loopRadius in w-units = speed in w-units per tick / angular speed in radians per tick // loopRadius in w-units = speed in w-units per tick / angular speed in radians per tick
// angular speed in radians per tick = VROT in facing units per tick * (pi radians / 128 facing units) // angular speed in radians per tick = rot in facing units per tick * (pi radians / 128 facing units)
// pi = 314 / 100 // pi = 314 / 100
// ==> loopRadius = (speed * 128 * 100) / (314 * VROT) // ==> loopRadius = (speed * 128 * 100) / (314 * rot)
return (speed * 6400) / (157 * rot); return (speed * 6400) / (157 * rot);
} }
void DetermineLaunchSpeedAndAngle(World world, out int speed, out int vFacing) void DetermineLaunchSpeedAndAngleForIncline(int predClfDist, int diffClfMslHgt, int relTarHorDist,
out int speed, out int vFacing)
{ {
speed = info.MaximumLaunchSpeed.Length; speed = info.MaximumLaunchSpeed.Length;
var loopRadius = LoopRadius(speed, info.VerticalRateOfTurn);
// Compute current distance from target position // Find smallest vertical facing, for which the missile will be able to climb terrAltDiff w-units
var tarDistVec = targetPosition + offset - pos; // within hHeightChange w-units all the while ending the ascent with vertical facing 0
var relTarHorDist = tarDistVec.HorizontalLength;
int predClfHgt;
int predClfDist;
InclineLookahead(world, relTarHorDist, out predClfHgt, out predClfDist);
// Height difference between the incline height and missile height
var diffClfMslHgt = predClfHgt - pos.Z;
// Incline coming up
if (diffClfMslHgt >= 0)
{
// Find smallest vertical facing, attainable in the next tick,
// for which the missile will be able to climb terrAltDiff w-units
// within hHeightChange w-units all the while ending the ascent
// with vertical facing 0
vFacing = info.MaximumLaunchAngle.Angle >> 2; vFacing = info.MaximumLaunchAngle.Angle >> 2;
// Compute minimum speed necessary to both be able to face directly upwards and // Compute minimum speed necessary to both be able to face directly upwards and have enough space
// have enough space to hit the target without passing it by (and thus having to // to hit the target without passing it by (and thus having to do horizontal loops)
// do horizontal loops)
var minSpeed = ((System.Math.Min(predClfDist * 1024 / (1024 - WAngle.FromFacing(vFacing).Sin()), var minSpeed = ((System.Math.Min(predClfDist * 1024 / (1024 - WAngle.FromFacing(vFacing).Sin()),
(relTarHorDist + predClfDist) * 1024 / (2 * (2048 - WAngle.FromFacing(vFacing).Sin()))) (relTarHorDist + predClfDist) * 1024 / (2 * (2048 - WAngle.FromFacing(vFacing).Sin())))
* info.VerticalRateOfTurn * 157) / 6400).Clamp(info.MinimumLaunchSpeed.Length, info.MaximumLaunchSpeed.Length); * info.VerticalRateOfTurn * 157) / 6400).Clamp(info.MinimumLaunchSpeed.Length, info.MaximumLaunchSpeed.Length);
@@ -276,10 +261,36 @@ namespace OpenRA.Mods.Common.Effects
// Find least vertical facing that will allow the missile to climb // Find least vertical facing that will allow the missile to climb
// terrAltDiff w-units within hHeightChange w-units // terrAltDiff w-units within hHeightChange w-units
// all the while ending the ascent with vertical facing 0 // all the while ending the ascent with vertical facing 0
vFacing = BisectionSearch(0, info.MaximumLaunchAngle.Angle, vFacing = BisectionSearch(System.Math.Max((sbyte)(info.MinimumLaunchAngle.Angle >> 2), (sbyte)0),
(sbyte)(info.MaximumLaunchAngle.Angle >> 2),
vFac => !WillClimbWithinDistance(vFac, loopRadius, predClfDist, diffClfMslHgt)) + 1; vFac => !WillClimbWithinDistance(vFac, loopRadius, predClfDist, diffClfMslHgt)) + 1;
} }
} }
// TODO: Double check Launch parameter determination
void DetermineLaunchSpeedAndAngle(World world, out int speed, out int vFacing)
{
speed = info.MaximumLaunchSpeed.Length;
loopRadius = LoopRadius(speed, info.VerticalRateOfTurn);
// Compute current distance from target position
var tarDistVec = targetPosition + offset - pos;
var relTarHorDist = tarDistVec.HorizontalLength;
int predClfHgt, predClfDist, lastHtChg, lastHt;
InclineLookahead(world, relTarHorDist, out predClfHgt, out predClfDist, out lastHtChg, out lastHt);
// Height difference between the incline height and missile height
var diffClfMslHgt = predClfHgt - pos.Z;
// Incline coming up
if (diffClfMslHgt >= 0)
DetermineLaunchSpeedAndAngleForIncline(predClfDist, diffClfMslHgt, relTarHorDist, out speed, out vFacing);
else if (lastHt != 0)
{
vFacing = System.Math.Max((sbyte)(info.MinimumLaunchAngle.Angle >> 2), (sbyte)0);
speed = info.MaximumLaunchSpeed.Length;
}
else else
{ {
// Set vertical facing so that the missile faces its target // Set vertical facing so that the missile faces its target
@@ -299,13 +310,13 @@ namespace OpenRA.Mods.Common.Effects
// Will missile be able to climb terrAltDiff w-units within hHeightChange w-units // Will missile be able to climb terrAltDiff w-units within hHeightChange w-units
// all the while ending the ascent with vertical facing 0 // all the while ending the ascent with vertical facing 0
// Calling this function only makes sense when vFacing is nonzero // Calling this function only makes sense when vFacing is nonnegative
static bool WillClimbWithinDistance(int vFacing, int loopRadius, int predClfDist, int diffClfMslHgt) static bool WillClimbWithinDistance(int vFacing, int loopRadius, int predClfDist, int diffClfMslHgt)
{ {
// Missile's horizontal distance from loop's center // Missile's horizontal distance from loop's center
var missDist = loopRadius * WAngle.FromFacing(vFacing).Sin() / 1024; var missDist = loopRadius * WAngle.FromFacing(vFacing).Sin() / 1024;
// Missile's height above loop's center // Missile's height below loop's top
var missHgt = loopRadius * (1024 - WAngle.FromFacing(vFacing).Cos()) / 1024; var missHgt = loopRadius * (1024 - WAngle.FromFacing(vFacing).Cos()) / 1024;
// Height that would be climbed without changing vertical facing // Height that would be climbed without changing vertical facing
@@ -392,13 +403,15 @@ namespace OpenRA.Mods.Common.Effects
// NOTE: It might be desirable to make lookahead more intelligent by outputting more information // NOTE: It might be desirable to make lookahead more intelligent by outputting more information
// than just the highest point in the lookahead distance // than just the highest point in the lookahead distance
void InclineLookahead(World world, int distCheck, out int predClfHgt, out int predClfDist) void InclineLookahead(World world, int distCheck, out int predClfHgt, out int predClfDist, out int lastHtChg, out int lastHt)
{ {
predClfHgt = 0; // Highest probed terrain height predClfHgt = 0; // Highest probed terrain height
predClfDist = 0; // Distance from highest point predClfDist = 0; // Distance from highest point
lastHtChg = 0; // Distance from last time the height changes
lastHt = 0; // Height just before the last height change
// NOTE: Might be desired to unhardcode the lookahead step size // NOTE: Might be desired to unhardcode the lookahead step size
var stepSize = 128; var stepSize = 32;
var step = new WVec(0, -stepSize, 0) var step = new WVec(0, -stepSize, 0)
.Rotate(new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(hFacing))); // Step vector of length 128 .Rotate(new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(hFacing))); // Step vector of length 128
@@ -408,42 +421,37 @@ namespace OpenRA.Mods.Common.Effects
var posProbe = pos; var posProbe = pos;
var curDist = 0; var curDist = 0;
var tickLimit = System.Math.Min(maxLookaheadDistance, distCheck) / stepSize; var tickLimit = System.Math.Min(maxLookaheadDistance, distCheck) / stepSize;
var prevHt = 0;
// TODO: Make sure cell on map!!!
for (var tick = 0; tick <= tickLimit; tick++) for (var tick = 0; tick <= tickLimit; tick++)
{ {
posProbe += step; posProbe += step;
curDist += stepSize; if (!world.Map.Contains(world.Map.CellContaining(posProbe)))
break;
var ht = world.Map.MapHeight.Value[world.Map.CellContaining(posProbe)] * 512; var ht = world.Map.MapHeight.Value[world.Map.CellContaining(posProbe)] * 512;
curDist += stepSize;
if (ht > predClfHgt) if (ht > predClfHgt)
{ {
predClfHgt = ht; predClfHgt = ht;
predClfDist = curDist; predClfDist = curDist;
} }
if (prevHt != ht)
{
lastHtChg = curDist;
lastHt = prevHt;
prevHt = ht;
}
} }
} }
int HomingInnerTick(int predClfDist, int diffClfMslHgt, int relTarHorDist, int IncreaseAltitude(int predClfDist, int diffClfMslHgt, int relTarHorDist, int vFacing)
int nxtRelTarHorDist, int relTarHgt, int vFacing, bool targetPassedBy)
{ {
int desiredVFacing = vFacing; var desiredVFacing = vFacing;
// NOTE: Might be desired to unhardcode the distance from target
// at which the missile no longer cruises at cruise altitude
// but instead keeps trying to hit the target
// It still avoids inclines however
int targetLockonDistance = 2 * loopRadius;
// Incline coming up -> attempt to reach the incline so that after predClfDist
// the height above the terrain is positive but as close to 0 as possible
// Also, never change horizontal facing and never travel backwards
// Possible techniques to avoid close cliffs are deceleration, turning
// as sharply as possible to travel directly upwards and then returning
// to zero vertical facing as low as possible while still not hittin the
// high terrain. A last technique (and the preferred one, normally used when
// the missile hasn't been fired near a cliff) is simply finding the smallest
// vertical facing that allows for a smooth climb to the new terrain's height
// and coming in at predClfDist at exactly zero vertical facing
if (diffClfMslHgt >= 0)
{
// If missile is below incline top height and facing downwards, bring back // If missile is below incline top height and facing downwards, bring back
// its vertical facing above zero as soon as possible // its vertical facing above zero as soon as possible
if ((sbyte)vFacing < 0) if ((sbyte)vFacing < 0)
@@ -463,7 +471,7 @@ namespace OpenRA.Mods.Common.Effects
// for which the missile will be able to climb terrAltDiff w-units // for which the missile will be able to climb terrAltDiff w-units
// within hHeightChange w-units all the while ending the ascent // within hHeightChange w-units all the while ending the ascent
// with vertical facing 0 // with vertical facing 0
for (var vFac = System.Math.Min(vFacing + info.VerticalRateOfTurn - 1, 63); vFac > vFacing; vFac--) for (var vFac = System.Math.Min(vFacing + info.VerticalRateOfTurn - 1, 63); vFac >= vFacing; vFac--)
if (!WillClimbWithinDistance(vFac, loopRadius, predClfDist, diffClfMslHgt) if (!WillClimbWithinDistance(vFac, loopRadius, predClfDist, diffClfMslHgt)
&& !(predClfDist <= loopRadius * (1024 - WAngle.FromFacing(vFac).Sin()) / 1024 && !(predClfDist <= loopRadius * (1024 - WAngle.FromFacing(vFac).Sin()) / 1024
&& WillClimbAroundInclineTop(vFac, loopRadius, predClfDist, diffClfMslHgt, speed))) && WillClimbAroundInclineTop(vFac, loopRadius, predClfDist, diffClfMslHgt, speed)))
@@ -492,12 +500,37 @@ namespace OpenRA.Mods.Common.Effects
if (slowDown) if (slowDown)
ChangeSpeed(-1); ChangeSpeed(-1);
return desiredVFacing;
} }
else if (nxtRelTarHorDist <= 2 * loopRadius || state == States.Hitting)
int HomingInnerTick(int predClfDist, int diffClfMslHgt, int relTarHorDist, int lastHtChg, int lastHt,
int nxtRelTarHorDist, int relTarHgt, int vFacing, bool targetPassedBy)
{
int desiredVFacing = vFacing;
// Incline coming up -> attempt to reach the incline so that after predClfDist
// the height above the terrain is positive but as close to 0 as possible
// Also, never change horizontal facing and never travel backwards
// Possible techniques to avoid close cliffs are deceleration, turning
// as sharply as possible to travel directly upwards and then returning
// to zero vertical facing as low as possible while still not hittin the
// high terrain. A last technique (and the preferred one, normally used when
// the missile hasn't been fired near a cliff) is simply finding the smallest
// vertical facing that allows for a smooth climb to the new terrain's height
// and coming in at predClfDist at exactly zero vertical facing
if (diffClfMslHgt >= 0 && !allowPassBy)
desiredVFacing = IncreaseAltitude(predClfDist, diffClfMslHgt, relTarHorDist, vFacing);
else if (relTarHorDist <= 3 * loopRadius || state == States.Hitting)
{ {
// No longer travel at cruise altitude // No longer travel at cruise altitude
state = States.Hitting; state = States.Hitting;
if (lastHt >= targetPosition.Z)
allowPassBy = true;
if (!allowPassBy && (lastHt < targetPosition.Z || targetPassedBy))
{
// Aim for the target // Aim for the target
var vDist = new WVec(-relTarHgt, -relTarHorDist, 0); var vDist = new WVec(-relTarHgt, -relTarHorDist, 0);
desiredVFacing = (sbyte)OpenRA.Traits.Util.GetFacing(vDist, vFacing); desiredVFacing = (sbyte)OpenRA.Traits.Util.GetFacing(vDist, vFacing);
@@ -514,7 +547,7 @@ namespace OpenRA.Mods.Common.Effects
// to hit the ground prematurely // to hit the ground prematurely
if (targetPassedBy) if (targetPassedBy)
desiredVFacing = desiredVFacing.Clamp(-info.VerticalRateOfTurn, info.VerticalRateOfTurn); desiredVFacing = desiredVFacing.Clamp(-info.VerticalRateOfTurn, info.VerticalRateOfTurn);
else else if (lastHt == 0)
{ // Before the target is passed by, missile speed should be changed { // Before the target is passed by, missile speed should be changed
// Target's height above loop's center // Target's height above loop's center
var tarHgt = (loopRadius * WAngle.FromFacing(vFacing).Cos() / 1024 - System.Math.Abs(relTarHgt)).Clamp(0, loopRadius); var tarHgt = (loopRadius * WAngle.FromFacing(vFacing).Cos() / 1024 - System.Math.Abs(relTarHgt)).Clamp(0, loopRadius);
@@ -534,6 +567,91 @@ namespace OpenRA.Mods.Common.Effects
ChangeSpeed(); ChangeSpeed();
} }
} }
else if (allowPassBy || (lastHt != 0 && relTarHorDist - lastHtChg < loopRadius))
{
// Only activate this part if target too close to cliff
allowPassBy = true;
// Vector from missile's current position pointing to the loop's center
var radius = new WVec(loopRadius, 0, 0)
.Rotate(new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(64 - vFacing)));
// Vector from loop's center to incline top hardcoded in height buffer zone
var edgeVector = new WVec(lastHtChg, lastHt - pos.Z, 0) - radius;
if (!targetPassedBy)
{
// Climb to critical height
if (relTarHorDist > 2 * loopRadius)
{
// Target's distance from cliff
var d1 = relTarHorDist - lastHtChg;
if (d1 < 0)
d1 = 0;
if (d1 > 2 * loopRadius)
return 0;
// Find critical height at which the missile must be once it is at one loopRadius
// away from the target
var h1 = loopRadius - Exts.ISqrt(d1 * (2 * loopRadius - d1)) - (pos.Z - lastHt);
if (h1 > loopRadius * (1024 - WAngle.FromFacing(vFacing).Cos()) / 1024)
desiredVFacing = WAngle.ArcTan(Exts.ISqrt(h1 * (2 * loopRadius - h1)), loopRadius - h1).Angle >> 2;
else
desiredVFacing = 0;
// TODO: deceleration checks!!!
}
else
{
// Avoid the cliff edge
if (edgeVector.Length > loopRadius && lastHt > targetPosition.Z)
{
int vFac;
for (vFac = vFacing + 1; vFac <= vFacing + info.VerticalRateOfTurn - 1; vFac++)
{
// Vector from missile's current position pointing to the loop's center
radius = new WVec(loopRadius, 0, 0)
.Rotate(new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(64 - vFac)));
// Vector from loop's center to incline top + 64 hardcoded in height buffer zone
edgeVector = new WVec(lastHtChg, lastHt - pos.Z, 0) - radius;
if (edgeVector.Length <= loopRadius)
break;
}
desiredVFacing = vFac;
}
else
{
// Aim for the target
var vDist = new WVec(-relTarHgt, -relTarHorDist * (targetPassedBy ? -1 : 1), 0);
desiredVFacing = (sbyte)OpenRA.Traits.Util.GetFacing(vDist, vFacing);
if (desiredVFacing < 0 && info.VerticalRateOfTurn < (sbyte)vFacing)
desiredVFacing = 0;
}
}
}
else
{
// Aim for the target
var vDist = new WVec(-relTarHgt, -relTarHorDist * (targetPassedBy ? -1 : 1), 0);
desiredVFacing = (sbyte)OpenRA.Traits.Util.GetFacing(vDist, vFacing);
if (desiredVFacing < 0 && info.VerticalRateOfTurn < (sbyte)vFacing)
desiredVFacing = 0;
}
}
else
{
// Aim to attain cruise altitude as soon as possible while having the absolute value
// of vertical facing bound by the maximum vertical rate of turn
var vDist = new WVec(-diffClfMslHgt - info.CruiseAltitude.Length, -speed, 0);
desiredVFacing = (sbyte)OpenRA.Traits.Util.GetFacing(vDist, vFacing);
desiredVFacing = desiredVFacing.Clamp(-info.VerticalRateOfTurn, info.VerticalRateOfTurn);
ChangeSpeed();
}
}
else else
{ {
// Aim to attain cruise altitude as soon as possible while having the absolute value // Aim to attain cruise altitude as soon as possible while having the absolute value
@@ -550,9 +668,8 @@ namespace OpenRA.Mods.Common.Effects
WVec HomingTick(World world, WVec tarDistVec, int relTarHorDist) WVec HomingTick(World world, WVec tarDistVec, int relTarHorDist)
{ {
int predClfHgt; int predClfHgt, predClfDist, lastHtChg, lastHt;
int predClfDist; InclineLookahead(world, relTarHorDist, out predClfHgt, out predClfDist, out lastHtChg, out lastHt);
InclineLookahead(world, relTarHorDist, out predClfHgt, out predClfDist);
// Height difference between the incline height and missile height // Height difference between the incline height and missile height
var diffClfMslHgt = predClfHgt - pos.Z; var diffClfMslHgt = predClfHgt - pos.Z;
@@ -565,10 +682,20 @@ namespace OpenRA.Mods.Common.Effects
// Compute which direction the projectile should be facing // Compute which direction the projectile should be facing
var desiredHFacing = OpenRA.Traits.Util.GetFacing(tarDistVec + predVel, hFacing); var desiredHFacing = OpenRA.Traits.Util.GetFacing(tarDistVec + predVel, hFacing);
var desiredVFacing = HomingInnerTick(predClfDist, diffClfMslHgt, relTarHorDist, nxtRelTarHorDist, relTarHgt, vFacing, targetPassedBy);
// The target will be passed by at the end of the tick if (allowPassBy && System.Math.Abs(desiredHFacing - hFacing) >= System.Math.Abs(desiredHFacing + 128 - hFacing))
if (nxtRelTarHorDist == 0) {
desiredHFacing += 128;
targetPassedBy = true;
}
else
targetPassedBy = false;
var desiredVFacing = HomingInnerTick(predClfDist, diffClfMslHgt, relTarHorDist, lastHtChg, lastHt,
nxtRelTarHorDist, relTarHgt, vFacing, targetPassedBy);
// The target has been passed by
if (tarDistVec.HorizontalLength < speed * WAngle.FromFacing(vFacing).Cos() / 1024)
targetPassedBy = true; targetPassedBy = true;
// Check whether the homing mechanism is jammed // Check whether the homing mechanism is jammed