Rotating my spaceship with Physics

Hey!



My first post here and Hooray for JMonkey! I have to say that I am perfectly satisfied with this engine.  For me as a programming-noob it is really easy to use, but now I'm really stuck.

I am trying to make some kind of space simulator. To rotate my spaceship I am using the standard RotationController manipulated with following code:


public void update(float time) {
   float value = positive.getValue() - negative.getValue();

   if (value != 0.0f) {            
      node.getLocalRotation().mult(dir, torque);
      torque.mult(rotSpeed, torque);
      torque.mult(value, torque);
      node.addTorque(torque);
   }
         
}



But this only works when I press one key at once. Otherwise the rotation accelerates unlimited.
Are there any solutions?

Welcome to the jME boards :slight_smile:



It should 'work' with multiple keys as well. The problem is that it is really hard to control a space ship this way. The effect (getting unlimited acceleration) is realistic.

You can reduce the rotation speed with a friction callback automatically. That'd be the simple solution.

If you want to avoid friction (usually in reality there is nearly no friction in space), you'd need to program a more sophisticated controller which computes more complex forces/torques to avoid a spinning ship.

Hi irrisor!



Thanks for your quick reply :)!



You are right. I have reduced the value for the rotation speed as well as the value for angularFrition.

It's not exactly a spaceshooter. It should be set in a similar world you know from the cartoon series Dragon Hunters. So friction is desired ;).

Now the behaviour seems to be more plausible when I press two keys at once. But when I now just press one Key, it rotates far too slow. I don't get why it makes a difference when I press one key and when I press multiple keys. Do you think my update-method is correct? Before I applied Physics I used JME's RotationController without problems. So I think something is wrong with my method.



I've noticed that Stardust also uses the normal RotationController. But when I use it with Physics, it causes a jitter in the rotation.

Yes, your method looks fine to me. And I would expect the ship to rotate faster with two keys - it gets more torque applied…

Milou said:

I've noticed that Stardust also uses the normal RotationController. But when I use it with Physics, it causes a jitter in the rotation.


Because its much easier than rotating with forces :)
The enemies in stardust also rotate without forces, they simply use lookAt, i didn't notice any jitter right now.
But of course its cleaner to use forces instead of setLocalRotation if you want to avoid any problems.

Maybe you could provide a simpleGame TestCase to reproduce the problem with multiple keys.

Thank you both for your help! I guess rotating with torque isn't as simple as I thought. So I will continue using the RotationController. You are right, Core-Dump, I tried a simple Example with RotationController and DynamicNode and there was no jitter. Thus the rotation works fine and I have to check what else causes the jitter in my game.


I discovered that the jitter occurs when I derive my gamestate from PhysicsGameState.

I'm doing the following:


import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.system.DisplaySystem;
import com.jme.util.Timer;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.input.controls.GameControl;
import com.jme.input.controls.GameControlManager;
import com.jme.input.controls.binding.KeyboardBinding;
import com.jme.input.controls.binding.MouseAxisBinding;
import com.jme.input.controls.controller.Axis;
import com.jme.input.controls.controller.RotationController;
import com.jme.input.controls.binding.MouseOffsetBinding;
import com.jme.light.DirectionalLight;
import com.jme.math.Vector3f;
import com.jme.scene.shape.Box;
import com.jme.scene.state.LightState;

import com.jmex.editors.swing.settings.GameSettingsPanel;
import com.jmex.game.StandardGame;
import com.jmex.game.state.GameStateManager;
import com.jmex.physics.DynamicPhysicsNode;
import com.jmex.physics.StaticPhysicsNode;
import com.jmex.physics.callback.FrictionCallback;
import com.jmex.physics.geometry.PhysicsBox;
import com.jmex.physics.util.states.PhysicsGameState;

public class TestPhysicsRotation extends PhysicsGameState{
   private Renderer renderer = DisplaySystem.getDisplaySystem().getRenderer();
   private Camera cam;
   private Timer timer;
   
   private GameControlManager gameControlManager;
   private FrictionCallback frictCallback = new FrictionCallback();
   private DynamicPhysicsNode playerNode;
   
   public TestPhysicsRotation(String name){
      super(name);
      initSystem();
      initGame();
   }
   private void initSystem(){
      cam=renderer.getCamera();
      timer=Timer.getTimer();
      
      KeyBindingManager.getKeyBindingManager().add("exit",KeyInput.KEY_ESCAPE);
   }
   private void initGame(){
      gameControlManager=new GameControlManager();
         
       getPhysicsSpace().addToUpdateCallbacks(frictCallback);
       
      playerNode=getPhysicsSpace().createDynamicNode();
      rootNode.attachChild(playerNode);
      
      PhysicsBox playerBox=playerNode.createBox("playerBox");
      playerBox.setLocalScale(new Vector3f(1,1,1));
      
      playerNode.setAffectedByGravity(false);
      
      frictCallback.add(playerNode,50,50);      
      
      GameControl yawLeft = gameControlManager.addControl("yawLeft");
      yawLeft.addBinding(new MouseOffsetBinding(MouseAxisBinding.AXIS_X,true));
       GameControl yawRight = gameControlManager.addControl("yawRight");
       yawRight.addBinding(new MouseOffsetBinding(MouseAxisBinding.AXIS_X,false));
       RotationController yawControl = new RotationController(playerNode, yawLeft,  yawRight, 0.5f, Axis.Y);
       playerNode.addController(yawControl);
         
      GameControl rollLeft = gameControlManager.addControl("rollLeft");
      rollLeft.addBinding(new KeyboardBinding(KeyInput.KEY_LEFT));
       GameControl rollRight = gameControlManager.addControl("rollRight");
       rollRight.addBinding(new KeyboardBinding(KeyInput.KEY_RIGHT));
       RotationController rollControl = new RotationController(playerNode, rollRight,  rollLeft, 0.5f, Axis.Z);
       playerNode.addController(rollControl);
      
       GameControl pitchUp = gameControlManager.addControl("pitchUp");
       pitchUp.addBinding(new MouseOffsetBinding(MouseAxisBinding.AXIS_Y, false));
       GameControl pitchDown = gameControlManager.addControl("pitchDown");
       pitchDown.addBinding(new MouseOffsetBinding(MouseAxisBinding.AXIS_Y, true));
       RotationController pitchControl = new RotationController(playerNode, pitchDown,  pitchUp, 0.5f, Axis.X);
       playerNode.addController(pitchControl);
      
       GameControl forward = gameControlManager.addControl("forward");
        forward.addBinding(new KeyboardBinding(KeyInput.KEY_UP));
        GameControl backward = gameControlManager.addControl("backward");
        backward.addBinding(new KeyboardBinding(KeyInput.KEY_DOWN));
        PhysicsThrustController thrustControl = new PhysicsThrustController(playerNode, Axis.Z, forward, backward, 1, 100, 20, 20);
        playerNode.addController(thrustControl);
      
       buildEnvironment();
      buildLighting();
      
      rootNode.updateGeometricState(0,true);
      rootNode.updateRenderState();
   }
   private void buildLighting(){
      DirectionalLight light=new DirectionalLight();
      light.setDiffuse(new ColorRGBA(1f,1f,1f,1));
      light.setAmbient(new ColorRGBA(5f,5f,5f,1));
      light.setSpecular(new ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f));
      light.setDirection(new Vector3f(150,100,150));
      light.setEnabled(true);
      
      LightState lightState=this.renderer.createLightState();
      lightState.attach(light);
      this.rootNode.setRenderState(lightState);
   }
   private void buildEnvironment(){
       StaticPhysicsNode staticNode=getPhysicsSpace().createStaticNode();
       
       for(int i=0;i<=20;i++){
          Box box=new Box("box"+i,new Vector3f(),1,1,1);
          box.setLocalTranslation(new Vector3f(i*i,-5,5+i*10));
          staticNode.attachChild(box);
       }
      staticNode.generatePhysicsGeometry();
      rootNode.attachChild(staticNode);
    }
   public void update(float tpf){
      super.update(tpf);
      timer.update();
      tpf = timer.getTimePerFrame();
      cam.setLocation(playerNode.getWorldTranslation());
       cam.setAxes(playerNode.getLocalRotation());
      cam.update();
      rootNode.updateGeometricState(tpf, true);
      
      if(KeyBindingManager.getKeyBindingManager().isValidCommand("exit")){
         System.exit(0);
      }
   }
   public static void main(String[] args){
      StandardGame testGame=new StandardGame("testGame");
      
      try{
         GameSettingsPanel.prompt(testGame.getSettings());
         testGame.getSettings().setSamples(4);
         testGame.start();
      }catch(Exception e){}
      TestPhysicsRotation inGameState = new TestPhysicsRotation("ingamestate");
        GameStateManager.getInstance().attachChild(inGameState);
        inGameState.setActive(true);
   }
}



I've taken the concept from Stardust, so I think I missed something?

a few things i noticed in your GameStates update method.


  • dont update the timer manually, StandardGame already passes the correct tpf into the GameStates update method.
  • super.update() already calls rootNode.updateGeometricState().

          either only call super.update() at the end of your update method,

          or call root.updateGeometrcState() and physicsSpace.update() manually and don't call super.update().
  • before accessing playerNode.getWorldTranslation(), make sure to update the worldVectors (player.updateWorldVectors()


I changed my update method to:


public void update(float tpf){
      
      playerNode.updateWorldVectors();
      cam.setLocation(playerNode.getWorldTranslation());
          cam.setAxes(playerNode.getLocalRotation());
      cam.update();
      super.update(tpf);

      if(KeyBindingManager.getKeyBindingManager().isValidCommand("exit")){
         System.exit(0);
      }
      
}



It looks more neat now, but the jitter remains constant :|...

is your camera always following the player at the same range and stuff? maybe its easier to attach a cameraNode to your player, that way you wouldn't need to update the camera manually.



i played a bit with your example, can you try the following update method and see if it still jitters?



    public void update(float tpf){
        getPhysicsSpace().update(tpf);
        rootNode.updateGeometricState(tpf, true);
       
        cam.setLocation(playerNode.getWorldTranslation());
        cam.setAxes(playerNode.getLocalRotation());
        cam.update();
       
        if(KeyBindingManager.getKeyBindingManager().isValidCommand("exit")){
            System.exit(0);
        }
    }



i think thats the correct way, first update physics, then update the player based on inputs (updateGeometrcState) then update camera with the new position.

Yes, the camera is following the player at the same range. The jitter mainly occurs when you are rotating whereas moving forward. It is more obvious when you are close to other objects.

I've tried your update method, but it still jitters.

I've also tried to use a CameraNode:


private void buildCamera(){
   camNode=new CameraNode("camNode",cam);
   playerNode.attachChild(camNode);
}



public void update(float tpf){
        getPhysicsSpace().update(tpf);
        rootNode.updateGeometricState(tpf, true);
       
        if(KeyBindingManager.getKeyBindingManager().isValidCommand("exit")){
            System.exit(0);
        }
}


but the result is still the same.

Just for the case someone has the same problem:

getPhysicsSpace().setAccuracy(1 / 60f);


removed the jitter.