Preventing the camera from turning upside down

hi community,



i haven't found anything in jme to prevent the "turn the world upside down"-behaviour of the mouselook+camera-combination when looking upwards too much. i've dug through the code a bit and found these two guys:

private KeyLookDownAction lookDown;

    private KeyLookUpAction lookUp;


inside these, there i an update method.

/**
     * <code>performAction</code> adjusts the view of the camera to tilt up a
     * given angle. This angle is determined by the camera's speed and the time
     * which has passed.
     *
     * @see com.jme.input.action.KeyInputAction#performAction(InputActionEvent)
     */
    public void performAction(InputActionEvent evt) {
        incr.fromAngleNormalAxis(-speed * evt.getTime(), camera.getLeft());
        incr.mult(camera.getLeft(), camera.getLeft());
        incr.mult(camera.getDirection(), camera.getDirection());
        incr.mult(camera.getUp(), camera.getUp());
        camera.normalize();
        camera.update();
    }



i'm pretty sure a oneliner can fix my problem, but since i don't know how quaternions work, i'm lost and need your help.

i tried this:

    m_mouseLookHandler.setLockAxis(Vector3f.UNIT_Y);



but nothing changed. the camera gets all drunk if i use UNIT_X or UNIT_Z, so the code it at least called.

First person controller has methods to lock axis of rotation. You can use that as an example.

the firstpersoncontroller doesn't seem to prevent anything from happening. it does exactly what my inputcontroller does, which is calling

new MouseLookHandler( cam, turnSpeed )



and letting the mouselookhandler handle the mouselook.
and letting the mouselookhandler handle the mouselook.

And opening MouseLookHandler in the editor reveals the following method


setLockAxis(Vector3f);

a hint, maybe?

the problem is that the mouse movement is converted to an angle and the camera is rotated by that angle:

  incr.fromAngleNormalAxis(-speed * evt.getTime(), camera.getLeft());
        incr.mult(camera.getLeft(), camera.getLeft());
        incr.mult(camera.getDirection(), camera.getDirection());
        incr.mult(camera.getUp(), camera.getUp());


and there is no "if (passesUpVector(currentAngle+delta)) {reduceDelta(delta)}" to stop at direction 0,1,0 when the camera is passing it.

Try to adapt this code to your needs:



...
private final Quaternion orig=new Quaternion();
private final float angles[]=new float[3];
...
orig.set(control.getLocalRotation());
...
// do rotation stuff here
...
control.getLocalRotation().toAngles(angles);
if( (angles[0]>1.2f) || (angles[0]<-1.2f) ) {
// too high, or too low
//    control.setLocalRotation(orig);  <-- bad
control.getLocalRotation().set(orig);  // correct
}



Dont know if this is what you need, but this prevents the rotated stuff to look too much upwards or downwards.

EDIT: Found that i was using it wrong, overwriting the rotation object. Thanks for bringing this up, so i could spot the bug. :D

god…i've been so blind…

An alternative is to make the mouse affect a "pitch" and a "yaw" scalar variable, where you can lock the pitch to +/-1.2. Then, when you actually need the rotation, you create it from the pitch and yaw angles, instead of trying to multiply in new rotations all the time, which may require re-normalization, and may accumulate some roll over time.

i did it. if anyone is interested:

/*
 * Copyright (c) 2003-2006 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 hstar.monkeyextensions;

import com.jme.input.Mouse;
import com.jme.input.action.InputActionEvent;
import com.jme.input.action.KeyRotateLeftAction;
import com.jme.input.action.KeyRotateRightAction;
import com.jme.input.action.MouseInputAction;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;

/**
 * <code>MouseLook</code> defines a mouse action that detects mouse movement
 * and converts it into camera rotations and camera tilts.
 *
 * @author Mark Powell
 * @version $Id: MouseLook.java,v 1.18 2007/08/02 21:38:55 nca Exp $
 */
public class LockedMouseLook extends MouseInputAction {

  private KeyRotateLeftAction m_rotateLeft;
  private KeyRotateRightAction m_rotateRight;
  private KeyLookUpAndDownAction m_lookUpAndDown;
  private InputActionEvent m_event;

  public LockedMouseLook(final Mouse p_mouse, final Camera p_camera, final float p_speed) {
    super();
    this.mouse = p_mouse;
    this.speed = p_speed;

    m_lookUpAndDown = new KeyLookUpAndDownAction(p_camera, p_speed);
    m_rotateLeft = new KeyRotateLeftAction(p_camera, p_speed);
    m_rotateRight = new KeyRotateRightAction(p_camera, p_speed);
    m_rotateLeft.setLockAxis(Vector3f.UNIT_Y);
    m_rotateRight.setLockAxis(Vector3f.UNIT_Y);
    m_event = new InputActionEvent();
  }

  public void setSpeed(final float speed) {
    super.setSpeed(speed);
    m_lookUpAndDown.setSpeed(speed);
    m_rotateRight.setSpeed(speed);
    m_rotateLeft.setSpeed(speed);
  }

  public void performAction(final InputActionEvent evt) {
    final float time = 0.01f * speed;

    if (mouse.getLocalTranslation().x > 0.0F) {
      m_event.setTime(time * mouse.getLocalTranslation().x);
      m_rotateRight.performAction(m_event);
    } else if (mouse.getLocalTranslation().x < 0.0F) {
      m_event.setTime(time * mouse.getLocalTranslation().x * -1.0F);
      m_rotateLeft.performAction(m_event);
    }
    m_event.setTime(-1.0F * time * mouse.getLocalTranslation().y);
    m_lookUpAndDown.performAction(m_event);

  }


and

/* Copyright H-Star Development 2007 */
package hstar.monkeyextensions;

import com.jme.input.action.InputActionEvent;
import com.jme.input.action.KeyInputAction;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import org.jetbrains.annotations.NotNull;

/**
 * Developed with pleasure :)<br>
 *
 * @author HamsterofDeath
 *         Created 07.12.2007 @ 21:14:37
 */
public class KeyLookUpAndDownAction extends KeyInputAction {
  //maintains the rotation matrix
  private static final Quaternion spin = new Quaternion();
  //the camera to manipulate
  @NotNull private final Camera m_camera;
  private float m_speed;
  private float m_angle = 0.0F;
  @NotNull private final Vector3f m_tmp = new Vector3f();

  public KeyLookUpAndDownAction(final Camera p_camera, final float p_speed) {
    super();
    this.m_camera = p_camera;
    this.m_speed = p_speed;
  }

  public void setSpeed(final float p_speed) {
    m_speed = p_speed;
  }

  public void performAction(final InputActionEvent p_event) {
    m_angle += m_speed * p_event.getTime();
    m_angle = Math.max(m_angle, -FastMath.HALF_PI + 0.01F);
    m_angle = Math.min(m_angle, FastMath.HALF_PI - 0.01F);
    m_tmp.set(m_camera.getDirection()).setY(0.0F);
    m_tmp.normalizeLocal();
    m_tmp.set(m_tmp.z, 0.0F, -m_tmp.x);
    spin.fromAngleNormalAxis(m_angle, m_tmp);
    m_tmp.set(m_camera.getDirection()).setY(0.0F);
    m_tmp.normalizeLocal();
    spin.multLocal(m_tmp);
    m_camera.lookAt(m_tmp.addLocal(m_camera.getLocation()), Vector3f.UNIT_Y);
    m_camera.normalize();
    m_camera.update();
  }

}

1 Like

Hi,

I am seeking a solution to prevent this flip of the camera, but this code is outdated ? Anyone can help me to fixe it ?

I have already override the FlyByCam to lock movement on Y axis (can’t use the physics engine for now in my project, so no gravity…), is it the better solution? or one other exist?

Just don’t try to use rotations to handle where your camera looks, use direction vectors:

https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:math_for_dummies

If you make your rotations with lookAt its easy to specify the up-vector.

I have looked this document, i understand how work vector and approximatly quaternion but i dont understand how limit the rotation to up/down cause it’s on axe x and z… and where this lock must be implement ? in the rotateCamera method of flybycamera class?

you just use quaternion.lookAt(direction, Vector3f.UNIT_Y); then its always y-up… Then if direction has no x component (direction.x=0) it won’t rotate in that direction.

I see about the lookAt but, i don’t know how use this to lock rotation to a certain degree… it’s certainly easy, but i tryed different thing and nothing work… :frowning:



// method in Flybycam class

[java]protected 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();

Quaternion quat = new Quaternion();

quat.lookAt(dir, up);



mat.mult(up, up);

mat.mult(left, left);

mat.mult(dir, dir);



Quaternion q = new Quaternion();

q.fromAxes(left, up, dir);

q.normalize();



cam.setAxes(q);

}[/java]

I had the same problem. The solution is actually very simple :



You can use rotateCamera(float value, Vector3f axis) as this.



But instead of

[java]rotateCamera(value, camera.getUp() );[/java]

.

use

[java]rotateCamera(value, Vector3f.UNIT_Y );[/java]

.

Indeed, camera.getUp is “tilted backward” when you look up. You don’t want to rotate around a tilted axis.

thanks, i replaced (always in the class “FlyByCamera”) the argument of the method call rotateCamera by the vector3f.UNIT_Y like you said, but nothing change…

In which classe do you use this? :s

I finally found a solution, thanks all:)

[java]protected 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();

Quaternion quat = new Quaternion();

quat.lookAt(dir, up);





mat.mult(up, up);

mat.mult(left, left);

mat.mult(dir, dir);



Quaternion q = new Quaternion();

q.fromAxes(left, up, dir);

q.normalize();



float angleDir = dir.angleBetween(Vector3f.UNIT_Y);

if((angleDir>0.05)&&(angleDir<3))

{

cam.setAxes(q);

}

}[/java]

Hi!

The above code won’t prevent the cam to turn upside down.

The clue here is to add another condition:

As a matter of fact, we have the “up” vector. So if up.y turns negative, the camera is positioned upside down (I also made degrees out of the radians):



[java]float angleY = dir.angleBetween(Vector3f.UNIT_Y);

float angleYDegree = angleY * 180 / FastMath.PI ;



// log.debug("X: " + up.x + " Y: " + up.y + " Z: " + up.z);



if(angleYDegree>=90 && angleYDegree<=180 && up.y>=0)

this.app3D.getCamera().setAxes(q);[/java]



Hope this one helps :wink: