- Non-Rotating Shooter
- Rotating Shooter
- The position of the shooter when the projectile will be launched: \(\vec{P_s}\)
- The position of the target when the shooter will launch the projectile (i.e. now): \(\vec{P_T^0}\)
- The speed at which your projectiles travel: \(S_b\)
- The velocity of the target, \(\vec{v_T}\)
The Problem Space
Consider the image below:![post_images_hitting_targets_with_bullets.png](https://uploads.gamedev.net/monthly_10_2014/ccs-224713-0-51408400-1414694120.png)
(1) \(\vec{P_T^1} = \vec{P_T^0} + \vec{v_T} * t_B\)
Since the projectile will have traveled for the same amount of time, it will have moved from \(\vec{P_s}\) to \(\vec{P_T^1}\) as well. In that time, it will have moved a distance of \(S_B x t_B\). Since we are talking about vector quantities here, we can write this as:
\(\mid\vec{P_T^1}-\vec{P_s}\mid = S_b * t_B\)
If we square both sides and break it into components to get rid of the absolute value:
(2) \((P_{Tx}^1 - P_{Sx})^2 +(P_{Ty}^1 - P_{Sy})^2 = S_b^2 * t_B^2\)
Breaking (1) into components as well and substituting back into (2) for the value of \(P_{Tx}^1\) and \(P_{Ty}^1\), we get the following:
\((P_{T0x} - P_{Sx} + v_{Tx}t_B)^2 + (P_{T0y} - P_{Sy} + v_{Ty}t_B)^2 = S_b^2 * t_B^2\)
For the sake of simplicity, we going to redefine:
\(P_T^0 - P_s = R \)(this is a constant)
After some algebra, this gives us the final equation:
\(t_B^2(v_{Tx}^2 + v_{Ty}^2-S_B^2) + t_B(2*R_x*v_{Tx} + 2*R_y*v_{Ty}) + (R_x^2 + R_y^2) = 0\)
This is a quadratic in \(t_B\):
\(t_b = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}\)
where:\( a =v_{Tx}^2 + v_y^2-S_B^2\)
\( b =2(R_x*v_{Tx} + R_y*v_{Ty})\)
\( c = R_x^2 + R_y^2\)
You can test the discriminant, \(b^2-4ac\):
< 0 \(\Rightarrow\) No Solution.
= 0 \(\Rightarrow\) One solution.
> 0 \(\Rightarrow\) Two solutions, pick the lowest positive value of \(t_B\).
Once you have solved the quadratic for \(t_B\), you can then substitute it back into (1) and calculate the intercept position, \(\vec{P_T^1}\).
The Code
Putting this together and covering some edge cases: /* Calculate the future position of a moving target so that * a projectile launched immediately can intercept (collide) * with it. * * Some situations where this might be useful for an AI to * make this calculation. * * 1. Shooting a projectile at a moving target. * 2. Launching a football or soccer ball to a player. * 3. Figuring out the best position to jump towards in * a platform game. * * * The output value, solution, is the position that the * intercept will occur at and the location that the * projectile should be launched towards. * * The function will return false if a solution cannot * be found. Consider the case of a target moving away * from the shooter faster than the speed of the * projectile and you will see at least one case where * this calculation may fail. */ bool CalculateInterceptShotPosition(const Vec2& pShooter, const Vec2& pTarget0, const Vec2& vTarget, float64 sProjectile, Vec2& solution ) { // This formulation uses the quadratic equation to solve // the intercept position. Vec2 R = pTarget0 - pShooter; float64 a = vTarget.x*vTarget.x + vTarget.y*vTarget.y - sProjectile*sProjectile; float64 b = 2*(R.x*vTarget.x + R.y*vTarget.y); float64 c = R.x*R.x + R.y*R.y; float64 tBullet = 0; // If the target and the shooter have already collided, don't bother. if(R.LengthSquared() < 2*DBL_MIN) { return false; } // If the squared velocity of the target and the bullet are the same, the equation // collapses to tBullet*b = -c. If they are REALLY close to each other (float tol), // you could get some weirdness here. Do some "is it close" checking? if(fabs(a) < 2*DBL_MIN) { // If the b value is 0, we can't get a solution. if(fabs(b) < 2*DBL_MIN) { return false; } tBullet = -c/b; } else { // Calculate the discriminant to figure out how many solutions there are. float64 discriminant = b*b - 4 * a * c; if(discriminant < 0) { // All solutions are complex. return false; } if (discriminant > 0) { // Two solutions. Pick the smaller one. // Calculate the quadratic. float64 quad = sqrt(discriminant); float64 tBullet1 = (-b + quad)/(2*a); float64 tBullet2 = (-b - quad)/(2*a); if((tBullet1 < 0) && (tBullet2 < 0)) { // This would be really odd. // Both times are negative. return false; } else if(tBullet2 < 0 && tBullet1 >= 0) { // One negative, one positive. tBullet = tBullet1; } else if(tBullet1 < 0 && tBullet2 >= 0) { // One negative, one positive. tBullet = tBullet2; } else if(tBullet1 < tBullet2) { // First less than second tBullet = tBullet1; } else { // Only choice left tBullet = tBullet2; } } else { tBullet = -b / (2*a); } } // If the time is negative, we can't get there from here. if(tBullet < 0) { return false; } // Calculate the intercept position. solution = pTarget0 + tBullet*vTarget; return true; }
I have posted a working solution, with a simulation of using the above function and which you can tinker with, on github.
I would not have guesses that it will be so complicated, but it is really useful, thanks!