0
\$\begingroup\$

EDIT: This is not a duplicate as the proposed solution does not seem to work. See the bottom of this question for clarification.

I am trying to calculate the launch velocity of a ball that needs to land at a certain position in 3D space. I know the height the ball will be launched from, the position it needs to land and the initial magnitude of the velocity.

I want to produce a Vector3 that I can use with AddForce that propels the ball and lands on the target.

I have the following function that I use to work out the launch angle, based on equations in this video:

private Vector3 GetLaunchAngle(float speed)
{
    //Calculate angle to z-axis
    float height = transform.position.y; //Height from 0
    float distance = Vector3.Distance(new Vector3(transform.position.x, 0, transform.position.z), new Vector3(aim.transform.position.x, 0, aim.transform.position.z)); //Distance to target (aim)
    float angle;
    Vector3 launchAngle = Vector3.forward;

    float quadB = 0.5f * Physics.gravity.y * (distance / speed); //B in quadratic equation = 1/2g * (d/v)
    float quadC = quadB + height;  //C in quadratic equation

    float tanAngle1 = (-quadB + Mathf.Sqrt((quadB * quadB) - (4 * distance * quadC))) / (2 * distance); //Calculate + in quadratic equation
    float tanAngle2 = (-quadB - Mathf.Sqrt((quadB * quadB) - (4 * distance * quadC))) / (2 * distance); //Calculate - in quadratic equation

    //Calculate both possibilities of angle
    float angle1 = Mathf.Rad2Deg * Mathf.Atan(tanAngle1);
    float angle2 = Mathf.Rad2Deg * Mathf.Atan(tanAngle2);

    //If angle 1 is invalid
    if (angle1 > 90 || angle1 < -90 || float.IsNaN(angle1))
    {
        angle = angle2;
    }
    //If angle 2 is invalid
    else if (angle2 > 90 || angle2 < -90 || float.IsNaN(angle2))
    {
        angle = angle1;
    }
    //If both are valid get min angle
    else if (!float.IsNaN(angle1) && !float.IsNaN(angle2))
    {
        angle = Math.Min(angle1, angle2);
    }
    else
    {
        angle = float.NaN;
    }

    //Try to rotate correct angles
    if (!float.IsNaN(angle))
    {
        //Apply x-axis rotation
        launchAngle = Quaternion.AngleAxis(angle, Vector3.left) * launchAngle;
        //Calculate (x,y) angle to target
        float angleTarget = Vector2.Angle(new Vector2(transform.position.x, transform.position.z), new Vector2(aim.transform.position.x, aim.transform.position.z));
        //Set left/right rotation
        angleTarget = angleTarget > 90 ? angleTarget - 180 : angleTarget;
        //Apply y-axis rotation
        launchAngle = Quaternion.AngleAxis(angleTarget, Vector3.up) * launchAngle;
    }
    else
    {
        launchAngle = Vector3.forward;
    }

    return launchAngle;
}

The main things that are wrong with this are, the (x,z) direction is off (see screenshot 1 below). I'm sure that the below is not the best way to do this:

float angleTarget = Vector2.Angle(new Vector2(transform.position.x, transform.position.z), new Vector2(aim.transform.position.x, aim.transform.position.z));
//Set left/right rotation
angleTarget = angleTarget > 90 ? angleTarget - 180 : angleTarget;
//Apply y-axis rotation
launchAngle = Quaternion.AngleAxis(angleTarget, Vector3.up) * launchAngle;

Also, I think that the calculated angle of incline is correct but I am open to other ideas around that.

Final issue is with the rigidbody.AddForce(launchAngle * speed);. So the speed is used to calculate the launch angle, but when used as the impulse for the force it barely moves, it looks like screenshot 2. I am using the ratio of 1 unit to 1m in Unity and therefore used a value of 35 for the speed (assuming this to mean 35m/s or approximately 80mph). Upon keeping the speed for the calculation of the angle but changing the impulse of the force to 1000, the ball moves like screenshot 3. How do you get the correct impulse on the rigidbody?

What is the best method for this?

EDIT: I have changed the AddForce to use the different ForceMode values with speed (35). Impulse and VelocityChange look like screens 4 & 5, it has improved the look of the arc, but it is still out (Long and left). Force and Acceleration looks like screenshot 2.

Screenshot 1 Screenshot 1 Screenshot 2 Screenshot 2 Screenshot 3 Screenshot 3 Screenshot 4 Screenshot 5 Screenshot 5 Screenshot 5

EDIT 2: In relation to @DMGregory I have seen that code and tried to implement it as such:

private Vector3 GetLaunchAngle2(float speed)
{
    Vector3 launchAngle = Vector3.up;

    float height = -transform.position.y; //Height from 0
    float distance = Vector3.Distance(new Vector3(transform.position.x, 0, transform.position.z), new Vector3(aim.transform.position.x, 0, aim.transform.position.z)); //Distance to target (aim)

    float b = (speed * speed) - (height * Physics.gravity.y);
    float discriminent = (b * b) - (Physics.gravity.y * Physics.gravity.y) * ((distance * distance) + (height + height));

    if (discriminent >= 0)
    {
        float discRoot = Mathf.Sqrt(discriminent);

        float tMin = Mathf.Sqrt((b - discRoot) * 2 / (Physics.gravity.y * Physics.gravity.y));
        float tMax = Mathf.Sqrt((b + discRoot) * 2 / (Physics.gravity.y * Physics.gravity.y));

        float vx = distance / tMin;
        float vy = height / tMin + tMin * Physics.gravity.y / 2;

        //float vx = distance / tMax;
        //float vy = height / tMax + tMax * Physics.gravity.y / 2;

        launchAngle = new Vector3(0.0f, vy, vx);
        //float angleTarget = Vector2.Angle(new Vector2(transform.position.x, transform.position.z), new Vector2(aim.transform.position.x, aim.transform.position.z));
        //angleTarget = angleTarget > 90 ? angleTarget - 180 : angleTarget;
        //launchAngle = Quaternion.AngleAxis(angleTarget, Vector3.up) * launchAngle;
    }

    return launchAngle;
}

The result of that is Screenshot 6 below, there is a massive problem with that. Screenshot 6

\$\endgroup\$
  • \$\begingroup\$ @DMGregory See edit above \$\endgroup\$ – anothershrubery Feb 18 '17 at 10:14
  • \$\begingroup\$ Use the 3D version of the code at the bottom of the answer. The code you wrote above is not implemented correctly. \$\endgroup\$ – DMGregory Feb 18 '17 at 15:43
  • \$\begingroup\$ Yes thank you! That works a treat, I have upvoted the other answer but if you want to add it here as an answer as well I will mark it as correct. I obviously just didn't incorporate the 2D method correctly previously. \$\endgroup\$ – anothershrubery Feb 18 '17 at 19:15