Input problem

Hi all,

This is really a follow-up on my last thread about a camera system that uses spherical coordinates.



I’ve made a little test here:

public class TestSphericalCamera extends SimpleGame {

    private CameraHandler cameraHandler;

   

   

   public static void main(String[] args) {

      TestSphericalCamera app = new TestSphericalCamera();

        app.setDialogBehaviour(SimpleGame.ALWAYS_SHOW_PROPS_DIALOG);

        app.start();

    }

   

   

   protected void simpleInitGame() {

      Box box = new Box(“Box”, new Vector3f(), 10F, 10F, 10F);

      Node boxNode = new Node(“Box Node”);

      boxNode.attachChild(box);

      

      boxNode.setLocalTranslation(new Vector3f(100, 100, 100));

      

      rootNode.attachChild(boxNode);

      

       float theta = 180 * FastMath.DEG_TO_RAD;

       float phi = 70 * FastMath.DEG_TO_RAD;

      

      cameraHandler = new CameraHandler(cam, theta, phi, 50);

      cameraHandler.setTarget(boxNode);

      

      input = new VesselHandler(this, boxNode, cameraHandler,

            properties.getRenderer());

      

        input.setMouseSpeed(0.001F);

   }

   

   

   protected void simpleUpdate() {

      cameraHandler.update();

   }

}


/**
 * @author Per
 *
 * This class handles the camera bahavior. It's locked to a target and is
 * positioned using spherical coordinates. It always faces its target.
 */
public class CameraHandler {
   
   // These are the angles between the target and the camera. They are public
   // so that they can be altered by a InputHandler.
   public float theta, phi;
   
   // The camera that is to be handled.
   private Camera cam;
   
   // This is what the camera should look at.
   private Node target;
   
   // The distance between the camera and the target.
   private float r;
   
   
   /**
    * Creates a new CameraHandler. Note that you would have to set a target
    * after calling this.
    * @param cam The camera to be handled.
    * @param theta The theta angle between the target and the camera.
    * @param phi The phi angle between the target and the camera.
    * @param r The distance between the camera and the target.
    */
   public CameraHandler(Camera cam, float theta, float phi, float r) {
      this.cam = cam;
      this.theta = theta;
      this.phi = phi;
      this.r = r;
   }
   
   
   /**
    * Sets the target for this camera.
    * @param target The node this camera should face.
    */
   public void setTarget(Node target) {
      this.target = target;
   }


   /**
    * Returns the target of this camera.
    * @return The node this camera faces.
    */
   public Node getTarget() {
      return target;
   }


   /**
    * Sets the distance between the camera and the target.
    * @param distance The radius of the coordinates sphere.
    */
   public void setDistance(float distance) {
      this.r = distance;
   }


   /**
    * Gets the distance between the camera and the target.
    * @return The radius of the coordinates sphere.
    */
   public float getDistance() {
      return r;
   }
   
   
   /**
    * Sets the camera position and direction based on spherical coordinates
    * around the target.
    */
   public void update() {      
      Vector3f currCameraPos = cam.getLocation();
      Vector3f targetPos = target.getLocalTranslation();

      //1) Use spherical coordinates to set the new camera position.
      float z = r * FastMath.cos(theta) * FastMath.sin(phi);
      float x = r * FastMath.sin(theta) * FastMath.sin(phi);
      float y = r * FastMath.cos(phi);

      cam.setLocation( targetPos.add(new Vector3f(x,y,z)) );

      //2) Make camera look at the target.
       Vector3f dirVec = new Vector3f();
       dirVec.set(targetPos).subtractLocal(currCameraPos).normalizeLocal();

       Vector3f upVec = new Vector3f(0,1,0);
       Vector3f leftVec = upVec.cross(dirVec).normalizeLocal();
       upVec = dirVec.cross(leftVec).normalizeLocal();

       cam.setAxes(leftVec, upVec, dirVec);
       cam.onFrameChange();
   }
}


/**
 * @author Per
 *
 * This class retrieves input from the passed mouse, and uses it to alter the
 * angles located in the passed CameraHandler.
 */
public class SphericalMouseLook implements MouseInputAction {   
   
   // The speed of the mouse wheel.
   private static float WHEEL_SPEED = 0.1F;
   
   // The mouse we'll retrieve input from.
   private RelativeMouse mouse;
   
   // The camera where the angles are stored.
   private CameraHandler cam;

   // How much the angles should be altered.
   private float mouseSpeed;
   
   
   /**
    *
    * @param mouse The mouse to retrieve input from.
    * @param cam The cam where the angles are stored.
    */
   public SphericalMouseLook(Mouse mouse, CameraHandler cam) {
        this.mouse = (RelativeMouse)mouse;
        this.cam = cam;
   }
   
   
   public void performAction(float f1) {
       MouseInput input = mouse.getMouseInput();
       
       cam.theta -= (input.getXDelta() * mouseSpeed);
       cam.phi   += (input.getYDelta() * mouseSpeed);
       
       
       // Make sure theta is valid.
      if (cam.theta >= 360*FastMath.DEG_TO_RAD) {
         cam.theta = 0;
      }
      else if (cam.theta < 0) {
         cam.theta = 359*FastMath.DEG_TO_RAD;
      }
      
      // Make sure phi is valid.
      if (cam.phi >= 360*FastMath.DEG_TO_RAD) {
         cam.phi = 0;
      }
      else if (cam.phi < 0) {
         cam.phi = 359*FastMath.DEG_TO_RAD;
      }
      
      
      cam.setDistance(cam.getDistance() + (input.getWheelDelta()*WHEEL_SPEED));
      
      System.out.println("THETA: " + cam.theta);
      System.out.println("PHI  : " + cam.phi);      
   }
    
    
   public void setSpeed(float speed) {
      this.mouseSpeed = speed;
        mouse.setSpeed(speed);
   }
    
   
   public void setMouse(Mouse mouse) {
        this.mouse = (RelativeMouse)mouse;
   }
}


/**
 * @author Per
 *
 * This class handles the controlling of a <code>Vessel</code>.
 */
public class VesselHandler extends InputHandler {

   
   public VesselHandler(AbstractGame app, Spatial vessel,
         CameraHandler cameraHandler, String api)
   {
        setKeyBindings(api);
        setUpMouse(cameraHandler);
        setActions(vessel, app);
    }

   
    private void setKeyBindings(String api) {
        KeyBindingManager keyboard = KeyBindingManager.getKeyBindingManager();
        InputSystem.createInputSystem(api);

        keyboard.setKeyInput(InputSystem.getKeyInput());
        keyboard.set("thrusterOn", KeyInput.KEY_W);
        keyboard.set("exit", KeyInput.KEY_ESCAPE);
        setKeyBindingManager(keyboard);
    }

   
    private void setUpMouse(CameraHandler sphericalCamera) {
        RelativeMouse mouse = new RelativeMouse("Mouse Input");
        mouse.setMouseInput(InputSystem.getMouseInput());
       
        setMouse(mouse);

        SphericalMouseLook mouseLook = new SphericalMouseLook(mouse,
              sphericalCamera);
       
        addAction(mouseLook);
    }


    private void setActions(Spatial vessel, AbstractGame app) {
        KeyExitAction exit = new KeyExitAction(app);
        exit.setKey("exit");
        addAction(exit);
    }
}


(Sorry for posting so much code)

I have almost gotten it to work exactly as intended, I have only one problem. When moving the mouse the camera seems to be moving abit choppy. This is especially noticeable when using high mouse speeds.

Why is this? I'm sure it's probably just me doing something fundamentally wrong since I'm new to this input system.

Thanks in advance,

Per

its probably due to the input.update(float time) being too large. E.g. your multiplying it by 10 or something. Always use the actual time, but set the input speed to be higher.



I think that is it anyway, I havent looked at your code, but that has happened to me once and that is how I solved it.



Hope that helps, DP

Commonly, in a third person system, you don’t have the mouse set the view directly. Because the amount of movement tends to vary from frame to frame (speed at which you moved the mouse, lag, etc), you have the mouse control a goal. Then you interpolate each frame from your current point to the goal point.

mojo’s right. You generally use a spring system to draw the camera to the desired position over time rather than just setting it right away whenthe mouse is moved.

Thanks for the great replies.


E.g. your multiplying it by 10 or something.

I'm extending SimpleGame so I havn't done anything like that.

mojo and renanse: I think I know what you guys mean, allthough I'd really like to have that FPS-Game feeling in the mouse movement. This is because in my game you'r going to aim with the mouse.

However, I will look into this further later on today.

You can make those springs pretty tight so that the change happens fast, but smooth. It works pretty well in Dirt.

Hmm… I changed a few lines in SphericalMouseLook.performAction(float time) to look like this:

time *= mouseSpeed;       

cam.theta -= (input.getXDelta() * time);

cam.phi   += (input.getYDelta() * time);



I’m not sure, but I think it made it a little bit better, although the problem was still there. So I thought I would go with your idea to make the SphericalMouseLook only set the goalAngles and let CameraHandler strive to reach them. To do this I changed CameraHandler update to recieve the interpolation and also altered SimpleGame.update(float) to call it. In the beginning of CameraHandler.update(float) I call this:

private void strive(float time) {

   // The amount we will increase/decrease the angles with.

   float inc = time * ROTATION_SPEED;

   

   float thetaDelta = thetaGoal - theta;

   float phiDelta = phiGoal - phi;

   

   // If thetaDelta is positive.

   if (thetaDelta >= 0) {

      if (thetaDelta < inc) theta = thetaGoal;

      else theta += inc;

   }

   

   // Else if thetaDelta is negative.

   else if (thetaDelta < 0) {

      if (thetaDelta > -inc) theta = thetaGoal;

      else theta -= inc;

   }      

   

   // If phiDelta is positive.

   if (phiDelta >= 0) {

      if (phiDelta < inc) phi = phiGoal;

      else phi += inc;

   }

   

   // Else if phiDelta is negative.

   else if (phiDelta < 0) {

      if (phiDelta > -inc) phi = phiGoal;

      else phi -= inc;

   }      

}

This however resulted in some really strange behavior, so I’m all out of ideas now :?



The wierd thing is that when going through the FirstPersonHandler, I can’t see what is done there that makes everything run so smooth. Are we really talking about the same choppy effect? I’m perfectly happy with how the camera moves around without any acceleration or deceleration. It just seems to be abit “jumpy”.



Any ideas? I know it’s getting cind of naggy.

You can make those springs pretty tight so that the change happens fast, but smooth. It works pretty well in Dirt.
I'm not sure of what you mean by this.

you make a "spring" between the goal and the actual position. That way, spring behaviour (which is very nice IMO) can interpolate between the goal and the position. Give it a very small length, and a LARGE K value so it becomes really stiff.



Ive posted some spring behaviour somewhere in this forum, you can search for it if you want (Sorry, i dont have time myself, i have to go out in a sec).



DP

Ah ok thanks for clearing that up. Yes, I know what thread you’r talking about and I’ll look into it tomorrow.

Well, I can’t really see how to combine the spring model shown in your thread and my way of placing the camera with spherical coordinates.



Besides, would it really solve my problem with the “jumpyness”?. Isn’t the spring models purpose to make the camera accelerate and decelerate and such?



Well, I’m gonna try some more with making the camera movement “timebased” in the CameraHandler.update, insead of SphericalMouseLook.performAction.