Discrepancy between physics simulation and prediction

Unity Version: 2021.3.25f1
I apologize if this has been addressed before in this forum. I looked for a while and found some things that were close but I think my issue is different.

TL;DR: I think my problem can be solved with this question: Does Unity use x = x0 + v0 * t + 0.5 * a * t^2 to calculate rigidbody positions? If not, what does it use?

I am working on a space simulation game. My current objective is to be able to fly around a real-scale solar system with mostly realistic physics. To reduce the complexity of the gravity calculations, the positions of celestial bodies are determined using Keplerian elements, not the physics simulation. I am trying to predict the path of a spacecraft and display it using a LineRenderer.

My tests involve a system with a star, a planet, and a cube. The star is stationary and exerts gravity. The planet has an orbit defined with Keplerian elements and exerts gravity. The cube has a Rigidbody and is affected by gravity*. I am able to set the initial position and velocity of the cube using Keplerian elements. This test system is all within 20 units of the origin, so I shouldn’t need to worry about precision. The gravity on the cube is calculated like so:

    public Vector3d GravitationalAcceleration(Vector3d position)
    {
        Vector3d acc = Vector3d.Zero();
        foreach (var body in celestialBodies)
        {
            if(body.exertsGravity)
            {
                Vector3d r = (Vector3d)body.transform.position - position;
                Vector3d dir = r.Normalized();
                acc += G * body.mass / r.SquareMagnitude() * dir;
            }
        }
        return acc;
    }

(Vector3d functions like Vector3 but uses doubles to combat precision issues down the line) I then use AddForce(acc, ForceMode.Acceleration) in FixedUpdate() to apply the acceleration to the cube. That all functions as expected.

To predict the path of the cube and show it to the player, I create a Vector6d array. Vector6d is a data type that lets me store a position and velocity. The first point is found using the position and velocity values of the rigidbody. The rest of the points are found using the following procedure:
0. The timestep is 0.01, which is the fixed timestep in the project settings.

  1. The acceleration due to gravity (vector a) at the previous point’s position (vector x_(n-1)) and expected positions of celestial bodies.
  2. The new position vector x_n and velocity vector v_n are calculated by: x_n = x_(n-1) + v_(n-1) * timestep + 0.5 * a * (timestep^2); v_n = v_(n-1) + a * timestep;
  3. The procedure then repeats until the array is full.

I then make a Vector3 array from the Vector6d array to set the positions of the LineRenderer.

Because the prediction timestep matches the fixed timestep, I would expect the prediction to match the simulation exactly. But it doesn’t. It is really close, but the path keeps changing over time. It shouldn’t do that if it was working properly.

I am testing with 1000 points, which according to the profiler is taking about 2ms to calculate on my machine. So I don’t think it’s performance or framerate related. Using debug statements, I can see that the predicted acceleration due to gravity does not match the simulated gravity at the same point in time:
9728614--1390912--upload_2024-3-26_11-49-31.png
Both are at 20s since the simulation started, but there is a small difference between the two accelerations.

My best guess is that Unity’s physics does not use the classic x = x0 + v * t + 0.5 * a * t^2 equation for rigidbodies, but I couldn’t find any information about what it might actually use. Does anyone have any idea what could be the problem?

*My custom gravity code, not Unity’s built-in gravity.

9728614--1390909--upload_2024-3-26_11-49-15.png

Unity uses PhysX as the 3d physics engine, and Box2D for 2d physics. Source code of both of those are publicly available.

The problem with basic Newtonian motion equation x = x0 + v * t + 0.5 * a * t^2 is that it works while the acceleration a is constant. But once the acceleration starts changing based on position or other factors things get more complicated. Using a sufficiently small time step you can approximate what happens, but the devil is in details. There are many similar but subtly different approaches , all of them are still based on the same x = x0 + v * Δt + 0.5 * a * Δt^2 equation. For example when calculating x(t+1), do you use v(t) or do you use v(t+1). When calculating v(t+1) do you use a(t) or a(t+1), and many more small differences like that. Since a is changing it’s not like the x = x0 + v * t + 0.5 * a * t^2 is quite accurate description of process, you could simplify it and just use x(t+Δt) = x(t) + Δt * v(t+?) and rely on v(t) being updated separately with sufficiently small timestep, or you can go the opposite direction and make the equation more complex why stop at the second derivative. In short term result will be similar but not quite identical, but as simulation moves on the results will diverge more and more. Different methods have different tradeoffs with regards to accuracy, stability, performance and other properties. What’s better using more iterations of simpler steps with smaller timestep, or using more complex steps but having less of them.

There is no single universally agreed best choice for all situations.

The problem of approximating movement based on those equations (and not just movement equations) is known as “numerical integration”. See the wikipedia page Numerical methods for ordinary differential equations - Wikipedia and the linked specific methods at the bottom of page.

Which exact method PhysX uses isn’t quite clear.Multiple Nvidia employes have said different things. But that might depend on PhysX version or which aspects of the physics engine is involved.
Once they responded “semi-implicit Euler” What's the integration method in PhysX? · Issue #57 · NVIDIAGameWorks/PhysX-3.4 · GitHub
In different issue they responded “backward Euler” Integration Scheme and LCP · Issue #422 · NVIDIAGameWorks/PhysX · GitHub

2 Likes