Bepu v2 vs PhysX

Discuss any questions about BEPUphysics or problems encountered.
Post Reply
tomweiland
Posts: 96
Joined: Wed May 08, 2019 12:17 am

Bepu v2 vs PhysX

Post by tomweiland » Thu Sep 12, 2019 4:18 am

So I've got Bepu v2 running on my server, and I'm using Unity to render the clients. In an effort to reduce the amount of packets I'm sending, I decided not to send regular positional updates for cannonballs. Instead, I simply send over the position where they're spawned and their starting velocity. Once they collide with something I'll send another packet letting clients know to make it explode.

However, I intend to simulate the movement between those two updates locally (during that time gravity is the only thing affecting them). I was hoping that I could get away with just using Unity's built-in physics (PhysX) instead of going through the process of implementing Bepu on the client as well.
I wouldn't mind sacrificing a little accuracy, and I didn't really expect it to go well since it's two completely separate systems.

Strangely enough, PhysX's gravity seems to be stronger than Bepu's. I made sure both are using the same gravity vector as well as the same timestep, but my cannonballs lose height faster on the client than on the server.

Now, I completely understand that there's probably no way to cleanly account for this, but I wanted to ask if you had any insight on how PhysX calculates gravity or what might otherwise be affecting it. I'm not really expecting a fix (I'll probably end up sticking Bepu into Unity, unless you've got a better idea), so this question is more to satisfy my curiosity.

Norbo
Site Admin
Posts: 4858
Joined: Tue Jul 04, 2006 4:45 am

Re: Bepu v2 vs PhysX

Post by Norbo » Fri Sep 13, 2019 6:27 pm

Is it definitely only height being lost faster, or is it possibly also moving faster? There could be a difference in the frequency of timesteps (in real time).

There might also be a difference in the integration of gravity. The integrator callbacks could be modified to match if necessary.
(I'll probably end up sticking Bepu into Unity, unless you've got a better idea)
That could be difficult until some runtime bugs in unity are fixed: viewtopic.php?f=9&t=2528 :(

tomweiland
Posts: 96
Joined: Wed May 08, 2019 12:17 am

Re: Bepu v2 vs PhysX

Post by tomweiland » Sat Sep 21, 2019 5:37 pm

Sorry it took so long to respond, my computer's power supply died the other day :(
Norbo wrote:
Fri Sep 13, 2019 6:27 pm
Is it definitely only height being lost faster, or is it possibly also moving faster? There could be a difference in the frequency of timesteps (in real time).
Well I've set Unity's Fixed Timestep to 0.03333, and I use simulation.Timestep(0.03333) server side, so shouldn't that force them to run physics at the same fps?
From the info I can find on Unity's Maximum Allowed Timestep property, it sounds as though that might be playing into this—my computer is quite old (horrendous GPU by today's standards) so my game usually runs at around 20 fps. I won't be able to confirm whether or not this is an issue until I get my new computer next week, although the more I think about it, the more likely it seems that this is at least part of it.
Norbo wrote:
Fri Sep 13, 2019 6:27 pm
There might also be a difference in the integration of gravity. The integrator callbacks could be modified to match if necessary.
I'll save this as a last resort then, as I don't really have any clue as to how I'd do this.

Someone on Discord pointed out that it may be possible to calculate a formula, that, given the starting angle and velocity could calculate the position of the projectile at any point in time in Bepu's simulation. Are there any obvious flaws with this that haven't occurred to me?

tomweiland
Posts: 96
Joined: Wed May 08, 2019 12:17 am

Re: Bepu v2 vs PhysX

Post by tomweiland » Sat Sep 21, 2019 6:16 pm

I actually had the bright idea of disabling the terrain, water, and shadows, which allowed me to reach ~45 fps, so Maximum Allowed Timestep should no longer be part of the equation.

Here's an in-editor screenshot of the 2 trajectories: https://imgur.com/tgkFQGm
I simply put a sphere gizmo at each positional update. Purple is the server's positions, yellow is the client's representation. As you can see, the client drops much faster.

Both have gravity set to -9.8 and they both have the same physics timestep.

Norbo
Site Admin
Posts: 4858
Joined: Tue Jul 04, 2006 4:45 am

Re: Bepu v2 vs PhysX

Post by Norbo » Sat Sep 21, 2019 8:02 pm

I'm not very familiar with how unity implements/terms things- if a fixed timestep of 0.03333 actually corresponds to on-demand timesteps of some other amount (like 1/50) plus interpolation to smooth out discontinuities, the integration won't match. (I don't think that's what it means in this context- I think you've got a variable rate update loop which then takes multiple timesteps of that FixedTimestep as needed, so it should be correct.)
I'll save this as a last resort then, as I don't really have any clue as to how I'd do this.
In your IPoseIntegratorCallbacks.IntegrateVelocity implementation, you would have to add a modification to the position to take into account the continuous acceleration of gravity, rather than treating it as a piecewise impulse as the demos callbacks do. It might be also possible to modify physx to not use a continuous integration of gravity (if it is in fact using one to begin with).

Notably, if reducing the timestep duration to a smaller value causes the difference to shrink, it makes it more likely that the integrator differs. The simple discrete demos gravity integration will converge to the same result with sufficiently high update rates.
Someone on Discord pointed out that it may be possible to calculate a formula, that, given the starting angle and velocity could calculate the position of the projectile at any point in time in Bepu's simulation. Are there any obvious flaws with this that haven't occurred to me?
It would be difficult to match perfectly using an analytic formula, since the simulation result isn't analytic. Damping and such would be annoying to deal with. But you could implement a very simple projectile integrator of your own that exactly mirrors your IPoseIntegratorCallback integration. Probably:

Code: Select all

p += v * dt;
v = (v + gravity * dt) * dampingMultiplier;

tomweiland
Posts: 96
Joined: Wed May 08, 2019 12:17 am

Re: Bepu v2 vs PhysX

Post by tomweiland » Tue Sep 24, 2019 1:07 am

Norbo wrote:
Sat Sep 21, 2019 8:02 pm
Damping and such would be annoying to deal with. But you could implement a very simple projectile integrator of your own that exactly mirrors your IPoseIntegratorCallback integration.
I'm using this now (timestep = 0.03333):

Code: Select all

transform.position += velocity * timestep;
velocity = velocity + Physics.gravity * timestep;
and although the difference between the two results is quite a bit smaller, they still vary too much for this to be a usable solution. The reason I haven't added damping is because I'm actually not applying any damping to the cannonballs server side—any ideas for what else might be causing the discrepancy?

Norbo
Site Admin
Posts: 4858
Joined: Tue Jul 04, 2006 4:45 am

Re: Bepu v2 vs PhysX

Post by Norbo » Tue Sep 24, 2019 10:21 pm

Provided zero damping, that should be numerically identical to what the engine does. For example, in the SimpleSelfContainedDemo, modifying the Run function to be:

Code: Select all

        public static void Run()
        {
            //The buffer pool is a source of raw memory blobs for the engine to use.
            var bufferPool = new BufferPool();
            //Note that you can also control the order of internal stage execution using a different ITimestepper implementation.
            //For the purposes of this demo, we just use the default by passing in nothing (which happens to be PositionFirstTimestepper at the time of writing).
            var simulation = Simulation.Create(bufferPool, new NarrowPhaseCallbacks(), new PoseIntegratorCallbacks(new Vector3(0, -10, 0)));

            //Drop a ball on a big static box.
            var sphere = new Sphere(1);
            sphere.ComputeInertia(1, out var sphereInertia);
            var bodyHandle = simulation.Bodies.Add(BodyDescription.CreateDynamic(new Vector3(0, 50, 0), sphereInertia, new CollidableDescription(simulation.Shapes.Add(sphere), 0.1f), new BodyActivityDescription(0.01f)));

            simulation.Statics.Add(new StaticDescription(new Vector3(0, 0, 0), new CollidableDescription(simulation.Shapes.Add(new Box(500, 1, 500)), 0.1f)));

            var threadDispatcher = new SimpleThreadDispatcher(Environment.ProcessorCount);

            var body = simulation.Bodies.GetBodyReference(bodyHandle);
            var position = body.Pose.Position;
            var velocity = body.Velocity.Linear;

            //Now take 100 time steps!
            for (int i = 0; i < 100; ++i)
            {
                //Multithreading is pretty pointless for a simulation of one ball, but passing a IThreadDispatcher instance is all you have to do to enable multithreading.
                //If you don't want to use multithreading, don't pass a IThreadDispatcher.
                const float dt = 0.01f;
                simulation.Timestep(dt, threadDispatcher);
                position += velocity * dt;
                velocity += new Vector3(0, -10f, 0) * dt;
                Console.WriteLine($"Real pos: {body.Pose.Position}, predicted: {position}, error {position - body.Pose.Position}");
                Console.WriteLine($"Velocity: {body.Velocity.Linear}, predicted: {velocity}, error {velocity - body.Velocity.Linear}");
            }

            //If you intend to reuse the BufferPool, disposing the simulation is a good idea- it returns all the buffers to the pool for reuse.
            //Here, we dispose it, but it's not really required; we immediately thereafter clear the BufferPool of all held memory.
            //Note that failing to dispose buffer pools can result in memory leaks.
            simulation.Dispose();
            threadDispatcher.Dispose();
            bufferPool.Clear();
        }
results in an exact match on every frame.

A couple of possibilities:
1) If you're using PositionLastTimestepper instead of the default PositionFirstTimestepper, the order of position and velocity integration should be swapped (just update velocity first). If the SubsteppingTimestepper is used, you'll have to take multiple equivalent substeps to match the integration.
2) Given that unity is running a different (older) runtime, it may be using floating point operations with different behavior. It may emit instructions that end up using 80 bit precision internally before rounding back down to 32 bits, while RyuJIT on the serverside is running SIMD operations at 32 bit precision. This kind of thing should have minimal impact, although it may still cause visible divergence over the duration of a cannonball shot. Periodic blended server updates would be useful.

tomweiland
Posts: 96
Joined: Wed May 08, 2019 12:17 am

Re: Bepu v2 vs PhysX

Post by tomweiland » Thu Sep 26, 2019 2:38 am

Norbo wrote:
Tue Sep 24, 2019 10:21 pm
2) Given that unity is running a different (older) runtime, it may be using floating point operations with different behavior. It may emit instructions that end up using 80 bit precision internally before rounding back down to 32 bits, while RyuJIT on the serverside is running SIMD operations at 32 bit precision. This kind of thing should have minimal impact, although it may still cause visible divergence over the duration of a cannonball shot. Periodic blended server updates would be useful.
I'm using the default PositionFirstTimestepper so this must be the issue. It's not a huge difference, the client's cannonball is roughly 1 unity below the server's by the time it reaches the end of an average-ish trajectory—I'll probably go with updating the position every once in a while.

Post Reply