[SOLVED] SimEtheral: Problem with entity jittering at client side

Hi

I want to fake multiple spaces/scenes by putting mob entities in one scene super far apart from mob entities in other scenes in the Y direction in @pspeed 's SimEtheral library.

To do so I added this in to update method in ZoneNetworkSystem :

        // A lot of times you can fake multiple spaces just by putting everyone super 
        // far apart in some standard direction (y is common).
        // Then you could achieve multiple “spaces” by setting the y values to spaceId * 1000 or whatever. 
        // Then the normal zone filtering will make sure clients only see their ‘space’.      
        pos.y += (body.getSceneId().getId() * 1000);

the whole class in case:

/**
 * A game system that registers a listener with the physics system and then
 * forwards those events to the SimEtheral zone manager, which in turn will
 * package them up for the clients in an efficient way.
 *
 * @author Paul Speed
 */
public class ZoneNetworkSystem extends AbstractGameSystem {

    static Logger log = LoggerFactory.getLogger(ZoneNetworkSystem.class);
        
    private ZoneManager zones;
    private PhysicsObserver physicsObserver = new PhysicsObserver();

    public ZoneNetworkSystem(ZoneManager zones) {
        this.zones = zones;
    }

    @Override
    protected void initialize() {
        getSystem(PhysicsSystem.class).addPhysicsObjectListener(physicsObserver);
    }

    @Override
    protected void terminate() {
        getSystem(PhysicsSystem.class).removePhysicsObjectListener(physicsObserver);
    }

    /**
     * Listens for changes in the physics objects and sends them to the zone
     * manager.
     */
    private class PhysicsObserver implements PhysicsObjectListener {
        
        private Vector3f posf = new Vector3f();
        private Quaternion orientf = new Quaternion();
        
        private Vec3d pos = new Vec3d();
        private Quatd orient = new Quatd();
        
        // We probably won't have many zones, if we even have more than one.
        // The physics objects do not provide any sort of accurate bounds so
        // we'll guess at a size that is "big enough" for any particular mobile
        // object.  2x2x2 meters should be good enough... until it isn't.
        private AaBBox box = new AaBBox(1);

        @Override
        public void startFrame(SimTime time) {
            zones.beginUpdate(time.getTime());
        }
        
        @Override
        public void endFrame() {
            zones.endUpdate();
        }
        
        @Override
        public void added(EntityPhysicsObject object) {
            // Don't really care about this
        }

        @Override
        public void updated(EntityPhysicsObject object) {
            if (object instanceof EntityRigidBody && ((EntityRigidBody) object).getMass() != 0) {
                EntityRigidBody body = (EntityRigidBody) object;
 
                // Grab the latest reference frame in our temp variables               
                body.getPhysicsLocation(posf);
                body.getPhysicsRotation(orientf);
 
                // Convert them to mathd values               
                pos.set(posf);
                orient.set(orientf);
            
                if( log.isTraceEnabled() ) {
                    log.trace("body:" + object.getEntityId() + "  pos:" + pos);
                }
                
                // A lot of times you can fake multiple spaces just by putting everyone super 
                // far apart in some standard direction (y is common).
                // Then you could achieve multiple “spaces” by setting the y values to spaceId * 1000 or whatever. 
                // Then the normal zone filtering will make sure clients only see their ‘space’.      
                pos.y += (body.getSceneId().getId() * 1000);
                        
                // Update the bounds so other things know 
                // where the object is for real
                //AaBBox bounds = body.getBounds();
                //bounds.setCenter(pos);
                
                // Move the box as appropriate
                // Note: we don't so much care about bounds as there won't be many zones.
                // So we set it rather arbitrarily to 'big enough'.
                box.setCenter(pos);
                
                zones.updateEntity(body.getEntityId().getId(), true, pos, orient, box);
            }
        }

        @Override
        public void removed(EntityPhysicsObject object) {
            zones.remove(object.getEntityId().getId());
        }
    }
}

and added this at ModelViewState.updateSpatial() method :

// Transfer location from SimEtheral zone to client view.
loc.y -= (sceneId * 1000); 

whole method in case:

public void updateSpatial(float tpf) {

            // Grab a consistent time for this frame
            long time = timeState.getTime();

            // Look back in the brief history that we've kept and
            // pull an interpolated value.  To do this, we grab the
            // span of time that contains the time we want.  PositionTransition
            // represents a starting and an ending pos+rot over a span of time.
            PositionTransition trans = buffer.getTransition(time, true);
            if (trans != null) {
                Vector3f loc = trans.getPosition(time, true);
                Quaternion rotation = trans.getRotation(time, true);

                // Transfer location from SimEtheral zone to client view.
                loc.y -= (sceneId * 1000);           
               
                spatial.setLocalTranslation(loc);
                spatial.setLocalRotation(rotation);
                setVisible(trans.getVisibility(time));
}

but after this change I am getting a jittering at entity’s Y location at client side.

here are some debug output from model position at client side before and after change:

Before:

loc.y = 1.5412678
loc.y = 1.5412679
loc.y = 1.5412678
loc.y = 1.5412679
loc.y = 1.5412679
loc.y = 1.5412679
loc.y = 1.5412679
loc.y = 1.5412679
loc.y = 1.5412679
loc.y = 1.5412679
loc.y = 1.5412679
loc.y = 1.5412679
loc.y = 1.5412679
loc.y = 1.541268
loc.y = 1.5412679

After :

loc.y = 1.53125
loc.y = 1.53125
loc.y = 1.53125
loc.y = 1.53125
loc.y = 1.53125
loc.y = 1.53125
loc.y = 1.5
loc.y = 1.53125
loc.y = 1.53125
loc.y = 1.53125
loc.y = 1.53125
loc.y = 1.53125
loc.y = 1.53125
loc.y = 1.5
loc.y = 1.53125
loc.y = 1.53125
loc.y = 1.5
loc.y = 1.53125
loc.y = 1.5
loc.y = 1.53125
loc.y = 1.5625
loc.y = 1.5625
loc.y = 1.53125
loc.y = 1.5625
loc.y = 1.53125
loc.y = 1.53125
loc.y = 1.5625
loc.y = 1.53125
loc.y = 1.53125
loc.y = 1.53125
loc.y = 1.5
loc.y = 1.53125
loc.y = 1.53125
loc.y = 1.5625
loc.y = 1.53125

It is variant from 1.50 ~ 1.56 in Y location at client side.

Note, the entity position which is published from ZoneNetworkSystem at server side is a fixed value (pos.y = 1.5409740209579468) and does not change over time, so not a problem with server-side physics system

I am curious to know why this happens and how can I fix it.
Any help is appreciated

Note, when I change it from sceneId * 1000 to sceneId * 100 jittering decreases also.

Probably float imprecision. Try double and see if it works better. If it does, you know you have to work around this issue completely.

Why? float looses precision the higher the exponent is, e.g. 1.50 is fine, but 15000000000000000 could be stored as 14999999999999 (you get it).
Then, you subtract it, but don’t get the same result.

2 Likes

@Darkchaos that seems not to be the case.

As I am already using bullet at server-side so positions come in Vector3f. Then I convert it to Vec3d and send it to ZoneManager.
I slightly modified my code to directly do the addition (posf.y += (body.getSceneId().getId() * 1000);) also on float vector then convert it to Vect3d, but yet I am seeing jittering at the client-side.

Here is my new changes:

// Grab the latest reference frame in our temp float variables from bullet physics              
body.getPhysicsLocation(posf);
body.getPhysicsRotation(orientf);

if( log.isTraceEnabled() ) {
    log.trace("body:" + object.getEntityId() + "  pos:" + posf);
}

// A lot of times you can fake multiple spaces just by putting everyone super 
// far apart in some standard direction (y is common).
// Then you could achieve multiple “spaces” by setting the y values to spaceId * 1000 or whatever. 
// Then the normal zone filtering will make sure clients only see their ‘space’.      
posf.y += (body.getSceneId().getId() * 1000);

// Convert them to mathd values               
pos.set(posf);
orient.set(orientf);

As DarkChaos alludes to, this is the issue. Note the Vector3f above which will kill your precision pretty significantly.

Probably you want to switch to https://github.com/Simsilica/SimMath/blob/master/src/main/java/com/simsilica/mathd/trans/PositionTransition3d.java

But I am already doing the math in float at the server side then convert the final result to Vect3d to send it to ZoneManager and reconvert it to Vector3f again at client side. Do not understand how it will kill precision, unless SimEtheral internally modifies the values I am giving to it through the ZoneManager.

That’s like: I have a Racecar Engine, which is put through a VW Beetle Transmission/Gearbox and then I have F1 Tires.
Once you ever use float, you loose that precision.

The main problem is just that float cannot represent values exactly, it will pick another one close to it, and that information is lost forever.

Note that Vector3d isn’t what you’d use to solve either, that’s why I said overthink.
Instead you’d use an integer (zone id) and display it relative (so you don’t ever have values larger than 1000 in your float).

Edit: For instance this site https://dzone.com/articles/never-use-float-and-double-for-monetary-calculatio shows that adding 0.2 always, does not mean that the result is dividable by 0.2 :smiley:
But BigDecimal and stuff isn’t really your solution here. You don’t need that precision, because as your code already shows you have a zoneId and it’ll offset by thousands

1 Like

You are using the regular old float-based PositionTransition that is float based so it’s doing it’s calculations in the lower crappy precision float because it’s float based. So the interpolation is happening in float and not double because it’s float based.

So the float based calculations to do the interpolation are interpolating lower precision floats and you are probably losing precision in that float based calculation before redoing the multiplication in double.

scale y in double. transfer it in double. interpolate it in double. unscale it in double… then set it to the float.

1 Like

Ah, I see now, I was not considering this one. Sorry for my ignorance.
After switching to PositionTransition3d I do not see jittering anymore.

Thanks, guys. :slightly_smiling_face:

2 Likes

I’m glad it turned out to be a simple fix.

1 Like