/// <summary>
        /// Calculates an approximate leading target point to ensure a ballistic projectile will impact a moving target assuming a given launch angle.
        /// Assumes constant target velocity and constant projectile speed after launch. Precision can be adjusted parametrically.
        /// Uses vertical gravity constant defined in project Physics settings.
        /// </summary>
        /// <param name="firePosition">Starting point of the projectile.</param>
        /// <param name="targetPosition">The current position of the intended target.</param>
        /// <param name="targetVelocity">Vector representing the velocity of the intended target.</param>
        /// <param name="launchAngle">The angle at which the projectile is to be launched.</param>
        /// <param name="arcHeight">Preference between parabolic ("underhand") or direct ("overhand") projectile arc.</param>
        /// <param name="gravity">Gravitational constant (Vertical only. Positive = down)</param>
        /// <param name="precision">Number of iterations to approximate the correct position. Higher precision is better for faster targets.</param>
        /// <returns>Vector3 representing the leading target point. Vector3.zero if no solution.</returns>
        public static Vector3 CalculateBallisticLeadingTargetPointWithAngle(Vector3 firePosition,
                                                                            Vector3 targetPosition,
                                                                            Vector3 targetVelocity, float launchAngle,
                                                                            BallisticArcHeight arcHeight, float gravity,
                                                                            int precision = 2)
        {
            // No precision means no leading, so we early-out.
            if (precision <= 1)
            {
                return(targetPosition);
            }

            Vector3 testPosition = targetPosition;

            for (int i = 0; i < precision; i++)
            {
                float launchSpeed = CalculateBallisticFireVectorFromAngle(firePosition, testPosition, launchAngle, gravity)
                                    .magnitude;

                float impactTime = CalculateBallisticFlightTime(firePosition, testPosition, launchSpeed, launchAngle, gravity);

                if (float.IsNaN(launchSpeed) || float.IsNaN(impactTime))
                {
                    return(Vector3.zero);
                }

                testPosition = targetPosition + (targetVelocity * impactTime);
            }

            return(testPosition);
        }
 /// <summary>
 /// Calculates an approximate leading target point to ensure a ballistic projectile will impact a moving target assuming a given launch speed.
 /// Assumes constant target velocity and constant projectile speed after launch. Precision can be adjusted parametrically.
 /// Uses vertical gravity constant defined in project Physics settings.
 /// </summary>
 /// <param name="firePosition">Starting point of the projectile.</param>
 /// <param name="targetPosition">The current position of the intended target.</param>
 /// <param name="targetVelocity">Vector representing the velocity of the intended target.</param>
 /// <param name="launchSpeed">Initial speed of the projectile.</param>
 /// <param name="arcHeight">Preference between parabolic ("underhand") or direct ("overhand") projectile arc.</param>
 /// <param name="precision">Number of iterations to approximate the correct position. Higher precision is better for faster targets.</param>
 /// <returns>Vector3 representing the leading target point. Vector3.zero if no solution.</returns>
 public static Vector3 CalculateBallisticLeadingTargetPointWithSpeed(Vector3 firePosition, Vector3 targetPosition,
                                                                     Vector3 targetVelocity, float launchSpeed,
                                                                     BallisticArcHeight arcHeight, int precision = 2)
 {
     return(CalculateBallisticLeadingTargetPointWithSpeed(firePosition, targetPosition, targetVelocity, launchSpeed,
                                                          arcHeight, Mathf.Abs(Physics.gravity.y), precision));
 }
        /// <summary>
        /// Calculates the angle at which a projectile with a given initial speed must be fired to impact a target.
        /// </summary>
        /// <param name="firePosition">Position from which the projectile is fired</param>
        /// <param name="targetPosition">Intended target position.</param>
        /// <param name="launchSpeed">The speed that the projectile is launched at.</param>
        /// <param name="arcHeight">Preference between parabolic ("underhand") or direct ("overhand") projectile arc.</param>
        /// <param name="gravity">Gravitational constant (Vertical only. Positive = down)</param>
        /// <returns>The required launch angle in degrees. NaN if no valid solution.</returns>
        public static float CalculateBallisticFireAngle(Vector3 firePosition, Vector3 targetPosition,
                                                        float launchSpeed, BallisticArcHeight arcHeight, float gravity)
        {
            Vector3 target = targetPosition;

            target.y = firePosition.y;
            Vector3 toTarget       = target - firePosition;
            float   targetDistance = toTarget.magnitude;
            float   relativeY      = targetPosition.y - firePosition.y;
            float   vSquared       = launchSpeed * launchSpeed;

            // If the distance to our target is zero, we can assume it's right on top of us (or that we're our own target).
            if (Mathf.Approximately(targetDistance, 0f))
            {
                // If we're preferring a high-angle shot, we just fire straight up.
                if (arcHeight == BallisticArcHeight.UseHigh || arcHeight == BallisticArcHeight.PreferHigh)
                {
                    return(90f);
                }

                // If we're doing a low-angle direct shot, we tweak our angle based on relative height of target.
                if (relativeY > 0)
                {
                    return(90f);
                }

                if (relativeY < 0)
                {
                    return(-90f);
                }
            }

            float b = Mathf.Sqrt((vSquared * vSquared) -
                                 (gravity * ((gravity * (targetDistance * targetDistance)) + (2 * relativeY * vSquared))));

            // The "underarm", parabolic arc angle
            float theta1 = Mathf.Atan((vSquared + b) / (gravity * targetDistance));

            // The "overarm", direct arc angle
            float theta2 = Mathf.Atan((vSquared - b) / (gravity * targetDistance));

            bool theta1Nan = float.IsNaN(theta1);
            bool theta2Nan = float.IsNaN(theta2);

            // If both are invalid, we early-out with a NaN to indicate no solution.
            if (theta1Nan && theta2Nan)
            {
                return(float.NaN);
            }

            // We'll init with the parabolic arc.
            float returnTheta = theta1;

            // If we want to return the direct arc
            if (arcHeight == BallisticArcHeight.UseLow)
            {
                returnTheta = theta2;
            }

            // If we want to return theta1 wherever valid, but will settle for theta2 if theta1 is invalid
            if (arcHeight == BallisticArcHeight.PreferHigh)
            {
                returnTheta = theta1Nan ? theta2 : theta1;
            }

            // If we want to return theta2 wherever valid, but will settle for theta1 if theta2 is invalid
            if (arcHeight == BallisticArcHeight.PreferLow)
            {
                returnTheta = theta2Nan ? theta1 : theta2;
            }

            return(returnTheta * Mathf.Rad2Deg);
        }
 /// <summary>
 /// Calculates the angle at which a projectile with a given initial speed must be fired to impact a target.
 /// Uses vertical gravity constant defined in project Physics settings.
 /// </summary>
 /// <param name="firePosition">Position from which the projectile is fired</param>
 /// <param name="targetPosition">Intended target position.</param>
 /// <param name="launchSpeed">The speed that the projectile is launched at.</param>
 /// <param name="arcHeight">Preference between parabolic ("underhand") or direct ("overhand") projectile arc.</param>
 /// <returns>The required launch angle in degrees. NaN if no valid solution.</returns>
 public static float CalculateBallisticFireAngle(Vector3 firePosition, Vector3 targetPosition,
                                                 float launchSpeed, BallisticArcHeight arcHeight)
 {
     return(CalculateBallisticFireAngle(firePosition, targetPosition, launchSpeed, arcHeight,
                                        Mathf.Abs(Physics.gravity.y)));
 }
        /// <summary>
        /// Calculates the launch velocity for a parabolic-path projectile to hit a given target point when
        /// fired at a given speed.
        /// </summary>
        /// <param name="firePosition">Position from which the projectile is fired.</param>
        /// <param name="targetPosition">Intended target position.</param>
        /// <param name="launchSpeed">The speed that the projectile is launched at.</param>
        /// <param name="arcHeight">Preference between parabolic ("underhand") or direct ("overhand") projectile arc.</param>
        /// <param name="gravity">Gravitational constant (Vertical only. Positive = down)</param>
        /// <returns>Vector3 representing launch launchSpeed to hit the target. Vector3.zero if no solution.</returns>
        public static Vector3 CalculateBallisticFireVectorFromVelocity(Vector3 firePosition, Vector3 targetPosition,
                                                                       float launchSpeed, BallisticArcHeight arcHeight,
                                                                       float gravity)
        {
            float theta = CalculateBallisticFireAngle(firePosition, targetPosition, launchSpeed, arcHeight, gravity);

            // If our angle is impossible, we early-out.
            if (float.IsNaN(theta))
            {
                return(Vector3.zero);
            }

            Vector3 target = targetPosition;

            target.y = firePosition.y;
            Vector3 toTarget = target - firePosition;

            float targetDistance = toTarget.magnitude;

            Vector3 aimVector = Vector3.forward;

            if (targetDistance > 0f)
            {
                // Flatten aim vector so we can rotate it
                aimVector   = toTarget / targetDistance;
                aimVector.y = 0;
            }

            Vector3    rotAxis  = Vector3.Cross(aimVector, Vector3.up);
            Quaternion rotation = Quaternion.AngleAxis(theta, rotAxis);

            aimVector = rotation * aimVector.normalized;

            return(aimVector * launchSpeed);
        }