5

Assuming I have a spaceship (source); And an asteroid (target) is somewhere near it.

I know, in 3D space (XYZ vectors):

  • My ship's position (sourcePos) and velocity (sourceVel).
  • The asteroid's position (targetPos) and velocity (targetVel).

(eg. sourcePos = [30, 20, 10]; sourceVel = [30, 20, 10]; targetPos = [600, 400, 200]; targetVel = [300, 200, 100]`)

I also know that:

  • The ship's velocity is constant.
  • The asteroid's velocity is constant.
  • My ship's projectile speed (projSpd) is constant.
  • My ship's projectile trajectory, after being shot, is linear (/straight).

(eg. projSpd = 2000.00)

How can I calculate the interception coordinates I need to shoot at in order to hit the asteroid?


Notes:

This question is based on this Yahoo - Answers page.

I also searched for similar problems on Google and here on SO, but most of the answers are for 2D-space, and, of the few for 3D, neither the explanation nor the pseudo-codes explain what is doing what and/or why, so I couldn't really understand enough to apply them on my code successfully. Here are some of the pages I visited:

Danik Games Devlog, Blitz3D Forums thread, UnityAnswers, StackOverflow #1, StackOverflow #2


I really can't figure out the maths / execution-flow on the linked pages as they are, unless someone dissects it (further) into what is doing what, and why;
Provides a properly-commented pseudo-code for me to follow;
Or at least points me to links that actually explain how the equations work instead of just throwing even more random numbers and unfollowable equations in my already-confused psyche.

Community
  • 1
  • 1
CosmicGiant
  • 6,275
  • 5
  • 43
  • 58
  • The yahoo answer you link to seems clear and well written. If that's not sufficient, you'll need to be very clear about what you don't understand. – tom10 Jun 20 '13 at 03:28
  • @tom10 Being specific to that particular page, it's not clear to me how to resolve the time (`t`) variable, nor what is the order of the equations, since, in the answer's text, `t` seems to be used before it's resolved. --- The purpose / logical-result of some of the equations is not explained either (eg *"Plugging in the 3 above equations and multiplying both sides by t^2 yields: ..."* as well as the equation that follows) and there is even a typing error in the equation itself (*"... + 2 **&** Yvar*Yar*t + ..."*) --- I run into similar problems with all other sources. – CosmicGiant Jun 20 '13 at 03:55

5 Answers5

13

I find the easiest approach to these kind of problems to make sense of them first, and have a basic high school level of maths will help too.

Solving this problem is essentially solving 2 equations with 2 variables which are unknown to you:

  1. The vector you want to find for your projectile (V)
  2. The time of impact (t)

The variables you know are:

  1. The target's position (P0)
  2. The target's vector (V0)
  3. The target's speed (s0)
  4. The projectile's origin (P1)
  5. The projectile's speed (s1)

Okay, so the 1st equation is basic. The impact point is the same for both the target and the projectile. It is equal to the starting point of both objects + a certain length along the line of both their vectors. This length is denoted by their respective speeds, and the time of impact. Here's the equation:

P0 + (t * s0 * V0) = P1 + (t * s0 * V)

Notice that there are two missing variables here - V & t, and so we won't be able to solve this equation right now. On to the 2nd equation.

The 2nd equation is also quite intuitive. The point of impact's distance from the origin of the projectile is equal to the speed of the projectile multiplied by the time passed:

We'll take a mathematical expression of the point of impact from the 1st equation:

P0 + (t * s0 * V0) <-- point of impact

The point of origin is P1 The distance between these two must be equal to the speed of the projectile multiplied by the time passed (distance = speed * time).

The formula for distance is: (x0 - x1)^2 + (y0 - y1)^2 = distance^2, and so the equation will look like this:

((P0.x + s0 * t * V0.x) - P1.x)^2 + ((P0.y + s0 * t * V0.y) - P1.y)^2 = (s1 * t)^2 

(You can easily expand this for 3 dimensions)

Notice that here, you have an equation with only ONE unknown variable: t!. We can discover here what t is, then place it in the previous equation and find the vector V.

Let me solve you some pain by opening up this formula for you (if you really want to, you can do this yourself).

a = (V0.x * V0.x) + (V0.y * V0.y) - (s1 * s1)
b = 2 * ((P0.x * V0.x) + (P0.y * V0.y) - (P1.x * V0.x) - (P1.y * V0.y))
c = (P0.x * P0.x) + (P0.y * P0.y) + (P1.x * P1.x) + (P1.y * P1.y) - (2 * P1.x * P0.x) - (2 * P1.y * P0.y)

t1 = (-b + sqrt((b * b) - (4 * a * c))) / (2 * a)
t2 = (-b - sqrt((b * b) - (4 * a * c))) / (2 * a)

Now, notice - we will get 2 values for t here.

One or both may be negative or an invalid number. Obviously, since t denotes time, and time can't be invalid or negative, you'll need to discard these values of t.

It could very well be that both t's are bad (in which case, the projectile cannot hit the target since it's faster and out of range). It could also be that both t's are valid and positive, in which case you'll want to choose the smaller of the two (since it's preferable to hit the target sooner rather than later).

t = smallestWhichIsntNegativeOrNan(t1, t2)

Now that we've found the time of impact, let's find out what the direction the projectile should fly is. Back to our 1st equation:

P0 + (t * s0 * V0) = P1 + (t * s0 * V)

Now, t is no longer a missing variable, so we can solve this quite easily. Just tidy up the equation to isolate V:

V = (P0 - P1 + (t * s0 * V0)) / (t * s1)
V.x = (P0.x - P1.x + (t * s0 * V0.x)) / (t * s1) 
V.y = (P0.y - P1.y + (t * s0 * V0.y)) / (t * s1) 

And that's it, you're done! Assign the vector V to the projectile and it will go to where the target will be rather than where it is now.

I really like this problem since it takes math equations we learnt in high school where everyone said "why are learning this?? we'll never use it in our lives!!", and gives them a pretty awesome and practical application.

I hope this helps you, or anyone else who's trying to solve this.

Gil Moshayof
  • 16,633
  • 4
  • 47
  • 58
  • very nice. i arrive at the same solution. i make a small simplification, which is to define an intermediate vector `DP = P1 - P0`. This results in B and especially C being somewhat simpler: `B = 2 * ((DP.x * V0.x) + (DP.y * V0.y))`, `C = (DP.x * DP.x) + (DP.y * DP.y)`. – orion elenzil May 08 '15 at 04:28
  • I've been wrestling with a math weirdness with this solution. the issue is that the `a` term for the quadratic is `a = V0.x^2+ V0.y^2 -s1^2`. which means that if the projectile and the target have the same speed, then `a = 0`. but that's bad-news because we divide by `2a` in the quadratic. Usually if the quadratic results in ∞ or sqrt(<0) it's meaningful, like the problem has no solution. but saying "if two particles have the same speed then they can never intersect" is crazy. i've seen people just special-case `a = 0`, but that's unsatisfying. anyone see the flaw in the reasoning here ? – orion elenzil May 09 '15 at 23:22
  • 1
    A friend helped me see why you have to special-case `a = 0`, which is simply that the quadratic equation is not valid for solving linear equations. – orion elenzil May 11 '15 at 16:33
  • How to handle a target that has a radius? It's not just point? – kriper Oct 27 '21 at 07:20
  • This approach just produces the collision vector, aimed at the center of where the target will be. If the equation produces no results, you could retry the equation where you set the "center" of the target around the circumference, perhaps starting from the point that intersects with the target's vector. – Gil Moshayof Jan 25 '22 at 09:23
  • I agree that 'You can easily expand this for 3 dimensions', however how to plug into the quadratic equation for 3D doesn't see so easy, at least for those that haven't used the quadratic formula in years! – user3015682 May 17 '22 at 00:11
  • This answer is almost right. There are some mistakes however. 1. P0 + (t * s0 * V0) = P1 + (t * s0 * V) is wrong, there should be s1 on the right side. There are two instances of this mistake 2. in "a" and "b" terms, V0 should be replaced with s0*V0 everywhere 3. Not all cases are covered i) a==0, b==0, c==0 (infinite solutions) ii) a==0, b==0 c!=0 (no solution) iii) a==0, b!=0 (t = -c/b) Please, consider correcting the errors and adding all possible cases to your otherwise very useful answer. If the answer is right and this comment is not, I deeply apologize. – EpsilonDelta Jun 25 '23 at 21:12
2

If you want a projectile to hit asteroid, it should be shoot at the point interceptionPos that satisfy the equation:

|interceptionPos - sourcePos| / |interceptionPos - targetPos| = projSpd / targetVel
where |x| is a length of vector x.

In other words, it would take equal amount of time for the target and the projectile to reach this point.
This problem would be solved by means of geometry and trigonometry, so let's draw it. enter image description here
A will be asteroid position, S - ship, I - interception point. Here we have:

AI = targetVel * t
SI = projSpd * t
AS = |targetPos - sourcePos|

vector AS and AI direction is defined, so you can easily calculate cosine of the SAI angle by means of simple vector math (take definitions from here and here). Then you should use the Law of cosines with the SAI angle. It will yield a quadratic equation with variable t that is easy to solve (no solutions = your projectile is slower than asteroid). Just pick the positive solution t, your point-to-shoot will be

targetPos + t * targetVel

I hope you can write a code to solve it by yourself. If you cannot get something please ask in comments.

Chechulin
  • 2,426
  • 7
  • 28
  • 35
  • Your answer is a lot more clear on explaining what has to be done and how, and it has already helped me understand a lot more. --- Still, not everything is clear yet and I don't really understand how some of the linked equations work for 3D space. --- The first *"take definitions"* link provides for 3D-space resolution, but the second, as well as the one for *"Law of cosines"*, don't. In particular, I'm unsure if I'd need a second instance of the equations to resolve the height (Z) axis, or if the Z axis can be inserted in. eg: `A * B * C(z) = ||Ax||*||By||*||C||*cos(º)` works? – CosmicGiant Jun 20 '13 at 13:34
  • It may cause a misunderstanding, but Law of cosines works only for a 2D triangle. Because triangle is a 2D shape. All you have to do is to move your problem from 3D to 2D. It can be done because target, projectile and their trajectories lies in a single plane. – Chechulin Jun 21 '13 at 04:39
  • And the equation from the Law of cosines should be solved disregarding x-y-z coordinates because it only operates with lengths. – Chechulin Jun 21 '13 at 04:40
  • I don't quite understand how the "target, projectile and their trajectories lies in a single plane." works. It's not clear to me where the transition to 2D is / should be. Can you explain? – CosmicGiant Jul 05 '13 at 17:48
  • If 2 lines intersect, they lie in one plane. For details you can read this theorem: http://math.tutorcircle.com/geometry/plane.html#theorem-%28if-two-lines-intersect,-then-exactly-one-plane-contains-both-lines%29 – Chechulin Jul 08 '13 at 07:55
  • In other words, you assume that the projectile will hit target, hense their trajectories intersects. And that means that they lie in one plane. – Chechulin Jul 08 '13 at 07:57
2

I got a solution. Notice that the ship position, and the asteroid line (position and velocity) define a 3D plane where the intercept point lies. In my notation below | [x,y,z] | denotes the magnitude of the vector or Sqrt(x^2+y^2+z^2).

Notice that if the asteroid travels with targetSpd = |[300,200,100]| = 374.17 then to reach the intercept point (still unknown, called hitPos) will require time equal to t = |hitPos-targetPos|/targetSpd. This is the same time the projectile needs to reach the intercept point, or t = |hitPos - sourcePos|/projSpd. The two equations are used to solve for the time to intercept

t = |targetPos-sourcePos|/(projSpd - targetSpd)
  = |[600,400,200]-[30,20,10]|/(2000 - |[300,200,100]|)
  = 710.81 / ( 2000-374.17 ) = 0.4372

Now the location of the intetception point is found by

hitPos = targetPos + targetVel * t
       = [600,400,200] + [300,200,100] * 0.4372
       = [731.18, 487.45, 243.73 ]

Now that I know the hit position, I can calculate the direction of the projectile as

projDir = (hitPos-sourcePos)/|hitPos-sourcePos|
        = [701.17, 467.45, 233.73]/874.52 = [0.8018, 0.5345, 0.2673]

Together the projDir and projSpd define the projectile velocity vector.

John Alexiou
  • 28,472
  • 11
  • 77
  • 133
2

I followed the problem formulation as described by Gil Moshayof's answer, but found that there was an error in the simplification of the quadratic formula. When I did the derivation by hand I got a different solution.

The following is what worked for me when finding the intersect in 2D:

std::pair<double, double> find_2D_intersect(Vector3 sourcePos, double projSpd, Vector3 targetPos, double targetSpd, double targetHeading)
{
    double P0x = targetPos.x; 
    double P0y = targetPos.y;
    double s0 = targetSpd;
    double V0x = std::cos(targetHeading);
    double V0y = std::sin(targetHeading);
    double P1x = sourcePos.x;
    double P1y = sourcePos.y;
    double s1 = projSpd;

    // quadratic formula
    double a = (s0 * s0)*((V0x * V0x) + (V0y * V0y)) - (s1 * s1);
    double b = 2 * s0 * ((P0x * V0x) + (P0y * V0y) - (P1x * V0x) - (P1y * V0y));
    double c = (P0x * P0x) + (P0y * P0y) + (P1x * P1x) + (P1y * P1y) - (2 * P1x * P0x) - (2 * P1y * P0y);

    double t1 = (-b + std::sqrt((b * b) - (4 * a * c))) / (2 * a);
    double t2 = (-b - std::sqrt((b * b) - (4 * a * c))) / (2 * a);

    double t = choose_best_time(t1, t2);

    double intersect_x = P0x + t * s0 * V0x;
    double intersect_y = P0y + t * s0 * V0y;
    return std::make_pair(intersect_x, intersect_y);
}
Brady Moon
  • 21
  • 1
1

Credit to Gil Moshayof's answer, as it really was what I worked off of to build this. But they did two dimensions, and I did three, so I'll share my Unity code in case it helps anyone along. A little long winded and redundant. It helps me to read it and know what's going on.

Vector3 CalculateIntercept(Vector3 targetLocation, Vector3 targetVelocity, Vector3 interceptorLocation, float interceptorSpeed)
{

    Vector3 A = targetLocation;

    float Ax = targetLocation.x;
    float Ay = targetLocation.y;
    float Az = targetLocation.z;

    float As = targetVelocity.magnitude;
    Vector3 Av = Vector3.Normalize(targetVelocity);
    float Avx = Av.x;
    float Avy = Av.y;
    float Avz = Av.z;

    Vector3 B = interceptorLocation;

    float Bx = interceptorLocation.x;
    float By = interceptorLocation.y;
    float Bz = interceptorLocation.z;

    float Bs = interceptorSpeed;

    float t = 0;

    float a = (
        Mathf.Pow(As, 2) * Mathf.Pow(Avx, 2) +
        Mathf.Pow(As, 2) * Mathf.Pow(Avy, 2) +
        Mathf.Pow(As, 2) * Mathf.Pow(Avz, 2) -
        Mathf.Pow(Bs, 2)
        );

    if (a == 0)
    {
        Debug.Log("Quadratic formula not applicable");
        return targetLocation;
    }

    float b = (
        As * Avx * Ax +
        As * Avy * Ay +
        As * Avz * Az +
        As * Avx * Bx +
        As * Avy * By +
        As * Avz * Bz
        );

    float c = (
        Mathf.Pow(Ax, 2) +
        Mathf.Pow(Ay, 2) +
        Mathf.Pow(Az, 2) -
        Ax * Bx -
        Ay * By -
        Az * Bz +
        Mathf.Pow(Bx, 2) +
        Mathf.Pow(By, 2) +
        Mathf.Pow(Bz, 2)
        );

    float t1 = (-b + Mathf.Pow((Mathf.Pow(b, 2) - (4 * a * c)), (1 / 2))) / (2 * a);
    float t2 = (-b - Mathf.Pow((Mathf.Pow(b, 2) - (4 * a * c)), (1 / 2))) / (2 * a);

    Debug.Log("t1 = " + t1 + "; t2 = " + t2);

    if (t1 <= 0 || t1 == Mathf.Infinity || float.IsNaN(t1))
        if (t2 <= 0 || t2 == Mathf.Infinity || float.IsNaN(t2))
            return targetLocation;
        else
            t = t2;
    else if (t2 <= 0 || t2 == Mathf.Infinity || float.IsNaN(t2) || t2 > t1)
        t = t1;
    else
        t = t2;

    Debug.Log("t = " + t);
    Debug.Log("Bs = " + Bs);

    float Bvx = (Ax - Bx + (t * As + Avx)) / (t * Mathf.Pow(Bs, 2));
    float Bvy = (Ay - By + (t * As + Avy)) / (t * Mathf.Pow(Bs, 2));
    float Bvz = (Az - Bz + (t * As + Avz)) / (t * Mathf.Pow(Bs, 2));

    Vector3 Bv = new Vector3(Bvx, Bvy, Bvz);

    Debug.Log("||Bv|| = (Should be 1) " + Bv.magnitude);

    return Bv * Bs;

}
  • A problem in your quadratic formula simplification: `(1 / 2)` will return `0` since you're doing integer division. It should be `0.5f` or `1f / 2f`. – Jschiff Feb 02 '23 at 00:50