I'm trying to predetermine if a projectile will hit a target (before the projectile has been fired). The projectile settings include an Initial Speed, Acceleration, Max Speed, and a Turn Rate. I'm only concerned about heading currently, but hoping any solution can also apply to pitch as well if I change my axis.

So far, this is what I've attempted:

  1. Calculate "time to target" using a direct distance and speed calculations
    • I know this isn't perfect since the projectile isn't pointed at the target, but represents a "best case" scenario I thought
  2. Calculate the angle to target, using the projectile forward and the direction to the target
  3. Check if time to target * turn rate >= angle to target

The hope was that if the check was true, then the projectile could steer to the target successfully. But that's not what I'm experiencing... instead the angles I'm calculating seem to far exceed the actual projectile movement.

I'm working in Lua, using a vector library (but no matrices).

Here's the pertinent bit of the code I'm using, simplified a bit for readability:

function checkAim()
    -- Get Distance to target
    local distToTarget = getDistance(targetPos, fireLocation)
    -- Get seconds to target (needs to consider acceleration)
    --  solve for dist to max velocity
    local accelTime, accelDist = timeToMaxSpeed(initialSpeed, maxSpeed, acceleration)

    --  if distToMax < distToTarget, use time = (-b +- sqrt(b^2-4ac)) / 2a
    if( accelDist > distToTarget ) then
        timeToTarget = math.max(quadraticEquation(0.5*acceleration, initialSpeed, -distToTarget))
    --  else solve for time to max velocity, solve for time at max to travel distToTarget - distToMax
    --      and add time to max velocity + time at max
        local remainingDist = distToTarget - accelDist
        local remainingTime = remainingDist / maxSpeed
        timeToTarget = accelTime + remainingTime
    local headingDiff = math.deg(math.atan2(DotProduct(CrossProduct(gunFwd, dirToTarget), {x=0,y=1,z=0}), DotProduct(gunFwd,dirToTarget)))
    if( math.abs(headingDiff) > headingTurnRate * timeToTarget ) then
        aimInvalid = true
        local pitchDiff = math.deg(math.atan2(DotProduct(CrossProduct(gunFwd, dirToTarget), {x=1,y=0,z=0}), DotProduct(gunFwd,dirToTarget)))
        if( math.abs(pitchDiff) > pitchTurnRate * timeToTarget ) then
            aimInvalid = true

function timeToMaxSpeed(initialSpeed, maxSpeed, acceleration)
    local t = (maxSpeed - initialSpeed) / acceleration
    local d = (initialSpeed * t) + (0.5 * acceleration * t * t)
    return t, d

function quadraticEquation(a, b, c)
    local fourAC = 4 * a * c
    local twoA = 2 * a
    local bSquared = b * b
    local pos = (-b + math.sqrt(bSquared- fourAC)) / (twoA)
    local neg = (-b - math.sqrt(bSquared- fourAC)) / (twoA)
    return pos, neg
  • \$\begingroup\$ If you're able to remove acceleration and accept constant-speed projectiles, then the calculation becomes much simpler (it amounts to checking whether the target is inside or outside a pair of unreachable discs in 2D, or an unreachable torus in 3D, moves together with the launch point). If you need acceleration, then describing this unreachable zone gets much more complicated. \$\endgroup\$
    – DMGregory
    Jan 31, 2022 at 18:51
  • \$\begingroup\$ I can't remove acceleration at this point, but would love to hear about this disc method if you have a reference available. Maybe I can apply it with some modification. \$\endgroup\$
    – Charles S.
    Jan 31, 2022 at 21:15


You must log in to answer this question.

Browse other questions tagged .