I've done something similar in the past and I used a quadratic equation to find the time t where the two paths (target and bullet) intersect.
This is what it looks like when implemented using libGDXs classes;
private static Vector2 sub(Vector2 lhs, Vector2 rhs) { return (new Vector2(lhs)).sub(rhs); } private static Vector2 calculateFireDirection(Vector2 targetPosition, Vector2 turretPosition, Vector2 targetVelocity, float bulletSpeed) { Vector2 turretToTarget = sub(targetPosition, turretPosition); float a = targetVelocity.x*targetVelocity.x + targetVelocity.y * targetVelocity.y - bulletSpeed*bulletSpeed; float b = 2.0f * targetVelocity.dot(turretToTarget); float p = -b / (2 * a); float q = (float)Math.sqrt((b * b) - 4 * a * (turretToTarget.x * turretToTarget.y + turretToTarget.y * turretToTarget.y)) / (2 * a); float t = Math.max(p - q, p + q); if (t > 0) { Vector2 tp = new Vector2(targetPosition); Vector2 tv = new Vector2(targetVelocity); Vector2 collisionPoint = tp.add(tv); return sub(collisionPoint, turretPosition); } else return null; // This means the collision will never happen (or happened before the shot was fired, which is nonsensical). }
I the formulas from somewhere, but I can't remember where.
A full program sample program;
public class Program { private static Vector2 sub(Vector2 lhs, Vector2 rhs) { return (new Vector2(lhs)).sub(rhs); } private static Vector2 calculateFireDirection(Vector2 targetPosition, Vector2 turretPosition, Vector2 targetVelocity, float bulletSpeed) { Vector2 turretToTarget = sub(targetPosition, turretPosition); float a = targetVelocity.x*targetVelocity.x + targetVelocity.y * targetVelocity.y - bulletSpeed*bulletSpeed; float b = 2.0f * targetVelocity.dot(turretToTarget); float p = -b / (2 * a); float q = (float)Math.sqrt((b * b) - 4 * a * (turretToTarget.x * turretToTarget.y + turretToTarget.y * turretToTarget.y)) / (2 * a); float t = Math.max(p - q, p + q); if (t > 0) { Vector2 tp = new Vector2(targetPosition); Vector2 tv = new Vector2(targetVelocity); Vector2 collisionPoint = tp.add(tv); return sub(collisionPoint, turretPosition); } else return null; // This means the collision will never happen (or happened before the shot was fired, which is non-sensical). } private static void runSimulation(Vector2 targetPosition, Vector2 turretPosition, Vector2 targetVelocity, float bulletSpeed) { Vector2 direction = calculateFireDirection(targetPosition, turretPosition, targetVelocity, bulletSpeed); for(float t = 0; t < 10.0f; t += 0.1f) { Vector2 tsp = new Vector2(targetPosition); Vector2 tv = new Vector2(targetVelocity); Vector2 ssp = new Vector2(turretPosition); Vector2 sv = new Vector2(direction); Vector2 tp = tsp.add(tv.scl(t)); Vector2 sp = ssp.add(sv.scl(t)); float distance = sub(tp, sp).len(); System.out.println(String.format("At time %.2f; Target=(%.2f, %.2f), Bullet=(%.2f, %.2f) distance=%.4f", t, tp.x, tp.y, sp.x, sp.y, distance)); if (distance < 0.01f) { System.out.println("COLLISION!"); break; } } } public static void main(String[] args) { // Input Vector2 targetPosition = new Vector2(1, 10); Vector2 targetVelocity = new Vector2(1, -1); Vector2 turretPosition = new Vector2(0, 0); float bulletSpeed = 10.0f; runSimulation(targetPosition, turretPosition, targetVelocity, bulletSpeed); } }
Gives the output;
At time 0,00; Target=(1,00, 10,00), Bullet=(0,00, 0,00) distance=10,0499 At time 0,10; Target=(1,10, 9,90), Bullet=(0,20, 0,90) distance=9,0449 At time 0,20; Target=(1,20, 9,80), Bullet=(0,40, 1,80) distance=8,0399 At time 0,30; Target=(1,30, 9,70), Bullet=(0,60, 2,70) distance=7,0349 At time 0,40; Target=(1,40, 9,60), Bullet=(0,80, 3,60) distance=6,0299 At time 0,50; Target=(1,50, 9,50), Bullet=(1,00, 4,50) distance=5,0249 At time 0,60; Target=(1,60, 9,40), Bullet=(1,20, 5,40) distance=4,0199 At time 0,70; Target=(1,70, 9,30), Bullet=(1,40, 6,30) distance=3,0150 At time 0,80; Target=(1,80, 9,20), Bullet=(1,60, 7,20) distance=2,0100 At time 0,90; Target=(1,90, 9,10), Bullet=(1,80, 8,10) distance=1,0050 At time 1,00; Target=(2,00, 9,00), Bullet=(2,00, 9,00) distance=0,0000 COLLISION!