Physics synchronization over network

Started by
29 comments, last by hplus0603 13 years, 5 months ago
Yes, 90% of answer I know is that we send position, orientation, their changes, and same delta processing on server and client. But quaternions are different from positions, where you can just process x_new = x_old + vel * dt, and vel * 0.5 seconds + vel * 0.5 seconds = vel * 1 second.
VATCHENKO.COM
Advertisement
Quote:Server cannot simulate physics on that speed


You should not lock graphics simulation speed to physics simulation speed.
You should use fixed time steps, and render frames of graphics with whatever time is left (which may be more or less than the physics rate).
This means graphics needs to know how to interpolate between physics frames. That's good, because graphics also needs to interpolate to hide "snaps" in prediction.
Basically, you should use the client simulation as the prediction/extrapolation for each object.

That way, you get position, orientation, and even bounces predicted for free, so all you need to send is events where the client simulation would diverge from the server (such as colliding with an object that's invisible to the client). Plus a slow round-robin baseline to heal whatever objects might not have gotten caught by the event sending.

I think that what you are asking is "how can I make each physics frame smaller to send" and/or "how can I use interpolation to interpolate physics objects," when your end goal is "how can I consume less network bandwidth."
What I'm answering is "you don't want to interpolate physics objects; you want to run the simulation; you don't want to send lots of small physics frames; you want to send *no* physics frames most of the time."
enum Bool { True, False, FileNotFound };
Yes, I understand that client just interpolates in own timesteps that not equal to server steps. How to send less data and make good result - how to make this interpolation.

In linear velocity and position it's easy - we can send position correction and velocity and client just moves object to that position and update it like pos_new = pos_old + vel * dt until next correction. Server makes the same processing and if the difference of this pos_new and position after REAL physics processing are very different, then send correction. Goal is that client processes 50 frames per second and server does 4 frames and pos_new = pos_old + vel / 4 4 times is equal to pos_new = pos_old + vel / 50 50 times so it's synchronized.

Rotations are not so easy. They use quaternions and we can solve as:
1. Sending quaternion correction and angular velocity. But there is 2 formulas, q_new = 1/2 * q * w * dt which is not equal for different time steps and we have some exponential formulae, but physics engines don't use it, so in any case we get not-synchronized physics: or server-client, or server_processing-physics_processing.

2. Send timesteps and calculate quaternion difference (this_step and last_step). Then we loose rotations more than 180 degrees per step.
VATCHENKO.COM
Quote:how to make this interpolation


I already told you: Use the physics engine.
enum Bool { True, False, FileNotFound };
Server uses it already. Client doesn't have and will not.
VATCHENKO.COM
Quote:Sending quaternion correction and angular velocity. But there is 2 formulas, q_new = 1/2 * q * w * dt which is not equal for different time steps and we have some exponential formulae, but physics engines don't use it, so in any case we get not-synchronized physics: or server-client, or server_processing-physics_processing.
Not sure what you're saying there, but applying a constant angular velocity isn't difficult, even with varying timesteps; just multiply the angular velocity vector by the timestep, interpret its magnitude as rotation angle, build the quaternion, multiply by the previous quaternion. The exponential map stuff going on there is all implicit.

Oh, and a minor quibble: The nature of quaternions (double cover of SO(3)) means that they can unambiguously represent rotations of up to 360 degrees per step, not just 180 degrees.
Quote:Original post by Sneftel
they can unambiguously represent rotations of up to 360 degrees per step, not just 180 degrees.


Imagine rotation 180 degrees per step. So we inform client "rotate this to 180 degrees", but interpolation can go from 0 to 180 or from 0 to -180... It's like in modelling programs when you rotate object to 181 degrees, it transforms to -179.
VATCHENKO.COM
Quote:Original post by Anton Vatchenko
Quote:Original post by Sneftel
they can unambiguously represent rotations of up to 360 degrees per step, not just 180 degrees.


Imagine rotation 180 degrees per step. So we inform client "rotate this to 180 degrees", but interpolation can go from 0 to 180 or from 0 to -180... It's like in modelling programs when you rotate object to 181 degrees, it transforms to -179.
Nope! With a quaternion, if you rotate 180 degrees, and then rotate another 179 degrees in the same direction, the result is a rotation of 359 degrees, which is numerically distinct from a rotation of -1 degrees. They're unlike other rotation representations in this respect.
Try to find some online calculator and convert rotation to 359 degrees and to -1 degree. Result will be the same. It means that if we send in quaternion rotation to 181 degrees, then in quaternion style we get rotation to -179, so we cannot use more than 180 degree rotation per step.

But the problem is another - how to synchronize physics over network. In details... Should I send quaternion orientation and rotation speed and get unsynchronized server real physics and server easy-physics-calculator (that emulates client updater), or send some difference amount, but loose rotation more than 180 degrees per step, or other solutions?
VATCHENKO.COM
Here's an other approach: binary delta compression.

1. Try to reduce the obviously data (i.e. use only x,y and recalculate z when working with normals).
2. Try to compress all left data to i.e. fix point number of a certain bit width(i.e. map a float value which will be always between 0.0-1.0 to 0-255 or 0-4096, try to check which precision is enough for rendering).
3. Define one fix data block for each physical object inside your focus considering 1. and 2.
4. Defnine a memory block which could hold up to 100 datablocks.

struct {  int pos_x : 22;  int pos_y : 22;  int pos_w : 22;  int quat_x: 10;  int quat_y: 10; //note calculate z from x,y  int quat_w: 10;} OrientationDataBlock;OrientationDataBlock clientDataBlock[100];


Assign all visible objects to an according datablock. Clear a datablock if it is not necessary any longer. Don't shift datablocks, just "activate/deactivate" them.

Well, now you can work with the binary data of this client array of data blocks. Send the whole data block once at the start, after that remember always the last send data array. Now you need only to send the delta (=previous_send_data_block XOR current_data_block), this will contain a 1 bit only at bit-positions which has been changed. So, *hopefully* most of the time there are more 0 than 1 which can be easily compressed with a RLE compression. Send this compressed data block to the client and uncompress it there.

Improvement: when only few data blocks are changing in a single frame, try to "defragmentate" your data block by swapping used blocks from the end of the array to the front (unused blocks). This way you need only to send the data between the first and last used block.

This topic is closed to new replies.

Advertisement