Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>I wrote an aiming subroutine for <a href="http://quozl.netrek.org/xtank/" rel="noreferrer">xtank</a> a while back. I'll try to lay out how I did it.</p> <p><strong>Disclaimer:</strong> I may have made one or more silly mistakes anywhere in here; I'm just trying to reconstruct the reasoning with my rusty math skills. However, I'll cut to the chase first, since this is a programming Q&amp;A instead of a math class :-)</p> <h3>How to do it</h3> <p>It boils down to solving a quadratic equation of the form: </p> <pre><code>a * sqr(x) + b * x + c == 0 </code></pre> <p>Note that by <code>sqr</code> I mean square, as opposed to square root. Use the following values:</p> <pre><code>a := sqr(target.velocityX) + sqr(target.velocityY) - sqr(projectile_speed) b := 2 * (target.velocityX * (target.startX - cannon.X) + target.velocityY * (target.startY - cannon.Y)) c := sqr(target.startX - cannon.X) + sqr(target.startY - cannon.Y) </code></pre> <p>Now we can look at the discriminant to determine if we have a possible solution.</p> <pre><code>disc := sqr(b) - 4 * a * c </code></pre> <p>If the discriminant is less than 0, forget about hitting your target -- your projectile can never get there in time. Otherwise, look at two candidate solutions:</p> <pre><code>t1 := (-b + sqrt(disc)) / (2 * a) t2 := (-b - sqrt(disc)) / (2 * a) </code></pre> <p>Note that if <code>disc == 0</code> then <code>t1</code> and <code>t2</code> are equal.</p> <p>If there are no other considerations such as intervening obstacles, simply choose the smaller positive value. (Negative <em>t</em> values would require firing backward in time to use!)</p> <p>Substitute the chosen <code>t</code> value back into the target's position equations to get the coordinates of the leading point you should be aiming at:</p> <pre><code>aim.X := t * target.velocityX + target.startX aim.Y := t * target.velocityY + target.startY </code></pre> <h3>Derivation</h3> <p>At time T, the projectile must be a (Euclidean) distance from the cannon equal to the elapsed time multiplied by the projectile speed. This gives an equation for a circle, parametric in elapsed time.</p> <pre><code>sqr(projectile.X - cannon.X) + sqr(projectile.Y - cannon.Y) == sqr(t * projectile_speed) </code></pre> <p>Similarly, at time T, the target has moved along its vector by time multiplied by its velocity:</p> <pre><code>target.X == t * target.velocityX + target.startX target.Y == t * target.velocityY + target.startY </code></pre> <p>The projectile can hit the target when its distance from the cannon matches the projectile's distance.</p> <pre><code>sqr(projectile.X - cannon.X) + sqr(projectile.Y - cannon.Y) == sqr(target.X - cannon.X) + sqr(target.Y - cannon.Y) </code></pre> <p>Wonderful! Substituting the expressions for target.X and target.Y gives</p> <pre><code>sqr(projectile.X - cannon.X) + sqr(projectile.Y - cannon.Y) == sqr((t * target.velocityX + target.startX) - cannon.X) + sqr((t * target.velocityY + target.startY) - cannon.Y) </code></pre> <p>Substituting the other side of the equation gives this:</p> <pre><code>sqr(t * projectile_speed) == sqr((t * target.velocityX + target.startX) - cannon.X) + sqr((t * target.velocityY + target.startY) - cannon.Y) </code></pre> <p>... subtracting <code>sqr(t * projectile_speed)</code> from both sides and flipping it around:</p> <pre><code>sqr((t * target.velocityX) + (target.startX - cannon.X)) + sqr((t * target.velocityY) + (target.startY - cannon.Y)) - sqr(t * projectile_speed) == 0 </code></pre> <p>... now resolve the results of squaring the subexpressions ...</p> <pre><code>sqr(target.velocityX) * sqr(t) + 2 * t * target.velocityX * (target.startX - cannon.X) + sqr(target.startX - cannon.X) + sqr(target.velocityY) * sqr(t) + 2 * t * target.velocityY * (target.startY - cannon.Y) + sqr(target.startY - cannon.Y) - sqr(projectile_speed) * sqr(t) == 0 </code></pre> <p>... and group similar terms ...</p> <pre><code>sqr(target.velocityX) * sqr(t) + sqr(target.velocityY) * sqr(t) - sqr(projectile_speed) * sqr(t) + 2 * t * target.velocityX * (target.startX - cannon.X) + 2 * t * target.velocityY * (target.startY - cannon.Y) + sqr(target.startX - cannon.X) + sqr(target.startY - cannon.Y) == 0 </code></pre> <p>... then combine them ...</p> <pre><code>(sqr(target.velocityX) + sqr(target.velocityY) - sqr(projectile_speed)) * sqr(t) + 2 * (target.velocityX * (target.startX - cannon.X) + target.velocityY * (target.startY - cannon.Y)) * t + sqr(target.startX - cannon.X) + sqr(target.startY - cannon.Y) == 0 </code></pre> <p>... giving a standard quadratic equation in <em>t</em>. Finding the positive real zeros of this equation gives the (zero, one, or two) possible hit locations, which can be done with the quadratic formula:</p> <pre><code>a * sqr(x) + b * x + c == 0 x == (-b ± sqrt(sqr(b) - 4 * a * c)) / (2 * a) </code></pre>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload