Limiting camera up/down?

Is there a way to limit the camera from looking > 90 degress up or down? It’s kinda odd when you can do backflips on the ground. ._. anybody got this solved? I had a thing set up where it said something like:

if(cam.getDirection().getY() >= 0.99f) {

then I’d set the camera’s Y to 0.99f.



Problem is it’s really jarring, and it often wouldn’t detect the camera’s Y being >= 0.99 until it had already started a backflip, in which case you just flip around and face the opposite direction. Any fix?



PS. I’ll feel really dumb if there’s a method in the Camera class… I’ll go look now… xD

Easiest is to modify your own flycam, try this



[java]

private void rotateCamera(float value, Vector3f axis){

if (dragToRotate){

if (canRotate){

// value = -value;

}else{

return;

}

}



Matrix3f mat = new Matrix3f();

mat.fromAngleNormalAxis(rotationSpeed * value, axis);



Vector3f up = cam.getUp();

Vector3f left = cam.getLeft();

Vector3f dir = cam.getDirection();



mat.mult(up, up);

mat.mult(left, left);

mat.mult(dir, dir);



// these 3 lines

if (up.getY() < 0) {

return;

}



Quaternion q = new Quaternion();

q.fromAxes(left, up, dir);

q.normalize();



cam.setAxes(q);

}[/java]



Note: this will only work for when Y up is (0, 1, 0)

I see. Someone should get a good working method to stop it from doing backflips and frontflips.

Hmm, I had the same problem, it is best to modify the FlyByCamera what @wezrule said, but that 3 lines didnt work for me, it stops when reached limit but onces reached you cant get back…

I’ve tried this and it works for me, this is in the FlyByCamera class:

I’ not sure if this is a good way…
see below for the whole class I’m not sure if it is allowed to share the whole class (copyright JME3)

[java] protected float maxDirY = 0.999f;
protected float minDirY = -0.999f;
protected boolean enableLock = true;[/java]
some variables for later.
And some getters and setters for changing it in your main class…
[java] /**
*Set the maxium look direction for the Y axis.
*Setting this to more than 1 or less than minDirY does not work, if want to
* disable the lock see: #.
* @param dirY the maxium direction that a FlyByCamera can look at the Y.
* A FlyByCamera cannot look higher than the maxDirY
*/
public void setmaxDirY(float dirY){
this.maxDirY = dirY;
}

/**
 *Set the minium look direction for the Y axis.
 *Setting this to more than -1 or more than minDirY to disable look lock.
 * @param dirY the minium direction that a FlyByCamera can look at the Y.
 * A FlyByCamera cannot look lower than the minDirY.
 */
public void setMinDirY(float dirY){
this.minDirY = dirY;
}

/**
 * @return the maxium FlyByCamera look, a FlyByCamera cannot look higher than this.
 *
 * @see FlyByCamera#setmaxDirY(float)  
 */
public float getMaxDirY(){
return maxDirY;
}

 /**
 * @return the minium FlyByCamera look, a FlyByCamera cannot look lower than this.
 *
 * @see FlyByCamera#setMinDirY(float)  
 */
public float getMinDirY(){
return minDirY;
}

/**
 *Set wether the lock to look down/up is enabled.
 */
public void enableLock(boolean enable){
this.enableLock = enable;
}

/*
 *@return true if look lock is enabled.
 *@see  FlyByCamera#enableLock
 * @see  FlyByCamera#setMinDirY(float) 
 * @see FlyByCamera#setmaxDirY(float) 
 */
public boolean isEnableLock(){
return enableLock;
}[/java] 

Here I"ve added two booleans
[java] protected void rotateCamera(float value, Vector3f axis, boolean isMovingY, boolean isNegative){[/java] the isMovingY and isNegative, the first to check if you are moving the y or x. The second to see if you are moving up or down.

these 3 vectors where there already but to show where the code is:
[java]
Vector3f up = cam.getUp();
Vector3f left = cam.getLeft();
Vector3f dir = cam.getDirection();

    if(isMovingY){
        if(enableLock){
           if(maxDirY&lt;1 &amp;&amp; maxDirY &gt; minDirY &amp;&amp; minDirY &gt; -1){
              if(!isNegative){
                     if(dir.getY() &gt;= maxDirY){
                     return;
                   }
             }else{
                    if(dir.getY() &lt;= minDirY){
                    return;
                  }
             }
          }
        }   
    }[/java] 

It checks here to stop further actions from moving the camera more up or down if the limit is reached

and of course because if changed the method above, I need to add some more variables here in the void onAnalog
[java] if (name.equals(“FLYCAM_Left”)){
rotateCamera(value, initialUpVec, false, true);
}else if (name.equals(“FLYCAM_Right”) ){
rotateCamera(-value, initialUpVec, false, false);
}else if (name.equals(“FLYCAM_Up”)){
rotateCamera(-value * (invertY ? -1 : 1), cam.getLeft() , true, false);
}else if (name.equals(“FLYCAM_Down”)){
rotateCamera(value * (invertY ? -1 : 1), cam.getLeft(), true, true);[/java]
that true/false was added

And here the whole class:
[java]/*

  • Copyright © 2009-2012 jMonkeyEngine
  • All rights reserved.
  • Redistribution and use in source and binary forms, with or without
  • modification, are permitted provided that the following conditions are
  • met:
    • Redistributions of source code must retain the above copyright
  • notice, this list of conditions and the following disclaimer.
    • Redistributions in binary form must reproduce the above copyright
  • notice, this list of conditions and the following disclaimer in the
  • documentation and/or other materials provided with the distribution.
    • Neither the name of ‘jMonkeyEngine’ nor the names of its contributors
  • may be used to endorse or promote products derived from this software
  • without specific prior written permission.
  • THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  • “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  • TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  • PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  • CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  • EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  • PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  • PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  • LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  • NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  • SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    */
    package com.jme3.input;

import com.jme3.collision.MotionAllowedListener;
import com.jme3.input.controls.*;
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;

/**

  • A first person view camera controller.

  • After creation, you must register the camera controller with the

  • dispatcher using #registerWithDispatcher().

  • Controls:

    • Move the mouse to rotate the camera
    • Mouse wheel for zooming in or out
    • WASD keys for moving forward/backward and strafing
    • QZ keys raise or lower the camera
      */
      public class FlyByCamera implements AnalogListener, ActionListener {

    private static String[] mappings = new String[]{
    “FLYCAM_Left”,
    “FLYCAM_Right”,
    “FLYCAM_Up”,
    “FLYCAM_Down”,

        "FLYCAM_StrafeLeft",
        "FLYCAM_StrafeRight",
        "FLYCAM_Forward",
        "FLYCAM_Backward",
    
        "FLYCAM_ZoomIn",
        "FLYCAM_ZoomOut",
        "FLYCAM_RotateDrag",
    
        "FLYCAM_Rise",
        "FLYCAM_Lower",
        
        "FLYCAM_InvertY"
    };
    

    protected float maxDirY = 0.999f;
    protected float minDirY = -0.999f;
    protected boolean enableLock = true;
    protected Camera cam;
    protected Vector3f initialUpVec;
    protected float rotationSpeed = 1f;
    protected float moveSpeed = 3f;
    protected float zoomSpeed = 1f;
    protected MotionAllowedListener motionAllowed = null;
    protected boolean enabled = true;
    protected boolean dragToRotate = false;
    protected boolean canRotate = false;
    protected boolean invertY = false;
    protected InputManager inputManager;

    /**

    • Creates a new FlyByCamera to control the given Camera object.
    • @param cam
      */
      public FlyByCamera(Camera cam){
      this.cam = cam;
      initialUpVec = cam.getUp().clone();
      }

    /**

    • Sets the up vector that should be used for the camera.
    • @param upVec
      */
      public void setUpVector(Vector3f upVec) {
      initialUpVec.set(upVec);
      }

    public void setMotionAllowedListener(MotionAllowedListener listener){
    this.motionAllowed = listener;
    }

    /**

    • Sets the move speed. The speed is given in world units per second.
    • @param moveSpeed
      */
      public void setMoveSpeed(float moveSpeed){
      this.moveSpeed = moveSpeed;
      }

    /**

    • Gets the move speed. The speed is given in world units per second.
    • @return moveSpeed
      */
      public float getMoveSpeed(){
      return moveSpeed;
      }

    /**

    • Sets the rotation speed.
    • @param rotationSpeed
      */
      public void setRotationSpeed(float rotationSpeed){
      this.rotationSpeed = rotationSpeed;
      }

    /**

    • Gets the move speed. The speed is given in world units per second.
    • @return rotationSpeed
      */
      public float getRotationSpeed(){
      return rotationSpeed;
      }

    /**

    • Sets the zoom speed.
    • @param zoomSpeed
      */
      public void setZoomSpeed(float zoomSpeed) {
      this.zoomSpeed = zoomSpeed;
      }

    /**

    • Gets the zoom speed. The speed is a multiplier to increase/decrease
    • the zoom rate.
    • @return zoomSpeed
      */
      public float getZoomSpeed() {
      return zoomSpeed;
      }

    /**

    • @param enable If false, the camera will ignore input.
      */
      public void setEnabled(boolean enable){
      if (enabled && !enable){
      if (inputManager!= null && (!dragToRotate || (dragToRotate && canRotate))){
      inputManager.setCursorVisible(true);
      }
      }
      enabled = enable;
      }

    /**

    • @return If enabled
    • @see FlyByCamera#setEnabled(boolean)
      */
      public boolean isEnabled(){
      return enabled;
      }

    /**

    • @return If drag to rotate feature is enabled.
    • @see FlyByCamera#setDragToRotate(boolean)
      */
      public boolean isDragToRotate() {
      return dragToRotate;
      }

    /**

    • Set if drag to rotate mode is enabled.
    • When true, the user must hold the mouse button
    • and drag over the screen to rotate the camera, and the cursor is
    • visible until dragged. Otherwise, the cursor is invisible at all times
    • and holding the mouse button is not needed to rotate the camera.
    • This feature is disabled by default.
    • @param dragToRotate True if drag to rotate mode is enabled.
      */
      public void setDragToRotate(boolean dragToRotate) {
      this.dragToRotate = dragToRotate;
      if (inputManager != null) {
      inputManager.setCursorVisible(dragToRotate);
      }
      }

    /**
    *Set the maxium look direction for the Y axis.
    *Setting this to more than 1 or less than minDirY does not work, if want to

    • disable the lock see: #.
    • @param dirY the maxium direction that a FlyByCamera can look at the Y.
    • A FlyByCamera cannot look higher than the maxDirY
      */
      public void setmaxDirY(float dirY){
      this.maxDirY = dirY;
      }

    /**
    *Set the minium look direction for the Y axis.
    *Setting this to more than -1 or more than minDirY to disable look lock.

    • @param dirY the minium direction that a FlyByCamera can look at the Y.
    • A FlyByCamera cannot look lower than the minDirY.
      */
      public void setMinDirY(float dirY){
      this.minDirY = dirY;
      }

    /**

    • @return the maxium FlyByCamera look, a FlyByCamera cannot look higher than this.
    • @see FlyByCamera#setmaxDirY(float)
      */
      public float getMaxDirY(){
      return maxDirY;
      }

    /**

    • @return the minium FlyByCamera look, a FlyByCamera cannot look lower than this.
    • @see FlyByCamera#setMinDirY(float)
      */
      public float getMinDirY(){
      return minDirY;
      }

    /**
    *Set wether the lock to look down/up is enabled.
    */
    public void enableLock(boolean enable){
    this.enableLock = enable;
    }

    /*
    *@return true if look lock is enabled.
    *@see FlyByCamera#enableLock

    • @see FlyByCamera#setMinDirY(float)
    • @see FlyByCamera#setmaxDirY(float)
      */
      public boolean isEnableLock(){
      return enableLock;
      }

    /**

    • Registers the FlyByCamera to receive input events from the provided

    • Dispatcher.

    • @param inputManager
      */
      public void registerWithInput(InputManager inputManager){
      this.inputManager = inputManager;

      // both mouse and button - rotation of cam
      inputManager.addMapping(“FLYCAM_Left”, new MouseAxisTrigger(MouseInput.AXIS_X, true),
      new KeyTrigger(KeyInput.KEY_LEFT));

      inputManager.addMapping(“FLYCAM_Right”, new MouseAxisTrigger(MouseInput.AXIS_X, false),
      new KeyTrigger(KeyInput.KEY_RIGHT));

      inputManager.addMapping(“FLYCAM_Up”, new MouseAxisTrigger(MouseInput.AXIS_Y, false),
      new KeyTrigger(KeyInput.KEY_UP));

      inputManager.addMapping(“FLYCAM_Down”, new MouseAxisTrigger(MouseInput.AXIS_Y, true),
      new KeyTrigger(KeyInput.KEY_DOWN));

      // mouse only - zoom in/out with wheel, and rotate drag
      inputManager.addMapping(“FLYCAM_ZoomIn”, new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false));
      inputManager.addMapping(“FLYCAM_ZoomOut”, new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true));
      inputManager.addMapping(“FLYCAM_RotateDrag”, new MouseButtonTrigger(MouseInput.BUTTON_LEFT));

      // keyboard only WASD for movement and WZ for rise/lower height
      inputManager.addMapping(“FLYCAM_StrafeLeft”, new KeyTrigger(KeyInput.KEY_A));
      inputManager.addMapping(“FLYCAM_StrafeRight”, new KeyTrigger(KeyInput.KEY_D));
      inputManager.addMapping(“FLYCAM_Forward”, new KeyTrigger(KeyInput.KEY_W));
      inputManager.addMapping(“FLYCAM_Backward”, new KeyTrigger(KeyInput.KEY_S));
      inputManager.addMapping(“FLYCAM_Rise”, new KeyTrigger(KeyInput.KEY_Q));
      inputManager.addMapping(“FLYCAM_Lower”, new KeyTrigger(KeyInput.KEY_Z));

      inputManager.addListener(this, mappings);
      inputManager.setCursorVisible(dragToRotate || !isEnabled());

      Joystick[] joysticks = inputManager.getJoysticks();
      if (joysticks != null && joysticks.length > 0){
      for (Joystick j : joysticks) {
      mapJoystick(j);
      }
      }
      }

    protected void mapJoystick( Joystick joystick ) {

    // Map it differently if there are Z axis
    if( joystick.getAxis( JoystickAxis.Z_ROTATION ) != null &amp;&amp; joystick.getAxis( JoystickAxis.Z_AXIS ) != null ) {
    
        // Make the left stick move
        joystick.getXAxis().assignAxis( "FLYCAM_StrafeRight", "FLYCAM_StrafeLeft" );
        joystick.getYAxis().assignAxis( "FLYCAM_Backward", "FLYCAM_Forward" );
        
        // And the right stick control the camera                       
        joystick.getAxis( JoystickAxis.Z_ROTATION ).assignAxis( "FLYCAM_Down", "FLYCAM_Up" );
        joystick.getAxis( JoystickAxis.Z_AXIS ).assignAxis(  "FLYCAM_Right", "FLYCAM_Left" );
    
        // And let the dpad be up and down           
        joystick.getPovYAxis().assignAxis("FLYCAM_Rise", "FLYCAM_Lower");
    
        if( joystick.getButton( "Button 8" ) != null ) { 
            // Let the stanard select button be the y invert toggle
            joystick.getButton( "Button 8" ).assignButton( "FLYCAM_InvertY" );
        }                
        
    } else {             
        joystick.getPovXAxis().assignAxis("FLYCAM_StrafeRight", "FLYCAM_StrafeLeft");
        joystick.getPovYAxis().assignAxis("FLYCAM_Forward", "FLYCAM_Backward");
        joystick.getXAxis().assignAxis("FLYCAM_Right", "FLYCAM_Left");
        joystick.getYAxis().assignAxis("FLYCAM_Down", "FLYCAM_Up");
    }                
    

    }

    /**

    • Registers the FlyByCamera to receive input events from the provided

    • Dispatcher.

    • @param inputManager
      */
      public void unregisterInput(){

      if (inputManager == null) {
      return;
      }

      for (String s : mappings) {
      if (inputManager.hasMapping(s)) {
      inputManager.deleteMapping( s );
      }
      }

      inputManager.removeListener(this);
      inputManager.setCursorVisible(!dragToRotate);

      Joystick[] joysticks = inputManager.getJoysticks();
      if (joysticks != null && joysticks.length > 0){
      Joystick joystick = joysticks[0];

       // No way to unassing axis
      

      }
      }

    protected void rotateCamera(float value, Vector3f axis, boolean isMovingY, boolean isNegative){
    if (dragToRotate){
    if (canRotate){
    // value = -value;
    }else{
    return;
    }
    }

    Matrix3f mat = new Matrix3f();
    mat.fromAngleNormalAxis(rotationSpeed * value, axis);
    
    Vector3f up = cam.getUp();
    Vector3f left = cam.getLeft();
    Vector3f dir = cam.getDirection();
    
    if(isMovingY){
        if(enableLock){
           if(maxDirY&lt;1 &amp;&amp; maxDirY &gt; minDirY &amp;&amp; minDirY &gt; -1){
              if(!isNegative){
                     if(dir.getY() &gt;= maxDirY){
                     return;
                   }
             }else{
                    if(dir.getY() &lt;= minDirY){
                    return;
                  }
             }
          }
        }   
    }
    
    mat.mult(up, up);
    mat.mult(left, left);
    mat.mult(dir, dir);
    
    Quaternion q = new Quaternion();
    q.fromAxes(left, up, dir);
    q.normalizeLocal();
    
    cam.setAxes(q);
    

    }

    protected void zoomCamera(float value){
    // derive fovY value
    float h = cam.getFrustumTop();
    float w = cam.getFrustumRight();
    float aspect = w / h;

    float near = cam.getFrustumNear();
    
    float fovY = FastMath.atan(h / near)
              / (FastMath.DEG_TO_RAD * .5f);
    float newFovY = fovY + value * 0.1f * zoomSpeed;
    if (newFovY &gt; 0f) {
        // Don't let the FOV go zero or negative.
        fovY = newFovY;
    }
    
    h = FastMath.tan( fovY * FastMath.DEG_TO_RAD * .5f) * near;
    w = h * aspect;
    
    cam.setFrustumTop(h);
    cam.setFrustumBottom(-h);
    cam.setFrustumLeft(-w);
    cam.setFrustumRight(w);
    

    }

    protected void riseCamera(float value){
    Vector3f vel = new Vector3f(0, value * moveSpeed, 0);
    Vector3f pos = cam.getLocation().clone();

    if (motionAllowed != null)
        motionAllowed.checkMotionAllowed(pos, vel);
    else
        pos.addLocal(vel);
    
    cam.setLocation(pos);
    

    }

    protected void moveCamera(float value, boolean sideways){
    Vector3f vel = new Vector3f();
    Vector3f pos = cam.getLocation().clone();

    if (sideways){
        cam.getLeft(vel);
    }else{
        cam.getDirection(vel);
    }
    vel.multLocal(value * moveSpeed);
    
    if (motionAllowed != null)
        motionAllowed.checkMotionAllowed(pos, vel);
    else
        pos.addLocal(vel);
    
    cam.setLocation(pos);
    

    }

    public void onAnalog(String name, float value, float tpf) {
    if (!enabled)
    return;

    if (name.equals("FLYCAM_Left")){
        rotateCamera(value, initialUpVec, false, true);
    }else if (name.equals("FLYCAM_Right") ){
        rotateCamera(-value, initialUpVec, false, false);
    }else if (name.equals("FLYCAM_Up")){
        rotateCamera(-value * (invertY ? -1 : 1), cam.getLeft() , true, false);
    }else if (name.equals("FLYCAM_Down")){
        rotateCamera(value * (invertY ? -1 : 1), cam.getLeft(), true, true);
    }else if (name.equals("FLYCAM_Forward")){
        moveCamera(value, false);
    }else if (name.equals("FLYCAM_Backward")){
        moveCamera(-value, false);
    }else if (name.equals("FLYCAM_StrafeLeft")){
        moveCamera(value, true);
    }else if (name.equals("FLYCAM_StrafeRight")){
        moveCamera(-value, true);
    }else if (name.equals("FLYCAM_Rise")){
        riseCamera(value);
    }else if (name.equals("FLYCAM_Lower")){
        riseCamera(-value);
    }else if (name.equals("FLYCAM_ZoomIn")){
        zoomCamera(value);
    }else if (name.equals("FLYCAM_ZoomOut")){
        zoomCamera(-value);
    }
    

    }

    public void onAction(String name, boolean value, float tpf) {
    if (!enabled)
    return;

    if (name.equals("FLYCAM_RotateDrag") &amp;&amp; dragToRotate){
        canRotate = value;
        inputManager.setCursorVisible(!value);
    } else if (name.equals("FLYCAM_InvertY")) {
        // Toggle on the up.
        if( !value ) {  
            invertY = !invertY;
        }
    }        
    

    }

}
[/java]
if I have some time I’ m going to add this for the x/z rotating,

Note: before you go through too much trouble rebuilding parts of FlyByCamera, consider that most real games don’t move a camera. They move the player object.

In other words, we aren’t dragging the player around by the eye ball… we are moving the player and his eye moves with him. This is a major reason why FlyByCamera is limited. It’s really only suitable for tests and demos as a real game will need something customized to how they are managing their game objects.

After all, the lack of rotational locking is only one of a dozen issues you will run into with a completely unconstrained camera control implementation.

Yeah I do that too, first move the player than move the camera, but I mean rotating, where you look, I dont change moving or something , just so you can look more up or down. I dont think I understand but if you mean the following I can do something with that.
Or do you mean I should rotate a player (e.g. model blender) and when rotating that player, rotate the camera? In that case I need to do almost the same but than with a “player”…
So maybe I should make my own player looking?
Anyway thanks for responding.

Almost forgot:
Thanks JME and everybody who helped, jme is a great engine (also the first I’m using) it is really cool to create a 3D game!