What on earth am I doing wrong?

LWJGL 3

Hey again,

So I’ve written this Camera class which handles the viewport transformation but there always seems to be something wrong with the transformation. If I negate the z component of the translation, I can get the camera to move in the right direction… sort of. Basically the more you move around the scene, the more the movement functions fall apart, and the camera begins to move in the wrong direction.


package com.ibm.bitone.renderer;

import org.joml.Matrix4f;
import org.joml.Vector3f;

public class Camera 
{
	private final float PI = 3.1415926535897932384626433832795f;
	
	private Vector3f position;
	private Vector3f rotation;
	private Vector3f forward;
	private Vector3f right;
	private Vector3f up;

	private Matrix4f viewMatrix;
	
	//private double movementSpeed = 1.0;
	
	public void init()
	{
		viewMatrix = new Matrix4f();
		//viewMatrix.lookAt(0.0f, 3.0f, -5.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, viewMatrix);
		
		rotation = new Vector3f();
		position = new Vector3f();
		forward = new Vector3f();
		right = new Vector3f();
		up = new Vector3f();
		
		//position.set(0.0f, 3.0f, -5.0f);
	}
	
	public void updateView(float rotX, float rotY, float rotZ)
	{
		rotation.add(new Vector3f(rotY, rotX, rotZ));
		if(rotation.x >= (PI/2))
			rotation.x = (PI/2);
		else if(rotation.x <= -(PI/2))
			rotation.x = -(PI/2);
		if(rotation.y >= 2 * PI || rotation.y <= -(2 * PI))
			rotation.y = 0;
		
		viewMatrix.identity();
		viewMatrix.rotateXYZ(rotation.x, rotation.y, rotation.z);
		viewMatrix.translate(position.x, position.y, position.z);
		
		forward.set(viewMatrix.m20, viewMatrix.m21, viewMatrix.m22);
		right.set(viewMatrix.m00, viewMatrix.m01, viewMatrix.m02);
		up.set(viewMatrix.m10, viewMatrix.m11, viewMatrix.m12);
	}
	
	public void moveForward()
	{
		position.add(forward);
		updateView(0, 0, 0);
	}
	
	public void moveBackward()
	{
		Vector3f temp = new Vector3f();
		forward.negate(temp);
		position.add(temp);
		updateView(0, 0, 0);
	}
	
	public void moveRight()
	{
		position.add(right);
		updateView(0, 0, 0);
	}
	
	public void moveLeft()
	{
		Vector3f temp = new Vector3f();
		right.negate(temp);
		position.add(temp);
		updateView(0, 0, 0);
	}
	
	public Matrix4f getViewMatrix()
	{
		return new Matrix4f(viewMatrix);
	}
}

(Also, I’m aware that I’m not multiplying movement by delta time, I want to get the camera moving in the right direction first :stuck_out_tongue: )

Okay, so I’ve rewritten the Camera class, but now I run into the problem where the camera is always looking towards the origin, and when I move the mouse it appears to “jiggle” about the origin in a sinusoidal fashion. I’ve included my MouseHandler class also, and even though it passes it’s output to RenderController, for all intents and purposes it may as well pass it straight to the Camera class.

I’m clearly still doing something wrong here, but I have absolutely no idea what it might be. I’m guessing I’m doing the Quaternion calculations wrong? Would someone be able to check over it and see if my maths works out?

Thanks

QCamera.java

package com.ibm.bitone.renderer;

import org.joml.Matrix4f;
import org.joml.Quaternionf;
import org.joml.Vector3f;

public class QCamera 
{
	private final float PI = 3.14159265358979323846264832795f;
	private final float MAX_Y = PI / 2;
	
	// Movement speed factor
	private float speedMul = 1.0f;
	
	// Total rotation about the x-axis (relative to the camera)
	// This is used to limit the up/down "looking" first person style
	private float rotX = 0;
	
	// Orientation tracking vectors
	private Vector3f forward;
	private Vector3f up;
	private Vector3f position;
	// Used to calculate the axis of rotation
	private Vector3f axis;
	// Should never be read, this is used so that some of the joml
	// functions don't edit their parent objects
	private Vector3f trash;
	
	// Initialise the vectors. Camera starts at origin, facing the -Z direction
	public void init()
	{
		forward = new Vector3f(0.0f, 0.0f, -1.0f);
		up = new Vector3f(0.0f, 1.0f, 0.0f);
		position = new Vector3f(0.0f, 0.0f, 0.0f);
		axis = new Vector3f();
		trash = new Vector3f();
	}
	
	// Publicly visible method for rotating the camera
	public void rotateView(float delX, float delY, float delZ)
	{
		rotX += delX;
		
		// Up/down look limiting
		if(this.rotX > MAX_Y)
			rotX = MAX_Y;
		else if(this.rotX < -MAX_Y)
			rotX = -MAX_Y;
		
		// Calculate axis of rotation
		forward.sub(position, axis);
		axis.cross(up).normalize();
		
		// Perform rotation on internal variables
		rotateCamera(delY, axis.x, axis.y, axis.z);
		rotateCamera(delX, 0, 1, 0);
	}
	
	// Private method which contains the heavy duty math for modifying the internal state
	private void rotateCamera(float angle, float x, float y, float z)
	{
		Quaternionf temp = new Quaternionf();
		Quaternionf qView = new Quaternionf();
		Quaternionf result = new Quaternionf();
		
		// Set temporary Quaternion variables, used in later multiplication
		temp.x = x * (float)Math.sin(angle / 2);
		temp.y = y * (float)Math.sin(angle / 2);
		temp.z = z * (float)Math.sin(angle / 2);
		temp.w = (float)Math.cos(angle / 2);
		
		// Get forward vector in Quaternion-multiplyable state
		qView.x = forward.x;
		qView.y = forward.y;
		qView.z = forward.z;
		qView.w = 0;
		
		// Multiply the temp and qView Quats, then multiply the result of that
		// operation with the conjugate of the temp Quat
		// W = R * V * R'
		temp.mul(qView, result);
		result.mul(temp.conjugate());
		
		// Store result of calculations
		forward.x = result.x;
		forward.y = result.y;
		forward.z = result.z;
	}
	
	// Movement functions add some vector to the position
	// TODO: implement frame speed invariance (time dependency)
	public void moveForward()
	{
		position.add(forward.normalize(trash).mul(speedMul, trash));
	}
	
	public void moveBackward()
	{
		position.sub(forward.normalize(trash).mul(speedMul, trash));
	}
	
	public void moveRight()
	{
		position.add(forward.cross(up, trash).mul(speedMul, trash));
	}
	
	public void moveLeft()
	{
		position.sub(forward.cross(up, trash).mul(speedMul, trash));
	}
	
	// Return a transformation matrix for the Camera.
	public Matrix4f getTransMatrix()
	{
		Matrix4f trans = new Matrix4f();
		
		trans.lookAt(position, forward, up);
		return trans;
	}
	
}

MouseHandler.java

package com.ibm.bitone.input;

import org.lwjgl.glfw.GLFWCursorPosCallback;

import com.ibm.bitone.renderer.RenderController;

public class MouseHandler extends GLFWCursorPosCallback
{
	// Previous state
	private double prevX = 0;
	private double prevY = 0;
	
	// Config options
	private float sensitivity = 0.05f;
	
	private RenderController renderer;
	
	public MouseHandler(RenderController renderer)
	{
		this.renderer = renderer;
	}
	
	@Override
	public void invoke(long window, double xpos, double ypos)
	{
		// Calculate difference between current and previous state
		double delX = xpos - prevX;
		double delY = ypos - prevY;
		
		// Set current state to be previous
		prevX = xpos;
		prevY = ypos;
		
		// Integrate sensitivity
		float rotX = (float)delX * sensitivity;
		float rotY = (float)delY * sensitivity;
		
		// Pass values back
		renderer.rotateView(rotX, rotY);
	}
	
}

Hard to find my way through your math. :wink:
Maybe the following helps you out. It is a “free”/“space” camera based on linear/angular acceleration/velocity.
You can control the movement by setting angularAcc/linearAcc appropriately, or just set linearVel/angularVel accordingly for immediate linear movement.
The angular velocity and acceleration are always in euler angle radians per time unit around the local camera axes, so very convenient when you want to always rotate “to the left/right/up/down” relative to the camera.


public class FreeCamera {
    /** In world-space. Use right()/up()/forward() to move in the
     *  respective direction relative to the camera. */
    public Vector3f linearAcc = new Vector3f();
    public Vector3f linearVel = new Vector3f();

    /** Always rotation about the local XYZ axes of the camera! */
    public Vector3f angularAcc = new Vector3f();
    public Vector3f angularVel = new Vector3f();

    public Vector3f position = new Vector3f(0, 0, 10);
    public Quaternionf rotation = new Quaternionf();

    /** Update camera based on elapsed time delta */
    public FreeCamera update(float dt) {
        // update linear velocity based on linear acceleration
        linearVel.fma(dt, linearAcc);
        // update angular velocity based on angular acceleration
        angularVel.fma(dt, angularAcc);
        // update the rotation based on the angular velocity
        rotation.integrate(dt, angularVel.x, angularVel.y, angularVel.z);
        // update position based on linear velocity
        position.fma(dt, linearVel);
        return this;
    }

    /** Compute the world-space 'right' vector */
    public Vector3f right(Vector3f dest) {
        return rotation.positiveX(dest);
    }
    /** Compute the world-space 'up' vector */
    public Vector3f up(Vector3f dest) {
        return rotation.positiveY(dest);
    }
    /** Compute the world-space 'forward' vector */
    public Vector3f forward(Vector3f dest) {
        return rotation.positiveZ(dest).negate();
    }

    /** Apply the camera/view transformation to the given matrix. */
    public Matrix4f apply(Matrix4f m) {
        return m.rotate(rotation).translate(-position.x, -position.y, -position.z);
    }
}

There is also a demo for this camera in the joml-lwjgl3-demos repository.

Not long ago I solved the getLineOfSight() problem.

If you wanna know how it works, PM me, but this is my implementation of the solution:


public Vector3f getLineOfSight(){
        float x = (float) Math.sin(Math.toRadians(yaw));
        float y = (float) Math.sin(-Math.toRadians(pitch));
        float z = (float) -Math.cos(Math.toRadians(yaw));
        return new Vector3f(x, y, z);
}


The above code returns your line of sight vector, which means if you add this vector to your camera translation vector, it sets you moving in the forward direction.

@Roquen mentioned correctly in another thread that if you have already built a matrix with orthogonal column/row vectors in the upper left 3x3 submatrix, then the directions for X, Y and Z can simply be obtained by reading the first, second or third matrix row, respectively. No need to use sin/cos.

So, obtaining the “forward” direction for a built view transformation matrix would simply be:


Matrix4f m = ...; // <- that view matrix
Vector3f forward = new Vector3f();
forward.x = -m.m02;
forward.y = -m.m12;
forward.z = -m.m22;

If you have a normalized quaternion (like in this case) and want to obtain the forward vector from that, you can do:


Quaternionf q = ...; // <- that quaternion
Vector3f forward = new Vector3f();
float dx = q.x + q.x;
float dy = q.y + q.y;
float dz = q.z + q.z;
forward.x =  q.w * dy - q.x * dz;
forward.y = -q.y * dz - q.w * dx;
forward.z =  q.x * dx + q.y * dy - 1.0f;

Or when you use JOML, like in this case, this is neatly packed in the “positiveZ()” method, whose result you then simply negate:


// with a quaternion:
Quaternionf q = ...;
q.positiveZ(forward).negate();
// or with a matrix:
Matrix4f m = ...;
m.positiveZ(forward).negate();