2
\$\begingroup\$

I'm curious what is the valid calculation of a projectile trajectory in case when:

  • surface is not flat but spherical like a planet
  • drag is enabled (for example 0.1)
  • and we are in 3D

I've found calculations where either the drag or the spherical surface factor was missing. I assume in each simulation step I have to recalculate the direction of the gravity which is the direction between the current position of the projectile and center of the planet.

enter image description here

I've tried to reuse the SimulateArc method in one of these answers: https://stackoverflow.com/questions/61125224/2d-projectile-trajectory-predictionunity-2d

I changed Vector2 to Vector3 and added a gravity direction recalculation on the loop after the new position is calculated. But Drag factor is missing yet. Is it a good approach to solve this issue? If not how should I calculate the trajectory also with the Drag factor?

private void CalculateTrajectory()
{
    float simulateForDuration = 5f;//simulate for 5 secs in the furture
    float simulationStep = 0.1f;//Will add a point every 0.1 secs.

    int steps = (int)(simulateForDuration/simulationStep);//50 in this example
    Vector3 calculatedPosition;
    Vector3 directionVector = unit.CannonDirection;
    Vector3 launchPosition = unit.StartPosition;
    float launchSpeed = unit.ShootPower;

    for(int i = 0; i < steps; ++i)
    {
        calculatedPosition = launchPosition + ( directionVector * (launchSpeed * i * simulationStep));
        //Calculate gravity
        Vector3 gravity = (planet.transform.position - transform.position).normalized * gravityConst;
        calculatedPosition += gravity * ( i * simulationStep) *  ( i * simulationStep);
            
        Gizmos.DrawSphere(calculatedPosition, 0.5f);
    }

}

EDIT 1: I tried to code the formula provided by Sacha but it seems it is not perfect yet (only on flat surface and without drag for now, if it works then I will add the drag factor and a sphere surface):

private void CalculateTrajectory()
{
    float simulateForDuration = 5f;
    float simulationStep = 0.1f;

    int steps = (int)(simulateForDuration / simulationStep);
    float mass = 0.5f;
    Vector3 previousPosition = transform.position;
    Vector3 previousVelocity = transform.forward.normalized * 5;
    Vector3 gravity = Physics.gravity;

    for (int i = 0; i < steps; ++i)
    {
        Vector3 acceleration = (previousVelocity + gravity) / mass;
        Vector3 newVelocity = previousVelocity + acceleration * simulationStep;
        Vector3 newPosition = previousPosition + newVelocity * simulationStep;

        Gizmos.DrawSphere(newPosition, 0.2f);

        previousPosition = newPosition;
        previousVelocity = newVelocity;
    }

}

enter image description here

Here is my projectile creation code:

GameObject clone = GameObject.Instantiate(sphere);
clone.transform.position = transform.position;
clone.transform.forward = transform.forward;

Rigidbody rb = clone.GetComponent<Rigidbody>();
rb.velocity = clone.transform.forward.normalized * 5f;

What did I miss?

EDIT 2: I tried another approach:

float simulateForDuration = 5f;
float simulationStep = 0.02f;

int steps = (int)(simulateForDuration / simulationStep);
Vector3 initialVelocity = transform.forward.normalized * 5f;

for (int i = 0; i < steps; ++i)
{
    Vector3 pos = new Vector3(
        transform.position.x + initialVelocity.x * (i * simulationStep),
        transform.position.y + initialVelocity.y * (i * simulationStep) - 0.5f * -Physics.gravity.y * Mathf.Pow(i * simulationStep, 2),
        transform.position.z + initialVelocity.z * (i * simulationStep));
        
    Gizmos.DrawSphere(pos, 0.05f);
}

Result: enter image description here

This seems fine when I have only gravity on axis Y. We can say that the first version is almost fine, but I cannot figure out what is missing.

EDIT Final: Based on Sacha updated answer regarding P = m * g the calculation is fine. I also had to update the AddForce method on the projectile itself because I added 9.81 * normalizedDirectionToTheCenter for the projectile as gravity. Now it is 9.81 * projectileMass * normalizedDirectionToTheCenter .

Here is the full code:

private void CalculateTrajectory()
{
    float simulateForDuration = 10f;
    float simulationStep = 0.02f;

    int steps = (int)(simulateForDuration / simulationStep);
    float mass = r.mass; // Mass of the projectile.

    Vector3 previousPosition = transform.position;
    Vector3 previousVelocity = transform.forward.normalized * power;

    for (int i = 1; i < steps; ++i)
    {
        Vector3 directionToCenter = (planet.position - previousPosition).normalized;
        Vector3 gravity = directionToCenter * 9.81f;

        // Separated vectors to understand better.
        Vector3 force = gravity * mass;
        Vector3 acceleration = force / mass;
        Vector3 newVelocity = previousVelocity + acceleration * simulationStep;
        Vector3 newPosition = previousPosition + newVelocity * simulationStep;

        Gizmos.DrawSphere(newPosition, 0.05f);

        previousPosition = newPosition;
        previousVelocity = newVelocity;
    }
}

Thanks Sacha!

enter image description here

\$\endgroup\$
2
\$\begingroup\$

Your approach has good grounds, but there are mistakes in your code.

A little theory first: Your simulation needs to use the second law of Newton which gives you the acceleration of the projectile:
\$\vec{F} = m.\vec{a}\$ where \$\vec{F}\$ is the sum of the forces applied to your projectile.
For that you need to add all the forces that act on your projectile: its weight (due to gravity) and air drag.

Drag
The drag can be computed as \$A.V^2\$ where V is the projectile's speed and A is a constant (your drag factor). Remember that it applies in the opposite direction of the speed vector.
(For reference, I've covered computation of air drag in a simplified model in the following answer: Simplified aerodynamics for 3D airplane )

Weight
The weight is given by \$\vec{P} = m.\vec{g}\$.
\$\vec{g}\$ is the gravity vector and for a good enough approximation, points from your projectile to the center on Earth. As you consider the curvature of Earth, you have to update this vector at each step.

Position
Acceleration being the derivative of speed, and speed being the derivative of position, you can update now update both:

  • \$\vec{V_{n+1}} = \vec{V_n} + \vec{a_{n+1}}.\Delta t\$
  • \$\vec{r_{n+1}} = \vec{r_n} + \vec{V_{n+1}}.\Delta t\$ (\$\vec{r}\$ is the position vector of your projectile)

In your code, you need variables to memorize the projectile speed and position (Vector3). Those variables will be incrementaly updated, once per loop. The initial conditions (launchPosition and launchSpeed) should not appear within the loop body.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Okay so I tried to code the formula but I think I have misalignments yet. See it in my edited question. \$\endgroup\$ – Panthesilea Nov 20 at 22:31
  • \$\begingroup\$ I'm not sure what exactly is wrong with your results, but I see that in your new code, the weight of the projectile is constant.This is a good approximation for flat surfaces but in your case, you want to update the weight vector at each simulation step (to account for change in direction, and intensity if you want to accound for change in weight with altitude). Same goes for drag: it should be updated at each step. \$\endgroup\$ – Sacha Nov 20 at 22:51
  • \$\begingroup\$ The problem is that the predicted trajectory is not the same as the one which is calculated by the Unity physics. What do you mean on 'weight vector'? A normalized direction vector from the projectile towards the center of gravity multiplied by mass constant? \$\endgroup\$ – Panthesilea Nov 20 at 23:03
  • \$\begingroup\$ Yes, the weight intensity can be simplified that way. It is constant, but the direction is not and will change at each step. Both curves look like parabolas, but with different parameters. Are you sure your constants (weight intensity, drag if any, initial speed) are the same in both cases? \$\endgroup\$ – Sacha Nov 20 at 23:06
  • \$\begingroup\$ Hmm, but if I create a vector for the mass then I cannot divide the force vector with this mass vector only if I divide each part separately (e.g. force.x / mass.x, etc.) In this case I don't see any result in the Unity editor. Vector3 acceleration = new Vector3(force.x/massVector.x, force.y/massVector.y, force.z/massVector.z); \$\endgroup\$ – Panthesilea Nov 20 at 23:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.