From 6d466fe6634481d3ad93e4f7c67479ccd8c694bf Mon Sep 17 00:00:00 2001 From: MEEPofFaith Date: Wed, 25 Oct 2023 10:05:13 -0700 Subject: [PATCH] Missile interception prediction Turns out this doesn't need calculus; just painfully solving a quartic. --- src/progressed/util/Math3D.java | 65 ++++++++++++++++--- src/progressed/util/PMMathf.java | 8 +-- .../turret/payload/ArcMissileTurret.java | 4 +- .../payload/modular/modules/ArcModule.java | 4 +- 4 files changed, 62 insertions(+), 19 deletions(-) diff --git a/src/progressed/util/Math3D.java b/src/progressed/util/Math3D.java index 714cea01..a13c1712 100644 --- a/src/progressed/util/Math3D.java +++ b/src/progressed/util/Math3D.java @@ -64,11 +64,11 @@ public static float[] diskVertices(float x, float y, float z, float rotation, fl public static float[] castVertices(float x, float y, float rotation, float startAngle, float tilt, float rad, int verts){ float[] castVerts = new float[verts * 2]; float space = 360f / (verts - 1f); - float scl = 1f + Mathf.sinDeg(tilt); + float scl = 1f + sinDeg(tilt); for(int i = 0; i < verts; i++){ float angle = startAngle + space * i - rotation; - vec.trns(rotation, Mathf.cosDeg(angle) * rad * scl, Mathf.sinDeg(angle) * rad); + vec.trns(rotation, cosDeg(angle) * rad * scl, sinDeg(angle) * rad); castVerts[i * 2] = x + vec.x; castVerts[i * 2 + 1] = y + vec.y; } @@ -83,15 +83,40 @@ public static float[] castVertices(float x, float y, float rotation, float start * @param dsty Y of target * @param dstvx X velocity of target (subtract shooter X velocity if needed) * @param dstvy Y velocity of target (subtract shooter Y velocity if needed) - * @param accel constant acceleration of bullet + * @param ba constant acceleration of the bullet + * @param bv initial velocity of the bullet * @return the intercept location */ - public static Vec2 intercept(float srcx, float srcy, float dstx, float dsty, float dstvx, float dstvy, float accel){ - //TODO come back once I learn parametrics - return vresult.set(dstx, dsty); + public static Vec2 intercept(float srcx, float srcy, float dstx, float dsty, float dstvx, float dstvy, float ba, float bv){ + dstvx /= Time.delta; + dstvy /= Time.delta; + float dx = dstx - srcx, + dy = dsty - srcy; + float uv = dstvx * dstvx + dstvy * dstvy, + ud = dx * dstvx + dy * dstvy; + + // Get quartic components + float a = -(ba * ba) / 4; + float b = -ba * bv; + float c = uv - (bv * bv); + float d = 2 * ud; + float e = dx * dx + dy * dy; + + // Solve + float[] ts = quartic(a, b, c, d, e); + + // Find smallest positive solution + Vec2 sol = vresult.set(dstx, dsty); + float min = Float.MAX_VALUE; + for(float t : ts){ + if(t >= 0 && t < min) min = t; + } + if(min < Float.MAX_VALUE) sol.set(dstx + dstvx * min, dsty + dstvy * min); + + return sol; } - public static Vec2 intercept(Position src, Position dst, float accel){ + public static Vec2 intercept(Position src, Position dst, float ba, float bv){ float ddx = 0, ddy = 0; if(dst instanceof Hitboxc h){ ddx += h.deltaX(); @@ -101,7 +126,8 @@ public static Vec2 intercept(Position src, Position dst, float accel){ ddx -= h.deltaX(); ddy -= h.deltaY(); } - return intercept(src.getX(), src.getY(), dst.getX(), dst.getY(), ddx, ddy, accel); + if(ddx == 0 && ddy == 0) return vresult.set(dst); //Don't bother performing unnecessary math if no prediction is needed. + return intercept(src.getX(), src.getY(), dst.getX(), dst.getY(), ddx, ddy, ba, bv); } public static Vec2 inaccuracy(float inaccuracy){ @@ -113,7 +139,7 @@ public static float dst(float x1, float y1, float z1, float x2, float y2, float float xd = x2 - x1; float yd = y2 - y1; float zd = z2 - z1; - return Mathf.sqrt(xd * xd + yd * yd + zd * zd); + return sqrt(xd * xd + yd * yd + zd * zd); } /** @@ -131,4 +157,25 @@ public static float tubeStartAngle(float x1, float y1, float x2, float y2, float return Angles.angle(x2, y2, Tmp.v2.x, Tmp.v2.y); } + + // https://math.stackexchange.com/questions/785/is-there-a-general-formula-for-solving-quartic-degree-4-equations + private static float[] quartic(float a, float b, float c, float d, float e){ + float p1 = 2*c*c*c - 9*b*c*d + 27*a*d*d + 27*b*b*e - 72*a*c*e; + float p2 = c*c - 3*b*d + 12*a*e; + float p3 = p1 + sqrt(-4*p2*p2*p2 + p1*p1); + float p4 = PMMathf.cbrt(p3/2); + float p5 = p2/(3*a*p4) + (p4/(3*a)); + + float p6 = sqrt((b*b)/(4*a*a) - (2*c)/(3*a) + p5); + float p7 = (b*b)/(2*a*a) - (4*c)/(3*a) - p5; + float p8 = (-(b*b*b)/(a*a*a) + (4*b*c)/(a*a) - (8*d)/a) / (4*p6); + + float[] out = new float[4]; + out[0] = -(b/(4*a)) - (p6/2) - (sqrt(p7-p8)/2); + out[1] = -(b/(4*a)) - (p6/2) + (sqrt(p7-p8)/2); + out[2] = -(b/(4*a)) - (p6/2) - (sqrt(p7+p8)/2); + out[3] = -(b/(4*a)) - (p6/2) + (sqrt(p7+p8)/2); + + return out; + } } diff --git a/src/progressed/util/PMMathf.java b/src/progressed/util/PMMathf.java index 7218b939..eff1bafc 100644 --- a/src/progressed/util/PMMathf.java +++ b/src/progressed/util/PMMathf.java @@ -20,12 +20,8 @@ public static float cornerDst(float w, float h){ return (float)Math.sqrt(w * h * 2f); } - public static float sqrt(float a){ - return Mathf.sign(a) * Mathf.sqrt(Math.abs(a)); - } - - public static float log(float a, float value){ - return Mathf.sign(value) * Mathf.log(a, Math.abs(value) + 1); + public static float cbrt(float x){ + return (float)Math.cbrt(x); } /** Copied from {@link Predict#quad(float, float, float)} */ diff --git a/src/progressed/world/blocks/defence/turret/payload/ArcMissileTurret.java b/src/progressed/world/blocks/defence/turret/payload/ArcMissileTurret.java index 08fb5fda..23c026aa 100644 --- a/src/progressed/world/blocks/defence/turret/payload/ArcMissileTurret.java +++ b/src/progressed/world/blocks/defence/turret/payload/ArcMissileTurret.java @@ -59,7 +59,7 @@ public void targetPosition(Posc pos){ ArcMissileBulletType bullet = (ArcMissileBulletType)peekAmmo(); if(predictTarget && pos instanceof Hitboxc h){ - targetPos.set(Math3D.intercept(this, h, bullet.accel)); + targetPos.set(Math3D.intercept(this, h, bullet.accel, bullet.speed)); }else{ targetPos.set(pos); } @@ -114,7 +114,7 @@ protected void bullet(BulletType type, float xOffset, float yOffset, float angle if(!type.scaleLife) targetPos.sub(this).setLength(range()).add(this); float dst = Math.max(Math.min(Mathf.dst(bulletX, bulletY, targetPos.x, targetPos.y), range()), minRange); ArcMissileBulletType m = (ArcMissileBulletType)type; - float time = Mathf.sqrt((2 * dst) / m.accel); + float time = Mathf.sqrt((2 * dst) / m.accel); //TODO consider initial velocity float zVel = -0.5f * -m.gravity * time; handleBullet(m.create3DVel(this, team, bulletX, bulletY, 0f, shootAngle, zVel, m.accel * velScl, targetPos.x, targetPos.y), xOffset, yOffset, shootAngle - rotation); diff --git a/src/progressed/world/blocks/defence/turret/payload/modular/modules/ArcModule.java b/src/progressed/world/blocks/defence/turret/payload/modular/modules/ArcModule.java index b1cffc39..b2867d3c 100644 --- a/src/progressed/world/blocks/defence/turret/payload/modular/modules/ArcModule.java +++ b/src/progressed/world/blocks/defence/turret/payload/modular/modules/ArcModule.java @@ -47,7 +47,7 @@ public void targetPosition(Posc pos){ ArcMissileBulletType bullet = (ArcMissileBulletType)peekAmmo(); if(predictTarget && pos instanceof Hitboxc h){ - targetPos.set(Math3D.intercept(this, h, bullet.accel)); + targetPos.set(Math3D.intercept(this, h, bullet.accel, bullet.speed)); }else{ targetPos.set(pos); } @@ -111,7 +111,7 @@ protected void bullet(BulletType type, float xOffset, float yOffset, float angle float dst = Math.max(Math.min(Mathf.dst(bulletX, bulletY, targetPos.x, targetPos.y), range()), minRange); ArcMissileBulletType m = (ArcMissileBulletType)type; - float time = Mathf.sqrt((2 * dst) / m.accel); + float time = Mathf.sqrt((2 * dst) / m.accel); //TODO consider initial velocity float zVel = -0.5f * -m.gravity * time; handleBullet(m.create3DVel(this, team, bulletX, bulletY, 0f, shootAngle, zVel, m.accel * velScl, targetPos.x, targetPos.y), xOffset, yOffset, shootAngle - rotation);