[SOLVED]Desync between spatial translation and physics location

I’m experiencing issues with my spaceship game: i’m using physics to move it as a dynamic object (RigidBodyControl on a BulletAppState) represented by a Node and I’m applying different forces on them, such as applyTorqueImpulse for rotations( which works perfectly) and applyForce for moving it forward and “backwards”.

However I’ve got a strange jittering problem with my ship whenever I try to apply any type of force to move it forward (applyImpulse, applyForce) and a desync between its physics and local translation resulting to it.

Don’t have any clue about how to solve it, already tried to isolate and find the problem but without any result. Tried even with a simple BoxCollisionShape in case it was some collision issue but the problem wasn’t gone.
Happy to receive any kind of help, support or advices. Thanks in advance.

Video about the issue here : https://www.youtube.com/watch?v=AMzdWTxwMLk

Main class preview

package mygame;

import planetsLogic.Pianeti;
import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh.Type;
import com.jme3.effect.shapes.EmitterSphereShape;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.PointLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix3f;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Cylinder;
import com.jme3.scene.shape.Sphere;
import com.leapmotion.leap.*;

/**
 * test
 * @author Dige
 */
public class Main extends SimpleApplication {
    BulletAppState bulletAppState;
    Pianeti planets;
    Geometry[] planetsGeometries;
    Geometry saturnRing;
    Spatial shipSpatial;
    Node ship;
    LeapMotionListener listener;
    Controller controller;
    int time;

    private AnalogListener analogListener = new AnalogListener() {
        public void onAnalog(String name, float value, float tpf) 
        {   
            if (name.equals("destra")) //YAW RIGHT
            {   
                Vector3f torqueYaxis = ship.getControl(RigidBodyControl.class).getPhysicsRotation().mult(Vector3f.UNIT_Y);
                ship.getControl(RigidBodyControl.class).applyTorqueImpulse(torqueYaxis.negate()); 
            } 
            if (name.equals("sinistra")) //YAW LEFT
            {     
                Vector3f torqueYaxis = ship.getControl(RigidBodyControl.class).getPhysicsRotation().mult(Vector3f.UNIT_Y);
                ship.getControl(RigidBodyControl.class).applyTorqueImpulse(torqueYaxis);
            }
            if (name.equals("su")) //PITCH UP
            {     
                Vector3f torqueXaxis = ship.getControl(RigidBodyControl.class).getPhysicsRotation().mult(Vector3f.UNIT_X);
                ship.getControl(RigidBodyControl.class).applyTorqueImpulse(torqueXaxis.negate().mult(10));
            } 
            if (name.equals("giu")) //PITCH DOWN
            {     
                Vector3f torqueXaxis = ship.getControl(RigidBodyControl.class).getPhysicsRotation().mult(Vector3f.UNIT_X);
                ship.getControl(RigidBodyControl.class).applyTorqueImpulse(torqueXaxis.mult(10));
            } 
            if (name.equals("inclinaSX")) //ROLL LEFT
            {     
                Vector3f torqueZaxis = ship.getControl(RigidBodyControl.class).getPhysicsRotation().mult(Vector3f.UNIT_Z);
                ship.getControl(RigidBodyControl.class).applyTorqueImpulse(torqueZaxis.mult(10).negate());
            } 
            if (name.equals("inclinaDX")) //ROLL RIGHT
            {     
                Vector3f torqueZaxis = ship.getControl(RigidBodyControl.class).getPhysicsRotation().mult(Vector3f.UNIT_Z);
                ship.getControl(RigidBodyControl.class).applyTorqueImpulse(torqueZaxis.mult(10));
            } 
            if(name.equals("avanti")) //FORWARD
            {     
                float[] angles = new float[3];
                angles = ship.getLocalRotation().toAngles(angles);
                ship.getControl(RigidBodyControl.class).applyImpulse(new Vector3f((float)Math.sin(angles[1])*100,0f,(float)Math.cos(angles[1])*100),new Vector3f(0, 0, 0));      
            } 
            if(name.equals("indietro")) //BACKWARDS OR BRAKE, STILL TO DEFINE AND COMPLETE
            {     
                /*if(value>0)
                    nave.getControl(RigidBodyControl.class).setFriction(8000f);
                else
                    nave.getControl(RigidBodyControl.class).setFriction(1f);*/
            }
            if (name.equals("Time forward")) //ACCELERATE PLANET UPDATE
            {     
                if(value > 0)
                    if(time < 1)    
                       aggiornaSistema(time++);
                    else
                       aggiornaSistema(time *= 2);
            }
            else if (name.equals("Time backwards"))  //DECELERATE PLANET UPDATE
            {     
                if(value > 0)
                {
                   if(time <= 1)    
                       aggiornaSistema(time--);
                   else
                       aggiornaSistema(time /= 2);
                }
                if(time < 0)
                    time = 0;
            }
        }
    };
    
    public static void main(String[] args) {
        Main app = new Main();       
        app.setDisplayStatView(false);
        app.start();
    }
    
    @Override
    public void simpleInitApp() {
        //PHYSICS
        setPhysics();
        
        //SHIP
        setShip();
        
        //UI
        //setUI();
        
        //LEAPMOTION
        //setLeap();
        
        //CAMERA
        setCam();
        
        //PLANETS
        setPlanets();
        
        //INPUT MANAGER
        setMappings();
        
        //SATURN RING
        setSaturnRing();
        
        //STARS
        setStars();
    }

    @Override
    public void simpleUpdate(float tpf) {
        //PLANETS UPDATE  
        aggiornaSistema(time);
        
        //CAM ROTATION UPDATE
        cam.setRotation(ship.getLocalRotation());
        rotateCamera(this.getCamera(), 0, tpf, ship.getLocalTranslation());
        
        //CHECKING DISTANCE DIFFERENCE ON Z AXIS
        if(FastMath.abs(ship.getLocalTranslation().getZ())- FastMath.abs(ship.getControl(RigidBodyControl.class).getPhysicsLocation().getZ())>3)
            System.out.println("distance too high");
        System.out.println("local" + ship.getLocalTranslation());
        System.out.println("phys" + ship.getControl(RigidBodyControl.class).getPhysicsLocation());
        
        /* LEAP MOTION CONTROL
        Frame frame = controller.frame();
        for(Hand hand : frame.hands())
        {
              String handType = hand.isLeft() ? "Left hand" : "Right hand";
              System.out.println(handType + " - " + hand.palmPosition());
              if(hand.isLeft() && hand.palmPosition().getY()> 40)
                  analogListener.onAnalog("avanti", 0.5f, tpf);
              if(hand.isRight()){
                  if(hand.palmPosition().getX()>80)
                      analogListener.onAnalog("destra", 0.5f, tpf);
                  else if(hand.palmPosition().getX()<-40)
                      analogListener.onAnalog("sinistra", 0.5f, tpf);
              }
        }*/

        /* COLLISION TO IMPLEMENT
         * for(int i=0; i<geom.length;i++)
          {
            CollisionResults results = new CollisionResults();
            geom[i].collideWith(nave, results);
            // Use the results
            if (results.size() > 0) 
            {
                System.out.println("eeeeeeeeee");
            }
        }*/

    }

    @Override
    public void simpleRender(RenderManager rm) 
    {
        
    }
    
    private void aggiornaSistema(int giorniDaTrascorrere){
        for(int giorno = 0; giorno < giorniDaTrascorrere; giorno++)
        {
          planets.aggiorna();
          for(int i=1;i<planetsGeometries.length;i++)
          {
            if(i==7)
                saturnRing.setLocalTranslation((float)planets.getPosizioneCorpo(i-1).getY(),0f, (float)planets.getPosizioneCorpo(i-1).getX());
                planetsGeometries[i].setLocalTranslation((float)planets.getPosizioneCorpo(i-1).getY(),0f, (float)planets.getPosizioneCorpo(i-1).getX());  
                planetsGeometries[i].rotate(0, 0, (float)planets.getLunghezzaRotazioneCorpo(i-1));
          }
        }
    }
    
    /*------------------------------------------------------------------------------------------
     *---   SETTERS
     *------------------------------------------------------------------------------------------
     */
    
    void setPhysics() {
        bulletAppState = new BulletAppState();
        stateManager.attach(bulletAppState);
        bulletAppState.getPhysicsSpace().setGravity(new Vector3f(0, 0, 0)); 
        bulletAppState.setDebugEnabled(true);
    }

    void setMappings(){
        inputManager.addMapping("Time forward", new KeyTrigger(KeyInput.KEY_N));
        inputManager.addMapping("Time backwards", new KeyTrigger(KeyInput.KEY_B));
        inputManager.addMapping("sinistra", new KeyTrigger(KeyInput.KEY_A));
        inputManager.addMapping("destra", new KeyTrigger(KeyInput.KEY_D));
        inputManager.addMapping("su", new KeyTrigger(KeyInput.KEY_W));
        inputManager.addMapping("giu", new KeyTrigger(KeyInput.KEY_S));
        inputManager.addMapping("avanti", new KeyTrigger(KeyInput.KEY_SPACE));
        inputManager.addMapping("indietro", new KeyTrigger(KeyInput.KEY_LMENU));
        inputManager.addMapping("inclinaSX", new KeyTrigger(KeyInput.KEY_LEFT));
        inputManager.addMapping("inclinaDX", new KeyTrigger(KeyInput.KEY_RIGHT));
        inputManager.addMapping("su", new KeyTrigger(KeyInput.KEY_DOWN));
        inputManager.addMapping("giu", new KeyTrigger(KeyInput.KEY_UP));
        inputManager.addListener(analogListener, new String[]{"Time forward","Time backwards","sinistra","destra","su","giu","avanti","indietro","inclinaSX","inclinaDX"});
    }
    
    void setUI() {
        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
        BitmapText ch = new BitmapText(guiFont, false);
        ch.setSize(guiFont.getCharSet().getRenderedSize());
        ch.setText("W, E, Space, LeftMouse, MiddleMouse, RightMouse"); // crosshairs
        ch.setColor(new ColorRGBA(1f, 0.8f, 0.1f, 1f));
        ch.setLocalTranslation(settings.getWidth() * 0.3f, settings.getHeight() * 0.1f, 0);
        guiNode.attachChild(ch);
    }

    void setCam() {
        flyCam.setEnabled(false);
        cam.setFrustumFar(30000);
    }
    
    void setShip(){
        shipSpatial = assetManager.loadModel("Models/Wraith Raider Starship/Wraith Raider Starship.j3o");
        ship = (Node)shipSpatial;
        ship.setLocalScale(0.01f);
        ship.setLocalTranslation(new Vector3f(2000,0,500));
        rootNode.attachChild(ship);
        
        CollisionShape shape = CollisionShapeFactory.createBoxShape(shipSpatial);
        RigidBodyControl nave_phys = new RigidBodyControl( shape , 100f );
        nave_phys.setKinematic(false);
        nave_phys.setFriction(100f);
        nave_phys.setAngularDamping(0.8f);
        nave_phys.setPhysicsLocation(new Vector3f(2000,0,500));
        ship.addControl(nave_phys);
        bulletAppState.getPhysicsSpace().add(nave_phys);
    }
    
    void setStars(){
        AmbientLight ambient = new AmbientLight();
        ambient.setColor(ColorRGBA.White);
        rootNode.addLight(ambient); 
        
        final ParticleEmitter stars = new ParticleEmitter("Emitter", Type.Point, 90000);
        stars.setShape(new EmitterSphereShape(Vector3f.ZERO, 100000f));
        stars.setGravity(0, 0, 0);
        stars.setLowLife(60);
        stars.setHighLife(600);
        stars.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 0, 0));
        stars.setImagesX(15);
        stars.setStartSize(0.05f);
        stars.setEndSize(0.05f);
        stars.setStartColor(ColorRGBA.White);
        stars.setEndColor(ColorRGBA.White);
        stars.setSelectRandomImage(true);
        stars.emitAllParticles();
       
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
        mat.setBoolean("PointSprite", true);
        //mat.setTexture("Texture", assetManager.loadTexture("Effects/Smoke/Smoke.png"));
        stars.setMaterial(mat);

        rootNode.attachChild(stars);
    }
    
    void setSaturnRing(){
        saturnRing = new Geometry("Anello", new Cylinder(100,100,141000/1500,0.5f,true,true));               
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setTexture("ColorMap", assetManager.loadTexture("Textures/full_saturn_ring.png"));
        saturnRing.rotate((float)Math.toRadians(270), 0f, 0f);
        saturnRing.setMaterial(mat);
        rootNode.attachChild(saturnRing);
    }
    
    void setPlanets(){
        planets = new Pianeti();
        time = 1;
        
        Sphere[] sfere = new Sphere[10];
        planetsGeometries = new Geometry[10];
        
        Material mat;
        
        for(int i = 0; i < sfere.length;i++)
        {
            if(i==0)
            {
                mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
                mat.setTexture("ColorMap", assetManager.loadTexture("Textures/texture_sun.jpg"));
                sfere[i] = new Sphere(100,100,(int)planets.getGrandezzaStella());
                planetsGeometries[i] = new Geometry("Sole", sfere[i]);
                PointLight sole = new PointLight();      
                sole.setPosition(planetsGeometries[i].getLocalTranslation());
                sole.setColor(ColorRGBA.White);
                rootNode.addLight(sole);
            }    
            else
            {
                mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
                mat.setTexture("DiffuseMap", assetManager.loadTexture("Textures/"+planets.getTexture(i-1)+".jpg"));
                sfere[i] = new Sphere(100, 100, (int)planets.getGrandezzaCorpo(i-1));
                planetsGeometries[i] = new Geometry("Sphere", sfere[i]);
                
            }
            planetsGeometries[i].setMaterial(mat);
            planetsGeometries[i].rotate((float)Math.toRadians(270), 0f, 0f);
            rootNode.attachChild(planetsGeometries[i]);
            
         } 
    }
    
    void setLeap()//-Djava.library.path=K:/MilkyWay/assets/Natives
    {
        
        // Create a sample listener and controller
        listener = new LeapMotionListener();
        controller = new Controller();
        controller.enableGesture(Gesture.Type.TYPE_SWIPE);
        // Have the sample listener receive events from the controller
        controller.addListener(listener);
        // Remove the sample listener when done
        controller.removeListener(listener);
    }
    
    /*------------------------------------------------------------------------------
     *--- AUXILIARY METHODS
     *------------------------------------------------------------------------------
     */
    Quaternion toQuaternion(float x, float y, float z)
    {
        Quaternion aus;
        
        double h = y*Math.PI/360;
        double a = z*Math.PI/360;
        double b = x*Math.PI/360;
        double c1 = Math.cos(h);
        double c2 = Math.cos(a);
        double c3 = Math.cos(b);
        double s1 = Math.sin(h);
        double s2 = Math.sin(a);
        double s3 = Math.sin(b);
        float qw = (float)(c1*c2*c3 - s1*s2*s3);
        float qx = (float)(s1*s2*c3 + c1*c2*s3);
        float qy = (float)(s1*c2*c3 + c1*s2*s3);
        float qz = (float)(c1*s2*c3 - s1*c2*s3);
        
        aus = new Quaternion(qx,qy,qz,qw);
        
        return aus;
    }
    
    protected void rotateCamera(Camera cam, float value, float tpf, Vector3f centre){

        float rotationSpeed = 1000;
        cam.setLocation(ship.getLocalTranslation());
        Vector3f axis = cam.getUp();

        Matrix3f mat = new Matrix3f();

        mat.fromAngleNormalAxis(rotationSpeed * value * tpf, axis);



        Vector3f upDir = cam.getUp();

        Vector3f leftDir = cam.getLeft();

        Vector3f dir = cam.getDirection();



        mat.mult(upDir, upDir);

        mat.mult(leftDir, leftDir);

        mat.mult(dir, dir);



        Quaternion q = new Quaternion();

        q.fromAxes(leftDir, upDir, dir);

        q.normalizeLocal();



        cam.setAxes(q);



        dir = cam.getDirection();

        dir = dir.mult(12.0f);

        Vector3f invDir;
        invDir = new Vector3f(-dir.x, -dir.y+3, -dir.z);

        Vector3f position = invDir.add(centre);

        cam.setLocation(position);

    }
    
    
}

You just have to be aware that the control applies the physics location to the spatial. So if you e.g. use the position of the spatial before the control applies the physics location, then the control applies the location and stuff gets rendered you get unexpected results.

1 Like

In other words, don’t do camera updates in simpleUpdate() because it runs before just about everything else… so you will get jitter because you are setting the camera to what will be old values by the time the frame is rendered.

Do it in simpleRender() (or an AppState render… since you really should be using app states for this stuff and leave your main class mostly empty).

1 Like

Many thanks for the advices, I actually solved the problem moving the cam updates in the simpleRender() :smiley:

to pspeed I was considering using app states but I was testing if the core functionalities were working