Understanding GameStates and camera movement

Hi everyone,



I'm trying to set up a simple camera control system. What we're after is the ability to move about the camera with the arrow keys, as well as a simple mouselook.



I'm uncertain, however, about how to go about making the game states communicate, and if my solution is really ideal. it feels a bit like a hack-job, and as can be seen if you try to run the code, my understanding of quaternion rotations still leaves something to be desired.



I've looked at the GameState example with the text, 3d and input game states, which my code is based on, but the problem is that that example lack real input except from key toggles, and the geometry itself is controlled by a GameController, not the player.



I also have problems understanding how to use CameraNode, and how I should make the game states communicate with each other.



Any insight into these matters, or thoughts about a more "ideal" structure I could use would be extremely appreciated.



Wobble.java (main class):

package no.kreatopia.wobble;

import java.util.concurrent.Callable;

import com.jme.util.GameTaskQueueManager;
import com.jmex.game.*;
import com.jmex.game.state.*;

public class Wobble {
   private static StandardGame game;
   
   public static void main(String[] args) throws Exception {
      // Instantiate the game
      game = new StandardGame("A Simple Test");
      // Start StandardGame, it will block until it has initialized successfully, then return
      game.start();

      GameStateManager.getInstance().attachChild(new InputGameState("input", game.getCamera()));
      GameStateManager.getInstance().attachChild(new MeshGameState("mesh"));
        GameStateManager.getInstance().activateAllChildren();

        // add a Statistics GameState which is disabled by default
        GameTaskQueueManager.getManager().update(new Callable<Object> () {
            public Object call() throws Exception {
                GameStateManager.getInstance().attachChild(new StatisticsGameState("statistics", 1f, 0.2f, 0.3f, true));
                return null;
            }
        });

   }
}



MeshGameState.java (Where the 3d objects are drawn):

package no.kreatopia.wobble;

import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.light.PointLight;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.shape.Box;
import com.jme.scene.state.LightState;
import com.jme.scene.state.MaterialState;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
import com.jmex.game.state.BasicGameState;

public class MeshGameState extends BasicGameState {
   
   final Box box;
   
   public MeshGameState(String name) {
      super(name);
      
        box = new Box("MonkeyBox", new Vector3f(0, 0, 0), 5, 5, 5);
       
        // Material: gray
        final MaterialState ms = DisplaySystem.getDisplaySystem().getRenderer().createMaterialState();
        ms.setEmissive(new ColorRGBA(0.5f, 0.4f, 0.5f, 1));
        box.setRenderState(ms);
 
        box.setModelBound(new BoundingBox());
        box.updateModelBound();
        Node boxNode = new Node("MonkeyBoxNode");
        boxNode.attachChild(box);
        getRootNode().attachChild(boxNode);
 
        // Spot on!
        final PointLight light = new PointLight();
        light.setDiffuse(new ColorRGBA(0.75f, 0.75f, 0.75f, 0.75f));
        light.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f));
        light.setLocation(new Vector3f(100, 100, 100));
        light.setEnabled(true);
 
        final LightState lightState = DisplaySystem.getDisplaySystem().getRenderer().createLightState();
        lightState.setEnabled(true);
        lightState.attach(light);
        getRootNode().setRenderState(lightState);
 
        getRootNode().updateRenderState();
   }
   
   public void update(float tpf) {
   }
}



And finally, InputGameState.java:

package no.kreatopia.wobble;

import com.jme.input.KeyInput;
import com.jme.input.controls.GameControlManager;
import com.jme.input.controls.binding.KeyboardBinding;
import com.jme.input.controls.binding.MouseAxisBinding;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.scene.CameraNode;
import com.jmex.game.state.BasicGameState;

public class InputGameState extends BasicGameState {
   
   final GameControlManager cameraControl;
   
   final Camera camera;
   final CameraNode cameraNode;
   
   public InputGameState(String name, Camera camera) {
      super(name);
      
      this.camera = camera;
      this.cameraNode = new CameraNode("Camera node", this.camera);
      this.cameraNode.setLocalTranslation(new Vector3f(0.0f, 0.0f, -40.0f));
      getRootNode().attachChild(this.cameraNode);
      
      // Create GameControlManager, needed to create controls from
        cameraControl = new GameControlManager();

        // Add controls and bind them (statically, should be changed for production code to allow people to customize this)
        cameraControl.addControl("left").addBinding(new KeyboardBinding(KeyInput.KEY_LEFT));
        cameraControl.addControl("right").addBinding(new KeyboardBinding(KeyInput.KEY_RIGHT));
        cameraControl.addControl("in").addBinding(new KeyboardBinding(KeyInput.KEY_UP));
        cameraControl.addControl("out").addBinding(new KeyboardBinding(KeyInput.KEY_DOWN));
       
        // Attach mouse control to camera
        cameraControl.addControl("pitchUp").addBinding(new MouseAxisBinding(MouseAxisBinding.AXIS_Y, true));
        cameraControl.addControl("pitchDown").addBinding(new MouseAxisBinding(MouseAxisBinding.AXIS_Y, false));
        cameraControl.addControl("yawLeft").addBinding(new MouseAxisBinding(MouseAxisBinding.AXIS_X, true));
        cameraControl.addControl("yawRight").addBinding(new MouseAxisBinding(MouseAxisBinding.AXIS_X, false));
       
   }

   public void update(float tpf) {
        super.update(tpf);
       
        Quaternion q = new Quaternion();
        Quaternion newRotation;
        float rotateAngle;
        // Handle mouse pitch and yaw
        if (cameraControl.getControl("pitchUp").getValue() > 0.0f) {
           rotateAngle = cameraControl.getControl("pitchUp").getValue() / 100.0f;
           newRotation = this.cameraNode.getLocalRotation();
           newRotation.addLocal(q.fromAngleNormalAxis(rotateAngle, this.camera.getLeft()));
          this.cameraNode.setLocalRotation(newRotation);
        }
           
        if (cameraControl.getControl("pitchDown").getValue() > 0.0f) {
           rotateAngle = cameraControl.getControl("pitchDown").getValue() / 100.0f;
           newRotation = this.cameraNode.getLocalRotation();
           newRotation.addLocal(q.fromAngleNormalAxis(-rotateAngle, this.camera.getLeft()));
          this.cameraNode.setLocalRotation(newRotation);
        }

        if (cameraControl.getControl("yawLeft").getValue() > 0.0f) {
           rotateAngle = cameraControl.getControl("yawLeft").getValue() / 100.0f;
           newRotation = this.cameraNode.getLocalRotation();
           newRotation.addLocal(q.fromAngleNormalAxis(rotateAngle, this.camera.getUp()));
          this.cameraNode.setLocalRotation(newRotation);
        }

        if (cameraControl.getControl("yawRight").getValue() > 0.0f) {
           rotateAngle = cameraControl.getControl("yawRight").getValue() / 100.0f;
           newRotation = this.cameraNode.getLocalRotation();
           newRotation.addLocal(q.fromAngleNormalAxis(-rotateAngle, this.camera.getUp()));
          this.cameraNode.setLocalRotation(newRotation);
        }

        // Handle keyboard movement
        float moveSpeed = tpf * 15;
        if (cameraControl.getControl("left").getValue() > 0.0f)
           this.cameraNode.setLocalTranslation(this.cameraNode.getLocalTranslation().add(this.camera.getLeft().mult(moveSpeed)));
        if (cameraControl.getControl("right").getValue() > 0.0f)
           this.cameraNode.setLocalTranslation(this.cameraNode.getLocalTranslation().add(this.camera.getLeft().mult(-moveSpeed)));
        if (cameraControl.getControl("in").getValue() > 0.0f)
           this.cameraNode.setLocalTranslation(this.cameraNode.getLocalTranslation().add(this.camera.getDirection().mult(moveSpeed)));
        if (cameraControl.getControl("out").getValue() > 0.0f)
           this.cameraNode.setLocalTranslation(this.cameraNode.getLocalTranslation().add(this.camera.getDirection().mult(-moveSpeed)));

        this.cameraNode.updateRenderState();
       
    }

}