Help finding out how this function works. Need to return V3

Source:
http://ringofblades.com/Blades/Code/PredictiveAim.cs

Questions:

  • The return value is a bool but wouldn’t I be needing the Vector3 in this case to find out the future position of my target?
    -targetVelocityDir.normalized is not sent in through as a value for the function so how did he get the value for this v3?
///////////////////////////////////////////////////////////////////////////////
using UnityEngine;

class GameUtilities
{
    //////////////////////////////////////////////////////////////////////////////
    //This implies that no solution exists for this situation as the target may literally outrun the projectile with its current direction
    //In cases like that, we simply aim at the place where the target will be 1 to 5 seconds from now.
    //Feel free to randomize t at your discretion for your specific game situation if you want that guess to feel appropriately noisier
    static float PredictiveAimWildGuessAtImpactTime()
    {
        return Random.Range(1, 5);
    }

    //////////////////////////////////////////////////////////////////////////////
    //returns true if a valid solution is possible
    //projectileVelocity will be a non-normalized vector representing the muzzle velocity of a lobbed projectile in 3D space
    //if it returns false, projectileVelocity will be filled with a reasonable-looking attempt
    //The reason we return true/false here instead of Vector3 is because you might want your AI to hold that shot until a solution exists
    //This is meant to hit a target moving at constant velocity
    //Full derivation by Kain Shin exists here:
    //http://www.gamasutra.com/blogs/KainShin/20090515/83954/Predictive_Aim_Mathematics_for_AI_Targeting.php
    //gravity is assumed to be a positive number. It will be calculated in the downward direction, feel free to change that if you game takes place in Spaaaaaaaace
    static public bool PredictiveAim(Vector3 muzzlePosition, float projectileSpeed, Vector3 targetPosition, Vector3 targetVelocity, float gravity, out Vector3 projectileVelocity)
    {
        Debug.Assert(projectileSpeed > 0, "What are you doing shooting at something with a projectile that doesn't move?");
        if (muzzlePosition == targetPosition)
        {
            //Why dost thou hate thyself so?
            //Do something smart here. I dunno... whatever.
            projectileVelocity = projectileSpeed * (Random.rotation * Vector3.forward);
            return true;
        }

        //Much of this is geared towards reducing floating point precision errors
        float projectileSpeedSq = projectileSpeed * projectileSpeed;
        float targetSpeedSq = targetVelocity.sqrMagnitude; //doing this instead of self-multiply for maximum accuracy
        float targetSpeed = Mathf.Sqrt(targetSpeedSq);
        Vector3 targetToMuzzle = muzzlePosition - targetPosition;
        float targetToMuzzleDistSq = targetToMuzzle.sqrMagnitude; //doing this instead of self-multiply for maximum accuracy
        float targetToMuzzleDist = Mathf.Sqrt(targetToMuzzleDistSq);
        Vector3 targetToMuzzleDir = targetToMuzzle;
        targetToMuzzleDir.Normalize();

        //Law of Cosines: A*A + B*B - 2*A*B*cos(theta) = C*C
        //A is distance from muzzle to target (known value: targetToMuzzleDist)
        //B is distance traveled by target until impact (targetSpeed * t)
        //C is distance traveled by projectile until impact (projectileSpeed * t)
        float cosTheta = (targetSpeedSq > 0)
            ? Vector3.Dot(targetToMuzzleDir, targetVelocityDir.normalized)
            : 1.0f;

        bool validSolutionFound = true;
        float t;
        if (Mathf.Approximately(projectileSpeedSq, targetSpeedSq))
        {
            //a = projectileSpeedSq - targetSpeedSq = 0
            //We want to avoid div/0 that can result from target and projectile traveling at the same speed
            //We know that C and B are the same length because the target and projectile will travel the same distance to impact
            //Law of Cosines: A*A + B*B - 2*A*B*cos(theta) = C*C
            //Law of Cosines: A*A + B*B - 2*A*B*cos(theta) = B*B
            //Law of Cosines: A*A - 2*A*B*cos(theta) = 0
            //Law of Cosines: A*A = 2*A*B*cos(theta)
            //Law of Cosines: A = 2*B*cos(theta)
            //Law of Cosines: A/(2*cos(theta)) = B
            //Law of Cosines: 0.5f*A/cos(theta) = B
            //Law of Cosines: 0.5f * targetToMuzzleDist / cos(theta) = targetSpeed * t
            //We know that cos(theta) of zero or less means there is no solution, since that would mean B goes backwards or leads to div/0 (infinity)
            if (cosTheta > 0)
            {
                t = 0.5f * targetToMuzzleDist / (targetSpeed * cosTheta);
            }
            else
            {
                validSolutionFound = false;
                t = PredictiveAimWildGuessAtImpactTime();
            }
        }
        else
        {
            //Quadratic formula: Note that lower case 'a' is a completely different derived variable from capital 'A' used in Law of Cosines (sorry):
            //t = [ -b � Sqrt( b*b - 4*a*c ) ] / (2*a)
            float a = projectileSpeedSq - targetSpeedSq;
            float b = 2.0f * targetToMuzzleDist * targetSpeed * cosTheta;
            float c = -targetToMuzzleDistSq;
            float discriminant = b * b - 4.0f * a * c;

            if (discriminant < 0)
            {
                //Square root of a negative number is an imaginary number (NaN)
                //Special thanks to Rupert Key (Twitter: @Arakade) for exposing NaN values that occur when target speed is faster than or equal to projectile speed
                validSolutionFound = false;
                t = PredictiveAimWildGuessAtImpactTime();
            }
            else
            {
                //a will never be zero because we protect against that with "if (Mathf.Approximately(projectileSpeedSq, targetSpeedSq))" above
                float uglyNumber = Mathf.Sqrt(discriminant);
                float t0 = 0.5f * (-b + uglyNumber) / a;
                float t1 = 0.5f * (-b - uglyNumber) / a;
                //Assign the lowest positive time to t to aim at the earliest hit
                t = Mathf.Min(t0, t1);
                if (t < Mathf.Epsilon)
                {
                    t = Mathf.Max(t0, t1);
                }

                if (t < Mathf.Epsilon)
                {
                    //Time can't flow backwards when it comes to aiming.
                    //No real solution was found, take a wild shot at the target's future location
                    validSolutionFound = false;
                    t = PredictiveAimWildGuessAtImpactTime();
                }
            }
        }

        //Vb = Vt - 0.5*Ab*t + [(Pti - Pbi) / t]
        projectileVelocity = targetVelocity + (-targetToMuzzle / t);
        if (!validSolutionFound)
        {
            //PredictiveAimWildGuessAtImpactTime gives you a t that will not result in impact
            // Which means that all that math that assumes projectileSpeed is enough to impact at time t breaks down
            // In this case, we simply want the direction to shoot to make sure we
            // don't break the gameplay rules of the cannon's capabilities aside from gravity compensation
            projectileVelocity = projectileSpeed * projectileVelocity.normalized;
        }

        if (!Mathf.Approximately(gravity, 0))
        {
            //projectileSpeed passed in is a constant that assumes zero gravity.
            //By adding gravity as projectile acceleration, we are essentially breaking real world rules by saying that the projectile
            // gets additional gravity compensation velocity for free
            //We want netFallDistance to match the net travel distance caused by gravity (whichever direction gravity flows)
            float netFallDistance = (t * projectileVelocity).Z;
            //d = Vi*t + 0.5*a*t^2
            //Vi*t = d - 0.5*a*t^2
            //Vi = (d - 0.5*a*t^2)/t
            //Remember that gravity is a positive number in the down direction, the stronger the gravity, the larger gravityCompensationSpeed becomes
            float gravityCompensationSpeed = (netFallDistance + 0.5f * gravity * t * t) / t;
            projectileVelocity.Z = gravityCompensationSpeed;
        }

        //FOR CHECKING ONLY (valid only if gravity is 0)...
        //float calculatedprojectilespeed = projectileVelocity.magnitude;
        //bool projectilespeedmatchesexpectations = (projectileSpeed == calculatedprojectilespeed);
        //...FOR CHECKING ONLY

        return validSolutionFound;
    }
}

Found the solutions

Post the solutions please.

There is nothing worse than googling for a problem and finding threads with responses like “Fixed”.

The solution for was to make targetPosition a regular variable which is calculate in the function and use that, I ended up not using the bool since I don’t really know what it’s for.

could you please share the final result?

1 Like

Now I use this solution

public class AimingUtilities
    {

        public static bool InterceptionDirection(Vector2 a, Vector2 b, Vector2 vA, float sB, out Vector2 resultDirection)
        {
            var aToB = b - a;
            var dC = aToB.magnitude;
            var alpha = Vector2.Angle(aToB, vA) * Mathf.Deg2Rad;
            var sA = vA.magnitude;
            var r = sA / sB;
            if (SolveQuadratic(1 - r * r, 2 * r * dC * Mathf.Cos(alpha), -(dC * dC), out var root1, out var root2) == 0)
            {
                resultDirection = Vector2.zero;
                return false;
            }

            var dA = Math.Max(root1, root2);
            var t = dA / sB;
            var c = a + vA * t;
            resultDirection = (c - b).normalized;
            return true;
        }
      
        private static int SolveQuadratic(float a, float b, float c, out float root1, out float root2)
        {
            var discriminant = b * b - 4 * a * c;
            if (discriminant < 0)
            {
                root1 = Mathf.Infinity;
                root2 = -root1;
                return 0;
            }

            root1 = (-b + Mathf.Sqrt(discriminant)) / (2 * a);
            root2 = (-b - Mathf.Sqrt(discriminant)) / (2 * a);
            return discriminant > 0 ? 2 : 1;
        }
    }

but if you need gravity this code works as well

using UnityEngine;

    public class GameUtilities
    {
        //////////////////////////////////////////////////////////////////////////////
        //This implies that no solution exists for this situation as the target may literally outrun the projectile with its current direction
        //In cases like that, we simply aim at the place where the target will be 1 to 5 seconds from now.
        //Feel free to randomize t at your discretion for your specific game situation if you want that guess to feel appropriately noisier
        static float PredictiveAimWildGuessAtImpactTime()
        {
            return Random.Range(1, 5);
        }

        //////////////////////////////////////////////////////////////////////////////
        //returns true if a valid solution is possible
        //projectileVelocity will be a non-normalized vector representing the muzzle velocity of a lobbed projectile in 3D space
        //if it returns false, projectileVelocity will be filled with a reasonable-looking attempt
        //The reason we return true/false here instead of Vector3 is because you might want your AI to hold that shot until a solution exists
        //This is meant to hit a target moving at constant velocity
        //Full derivation by Kain Shin exists here:
        //http://www.gamasutra.com/blogs/KainShin/20090515/83954/Predictive_Aim_Mathematics_for_AI_Targeting.php
        //gravity is assumed to be a positive number. It will be calculated in the downward direction, feel free to change that if you game takes place in Spaaaaaaaace
        static public bool PredictiveAim(Vector3 muzzlePosition, float projectileSpeed, Vector3 targetPosition, Vector3 targetVelocity, float gravity, out Vector3 projectileVelocity)
        {
            Debug.Assert(projectileSpeed > 0, "What are you doing shooting at something with a projectile that doesn't move?");
            if (muzzlePosition == targetPosition)
            {
                //Why dost thou hate thyself so?
                //Do something smart here. I dunno... whatever.
                projectileVelocity = projectileSpeed * (Random.rotation * Vector3.forward);
                return true;
            }

            //Much of this is geared towards reducing floating point precision errors
            float projectileSpeedSq = projectileSpeed * projectileSpeed;
            float targetSpeedSq = targetVelocity.sqrMagnitude; //doing this instead of self-multiply for maximum accuracy
            float targetSpeed = Mathf.Sqrt(targetSpeedSq);
            Vector3 targetToMuzzle = muzzlePosition - targetPosition;
            float targetToMuzzleDistSq = targetToMuzzle.sqrMagnitude; //doing this instead of self-multiply for maximum accuracy
            float targetToMuzzleDist = Mathf.Sqrt(targetToMuzzleDistSq);
            Vector3 targetToMuzzleDir = targetToMuzzle;
            targetToMuzzleDir.Normalize();

            //Law of Cosines: A*A + B*B - 2*A*B*cos(theta) = C*C
            //A is distance from muzzle to target (known value: targetToMuzzleDist)
            //B is distance traveled by target until impact (targetSpeed * t)
            //C is distance traveled by projectile until impact (projectileSpeed * t)
            float cosTheta = (targetSpeedSq > 0)
                ? Vector3.Dot(targetToMuzzleDir, targetVelocity.normalized)
                : 1.0f;

            bool validSolutionFound = true;
            float t;
            if (Mathf.Approximately(projectileSpeedSq, targetSpeedSq))
            {
                //a = projectileSpeedSq - targetSpeedSq = 0
                //We want to avoid div/0 that can result from target and projectile traveling at the same speed
                //We know that C and B are the same length because the target and projectile will travel the same distance to impact
                //Law of Cosines: A*A + B*B - 2*A*B*cos(theta) = C*C
                //Law of Cosines: A*A + B*B - 2*A*B*cos(theta) = B*B
                //Law of Cosines: A*A - 2*A*B*cos(theta) = 0
                //Law of Cosines: A*A = 2*A*B*cos(theta)
                //Law of Cosines: A = 2*B*cos(theta)
                //Law of Cosines: A/(2*cos(theta)) = B
                //Law of Cosines: 0.5f*A/cos(theta) = B
                //Law of Cosines: 0.5f * targetToMuzzleDist / cos(theta) = targetSpeed * t
                //We know that cos(theta) of zero or less means there is no solution, since that would mean B goes backwards or leads to div/0 (infinity)
                if (cosTheta > 0)
                {
                    t = 0.5f * targetToMuzzleDist / (targetSpeed * cosTheta);
                }
                else
                {
                    validSolutionFound = false;
                    t = PredictiveAimWildGuessAtImpactTime();
                }
            }
            else
            {
                //Quadratic formula: Note that lower case 'a' is a completely different derived variable from capital 'A' used in Law of Cosines (sorry):
                //t = [ -b � Sqrt( b*b - 4*a*c ) ] / (2*a)
                float a = projectileSpeedSq - targetSpeedSq;
                float b = 2.0f * targetToMuzzleDist * targetSpeed * cosTheta;
                float c = -targetToMuzzleDistSq;
                float discriminant = b * b - 4.0f * a * c;

                if (discriminant < 0)
                {
                    //Square root of a negative number is an imaginary number (NaN)
                    //Special thanks to Rupert Key (Twitter: @Arakade) for exposing NaN values that occur when target speed is faster than or equal to projectile speed
                    validSolutionFound = false;
                    t = PredictiveAimWildGuessAtImpactTime();
                }
                else
                {
                    //a will never be zero because we protect against that with "if (Mathf.Approximately(projectileSpeedSq, targetSpeedSq))" above
                    float uglyNumber = Mathf.Sqrt(discriminant);
                    float t0 = 0.5f * (-b + uglyNumber) / a;
                    float t1 = 0.5f * (-b - uglyNumber) / a;
                    //Assign the lowest positive time to t to aim at the earliest hit
                    t = Mathf.Min(t0, t1);
                    if (t < Mathf.Epsilon)
                    {
                        t = Mathf.Max(t0, t1);
                    }

                    if (t < Mathf.Epsilon)
                    {
                        //Time can't flow backwards when it comes to aiming.
                        //No real solution was found, take a wild shot at the target's future location
                        validSolutionFound = false;
                        t = PredictiveAimWildGuessAtImpactTime();
                    }
                }
            }

            //Vb = Vt - 0.5*Ab*t + [(Pti - Pbi) / t]
            projectileVelocity = targetVelocity + (-targetToMuzzle / t);
            if (!validSolutionFound)
            {
                //PredictiveAimWildGuessAtImpactTime gives you a t that will not result in impact
                // Which means that all that math that assumes projectileSpeed is enough to impact at time t breaks down
                // In this case, we simply want the direction to shoot to make sure we
                // don't break the gameplay rules of the cannon's capabilities aside from gravity compensation
                projectileVelocity = projectileSpeed * projectileVelocity.normalized;
            }

            if (!Mathf.Approximately(gravity, 0))
            {
                //projectileSpeed passed in is a constant that assumes zero gravity.
                //By adding gravity as projectile acceleration, we are essentially breaking real world rules by saying that the projectile
                // gets additional gravity compensation velocity for free
                //We want netFallDistance to match the net travel distance caused by gravity (whichever direction gravity flows)
                float netFallDistance = (t * projectileVelocity).z;
                //d = Vi*t + 0.5*a*t^2
                //Vi*t = d - 0.5*a*t^2
                //Vi = (d - 0.5*a*t^2)/t
                //Remember that gravity is a positive number in the down direction, the stronger the gravity, the larger gravityCompensationSpeed becomes
                float gravityCompensationSpeed = (netFallDistance + 0.5f * gravity * t * t) / t;
                projectileVelocity.z = gravityCompensationSpeed;
            }

            //FOR CHECKING ONLY (valid only if gravity is 0)...
            //float calculatedprojectilespeed = projectileVelocity.magnitude;
            //bool projectilespeedmatchesexpectations = (projectileSpeed == calculatedprojectilespeed);
            //...FOR CHECKING ONLY

            return validSolutionFound;
        }
    }

Sorry, I switched to a different prototype since so I deleted the old scripts. glad you found a solution.