Building a free flying 3D Quaternion camera

Hi Everyone,

Ok… so i’ve been trying to build a free flying camera.
I’ve used quaternions to handle rotations with success.

Only one part of mechanics seems to be eluding me. movement.

how do i find out after the rotations have been applied what my new relative forward vector is, so that i can translate in the right direction…

Im sure there is likely a simple answer that im just missing.

thanks for any help

J.

simply apply the rotation quaternions to forward and up vector too.

you know what… im probably making this harder in my head than it needs to be.

Here is a snippet of my code




	private Quaternion camRotations = new Quaternion();
	private Matrix4f   camMatrix = new Matrix4f();
	
	private Vector3f up = 		new Vector3f(0,1,0);
	private Vector3f forward =	new Vector3f(0,0,-1);
	private Vector3f right = 	new Vector3f(1,0,0);
	




	private void updatePosition(float deltaPitch, float deltaYaw, float deltaRoll, float deltaFwd, float deltaRight){
		Quaternion qPitch = new Quaternion();
		qPitch.setFromAxisAngle(new Vector4f(this.right.x, this.right.y, this.right.z, deltaPitch));
		camRotations.mul(camRotations, qPitch, camRotations);
		
		Quaternion qYaw = new Quaternion();
		qYaw.setFromAxisAngle(new Vector4f(this.up.x, this.up.y, this.up.z, deltaYaw));
		camRotations.mul(camRotations, qYaw, camRotations);
		
		Quaternion qRoll = new Quaternion();
		qRoll.setFromAxisAngle(new Vector4f(this.forward.x, this.forward.y, this.forward.z, deltaRoll));
		camRotations.mul(camRotations, qRoll, camRotations);
		
		
	}
    



whats throwing me off… is if i roll then pitch then yaw… then forward would be affected by all these rotations … so When and from which quat would i be adding… sigh… need more coffee.

thanks!
J.

yes, more coffee.

it depends on when you apply which quat to which vector/matrix :persecutioncomplex: … and if the update values are deltas or not.

it’s not clear from the code yet.

you could do something like this : idea is to create vectors, apply quaternions and then extract matrix. this might be not the most correct way to do it but is convenient and fast.

camera : vec3d pos, up, side (right in your code), dir.
side/right : always calculated with cross(up,dir)

so if i’d rotate the camera along Y axis, to me thats the one pointing up, looking left and right :

void rotate_Y ( float angle ) // angle is delta, not the absolute value
{
  quat.setXYZ( up.x * angle , up.y * angle , up.z * angle );  // quat made of up
  quat.setW(1.0f);
  normalize(quat);

  quat.applyTo( up );
  quat.applyTo( dir );

  side = cross(up, dir);
}

… to rotate along the X axis (nodding up and down) you would use a quat made of side/right, do rotate Z (head bobbing) quad would be made of dir

the apply method could be something like (quat and vector are expected to be normalised) :

public vec3 apply ( vec3 v )
{
    float tx = q.y*v.z - v.y*q.z + v.x*q.w;
    float ty = q.z*v.x - v.z*q.x + v.y*q.w;
    float tz = q.x*v.y - v.x*q.y + v.z*q.w;

    v.x += 2.0f*( q.y*tz - ty*q.z );
    v.y += 2.0f*( q.z*tx - tz*q.x );
    v.z += 2.0f*( q.x*ty - tx*q.y );

    return v;
}

// or another version :

public vec3 apply ( vec3 v )
{
    float tx = q.y*v.z - v.y*q.z;
    float ty = q.z*v.x - v.z*q.x;
    float tz = q.x*v.y - v.x*q.y;

    tx += v.x*q.w;
    ty += v.y*q.w;
    tz += v.z*q.w;

    float cx = q.y*tz - ty*q.z; 
    float cy = q.z*tx - tz*q.x;
    float cz = q.x*ty - tx*q.y;

    v.x += 2.0f*cx;
    v.y += 2.0f*cy;
    v.z += 2.0f*cz;

    return v;
}

// .. or this (works good with glsl)
vec3 apply_quat( in vec4 quat, inout vec3 v )
{
  v += 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v);
  return v;
}


whatever works for you …

now update the projection/modelview with your fav. math lib. like …

projection = setPerspective( fov_y, display_ratio, znear, zfar );
modelview = setLookDir( pos, dir, up );

GLU methods can help here too.

Hi Basil

thanks for the code snippets.

Im going to ask one more dum question. :slight_smile:

so no i can determine the forward vector. I want to ensure my translate of the position is relative to the new angles.

(ie… i want to fly forward in the forward vector direction)

Got any samples / tips for that?

sure thing.

moving forward would be simply

pos += dir * distance

panning to left and right

pos += side * distance

panning up and down

pos += up * distance

o/

edit

about the rotation around the up-vector (looking left and right) … if you do a space-flying game then the upper example works, but if you aim for a orbital-type-camera or first-person-type then you need to create the quaternion for Y rotation not from up but from a fixed vector. something like

vec3 fixed_up = vec3(0.0,1.0,0.0)

then when you want to “tilt” the camera you rotate just this new vector around dir.

Funny all these related questions popping. Forward from matrix, inverting SE(2) and this. Getting forward, up, right is equivalent to (part) of converting to a matrix, which in turn is just rotating an axial vector. Dead easy since two values are zero and one is one (or minus one if you’re into that kind of thing). The rotation example above is wrong…miss the trig. And if you’ve computed two, then the third is almost free…doing the cross product to produce it is more expensive.

which one ?

The rotate about Y by angle.

well im still struggling on this…

so here is the full class…

What works

  • Rotate the camera pitch (w/s) yaw (a/s) roll (q/e)

what doesnt work:

  • Movement: attempting to following the examples provide resulted in a axis getting locked (stopped moving with the rotations) or the forward direction ignoring an axis rotation

Since quaternions are still relatively new to me i’m feeling like im just missing that "ahah! " moment…


package com.windpoweredgames.wind.camera;

import org.lwjgl.input.Keyboard;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Quaternion;
import org.lwjgl.util.vector.Vector3f;
import org.lwjgl.util.vector.Vector4f;

import com.windpoweredgames.wind.GameEngine;
import com.windpoweredgames.wind.input.InputManager;
import com.windpoweredgames.wind.input.InputProcessor;
import com.windpoweredgames.wind.util.MatrixUtil;
import com.windpoweredgames.wind.util.VectorUtil;


public class DefaultCamera extends AbstractCamera implements InputProcessor{
	
	private float speed = 0f; // 1 unit per sec
	private float distRate = 1f/1000f;
	private float rotRate = 1f/1000f;
	
	private Quaternion camRotations = new Quaternion();
	
	private Vector3f pos = 		new Vector3f(0,0,0);
	private Vector3f up = 		new Vector3f(0,1,0);
	private Vector3f forward =	new Vector3f(0,0,-1);
	private Vector3f right = 	new Vector3f(1,0,0);
	
	
	
	public void setActive(boolean active) {
		super.setActive(active);
		if(active){
			InputManager.getInstance().add(this);
		}else{
			InputManager.getInstance().remove(this);
		}
	}
	
	public void setSpeed(float speed) {
			this.speed = speed;
	}
	
	public float getSpeed() {
		return speed;
	}
	
	
	
	
	
	private float getTimeDependantDistance(){
		float deltaTime = (float)GameEngine.getInstance().getDeltaTime();
		float dist = speed * (distRate * deltaTime);
		return dist;
	}
	
	private float getTimeDependantRotation(){
		float deltaTime = (float)GameEngine.getInstance().getDeltaTime();
		float rot =  (rotRate * deltaTime);
		return rot;
	}
	
	
	
	
	public Matrix4f getCameraPositionMatrix() {
		Matrix4f result = new Matrix4f();
		Matrix4f rotMat = MatrixUtil.convertToMatrix(this.camRotations);
		result = result.mul(result, rotMat, result);
		result.translate(this.pos);
		return result;
	}
	
	private void updatePosition(float deltaPitch, float deltaYaw, float deltaRoll, float deltaFwd, float deltaRight){
		
		Quaternion qPitch = new Quaternion();
		qPitch.setFromAxisAngle(new Vector4f(this.right.x, this.right.y, this.right.z, deltaPitch));
		camRotations.mul(camRotations, qPitch, camRotations);
		
		Quaternion qYaw = new Quaternion();
		qYaw.setFromAxisAngle(new Vector4f(this.up.x, this.up.y, this.up.z, deltaYaw));
		camRotations.mul(camRotations, qYaw, camRotations);
					
		Quaternion qRoll = new Quaternion();
		qRoll.setFromAxisAngle(new Vector4f(this.forward.x, this.forward.y, this.forward.z, deltaRoll));
		camRotations.mul(camRotations, qRoll, camRotations);
		
		
	}
    
	
	
    public void processInput() {
		float dist = getTimeDependantDistance();
		float rot = getTimeDependantRotation();
		float deltaPitch = 0f;
		float deltaRoll = 0f;
		float deltaYaw = 0f;
		
		
		//System.out.println(deltaTime);
		
		if(Keyboard.isKeyDown(Keyboard.KEY_Q)){
			deltaRoll -= rot;
		}
		if(Keyboard.isKeyDown(Keyboard.KEY_E)){
			deltaRoll +=rot;
		}
		
		if(Keyboard.isKeyDown(Keyboard.KEY_W) & Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)){
		}else if(Keyboard.isKeyDown(Keyboard.KEY_W)){
			deltaPitch -= rot;
		}
		
		if(Keyboard.isKeyDown(Keyboard.KEY_S) & Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)){
		}else if(Keyboard.isKeyDown(Keyboard.KEY_S)){
			deltaPitch += rot;
			
		}
		
		
		if(Keyboard.isKeyDown(Keyboard.KEY_A) & Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)){
		}else if(Keyboard.isKeyDown(Keyboard.KEY_A)){
			deltaYaw += rot;
		}
		
		if(Keyboard.isKeyDown(Keyboard.KEY_D) & Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)){
		}else if(Keyboard.isKeyDown(Keyboard.KEY_D)){
			deltaYaw -= rot;
		}
		
		
		
		
		if(Keyboard.isKeyDown(Keyboard.KEY_ADD) ||Keyboard.isKeyDown(Keyboard.KEY_EQUALS)  || Keyboard.isKeyDown(Keyboard.KEY_F)){
			setSpeed(getSpeed()+0.001f);
		}
		if(Keyboard.isKeyDown(Keyboard.KEY_MINUS) || Keyboard.isKeyDown(Keyboard.KEY_F)){
			setSpeed(getSpeed()-0.001f);
		}
		if(Keyboard.isKeyDown(Keyboard.KEY_BACK)){
			setSpeed(0);
		}
		
		updatePosition(deltaPitch, deltaYaw, deltaRoll, speed,0);
		
	}
	
	

	
}


i’ve removed my movement code so as to return to the “most working” state as i ask for suggestions.

thanks!
J.

Oh yeah and I should have said: the angle of rotation is atan(theta) instead of theta as is.

I don’t see the interest of using quaternions here as you still use Euler transforms with rotations around 3 axis… :clue:

thanks :slight_smile:

as long as we do not avoid Math.sin() :clue:

What’s the matter with Math.sin()?