Alex_Hyzer_Kenoyer
Alex_Hyzer_Kenoyer

Reputation: 1331

Apply Quaternion to Camera in libGDX

I am trying to rotate my camera using a Quaternion in libGDX. I have a Quaternion created and being manipulated but I have no idea how to apply it to the camera, everything I've tried hasn't moved the camera at all.

Here is how I set up the rotation Quaternion:

    public void rotateX(float amount) {
        tempQuat.set(tempVector.set(1.0f, 0.0f, 0.0f), amount * MathHelper.PIOVER180);
        rotation = rotation.mul(tempQuat);
    }

    public void rotateY(float amount) {
        tempQuat.set(tempVector.set(0.0f, 1.0f, 0.0f), amount * MathHelper.PIOVER180);
        rotation = tempQuat.mul(rotation);
    }

Here is how I am trying to update the camera (Same update method as the original libGDX version but I added the part about the rotation matrix to the top):

    public void update(boolean updateFrustum) {
        float[] matrix = new float[16];
        rotation.toMatrix(matrix);
        Matrix4 m = new Matrix4();
        m.set(matrix);

        camera.view.mul(m);
        //camera.direction.mul(m).nor();
        //camera.up.mul(m).nor();

        float aspect = camera.viewportWidth / camera.viewportHeight;
        camera.projection.setToProjection(Math.abs(camera.near), Math.abs(camera.far), camera.fieldOfView, aspect);
        camera.view.setToLookAt(camera.position, tempVector.set(camera.position).add(camera.direction), camera.up);
        camera.combined.set(camera.projection);
        Matrix4.mul(camera.combined.val, camera.view.val);

        if (updateFrustum) {
            camera.invProjectionView.set(camera.combined);
            Matrix4.inv(camera.invProjectionView.val);
            camera.frustum.update(camera.invProjectionView);
        }
    }

Upvotes: 2

Views: 2784

Answers (2)

aacotroneo
aacotroneo

Reputation: 2220

I must comment 2 things about your code:

  1. you can't modify the view matrix before a call to setToLookAt, It's useless as that method recomputes the hole matrix.
  2. The first couple of lines on the update() are quite a waste. Avoid creating unecesary objects (especially if you plan to update your camera every cycle). To apply the rotation to a matrix you can just call [matrix].rotate(quaternion).

Finally, there are many ways you can rotate your camera, so I can´t really solve your problem, but if you want to see something rotate just call camera.view.rotate(rotation) after the camera.view.setToLookat. May be you should rotate something else (e.g. the direction vector, the up vector, etc), but you can get started with this:

//protected float[] matrix = new float[16];//unused!

public void update(boolean updateFrustum) {

        float aspect = camera.viewportWidth / camera.viewportHeight;
        camera.projection.setToProjection(Math.abs(camera.near), Math.abs(camera.far), camera.fieldOfView, aspect);
        camera.view.setToLookAt(camera.position, tempVector.set(camera.position).add(camera.direction), camera.up);

        camera.view.rotate(q); // THIS IS THE ONLY REAL CHANGE TO YOUR CODE!

        camera.combined.set(camera.projection);
        Matrix4.mul(camera.combined.val, camera.view.val);

        if (updateFrustum) {
            camera.invProjectionView.set(camera.combined);
            Matrix4.inv(camera.invProjectionView.val);
            camera.frustum.update(camera.invProjectionView);
        }
    }

Happy coding!

Upvotes: 3

James Coote
James Coote

Reputation: 1973

I guess Quaternion.mul() is multiply? I think you need to do more than just one multiplication to do the rotation. Here is the code I use for rotating a vector or point around an axis-angle (Quaternion):

private double[] vecQuat = new double[4];
private double[] resQuat = new double[4];
private double[] thisQuat = new double[4];

private double[] conj = new double[4];

/**
 * Rotates a vector (or point) around this axis-angle
 * 
 * @param vectorX the x component of the vector (or point)
 * @param vectorY the y component of the vector (or point)
 * @param vectorZ the z component of the vector (or point)
 * @param outputArray the array in which the results will be stored
 */
public void RotateVector(double vectorX, double vectorY, double vectorZ, double[] outputArray){

    vecQuat[0] = 0.0f;
    vecQuat[1] = vectorX;
    vecQuat[2] = vectorY;
    vecQuat[3] = vectorZ;

    thisQuat[0] = w;
    thisQuat[1] = x;
    thisQuat[2] = y;
    thisQuat[3] = z;

    getConjugate(conj);
    Multiply(vecQuat,conj,resQuat);
    Multiply(thisQuat,resQuat,vecQuat);

    outputArray[0] = vecQuat[1];
    outputArray[1] = vecQuat[2];
    outputArray[2] = vecQuat[3];

}

public void getConjugate(double[] outputArray){

    outputArray[0] = w;
    outputArray[1] = -x;
    outputArray[2] = -y;
    outputArray[3] = -z;

}

public void Multiply(double[] aq, double[] rq, double[] outputArray){

    outputArray[0] = aq[0] * rq[0] - aq[1] * rq[1] - aq[2] * rq[2] - aq[3] * rq[3];
    outputArray[1] = aq[0] * rq[1] + aq[1] * rq[0] + aq[2] * rq[3] - aq[3] * rq[2];
    outputArray[2] = aq[0] * rq[2] + aq[2] * rq[0] + aq[3] * rq[1] - aq[1] * rq[3];
    outputArray[3] = aq[0] * rq[3] + aq[3] * rq[0] + aq[1] * rq[2] - aq[2] * rq[1];

}

I don't know libgdx, but maybe have a look to see if it has a rotate function like the one above. If not, you can probably add it in


Edit:

I don't know exactly what you want to do with the camera, but here is an example where I use quaternions for an RTS game set in space (think homeworld). Spaceships have a direction vector (the direction they are travelling in), used to update their movement every tick and rotate them to face direction of travel (in the draw() function). They also have a target vector: between the ship's location and its current destination. The ship can only turn at turningCircle radians a second. So after updating movement each tick, I take the cross product of the direction/target vector to get an axis of rotation, and take turningCircle as the angle, to create a quaternion. I rotate the ship direction vector by the quaternion to "turn" the ship.

The net result is the ship over a number of ticks, turns in a graceful arc until it is heading in the right direction.

The same process could also be used in a combat flight simulator to simulate the turning of AI aircraft. Using quaternions will also avoid 'gimbal lock', something that real aircraft suffer from!

However, using quaternions, your AI aircraft might find it more convenient to just fly upside down half the time, so you'd probably have to put in some extra slow rotating of the aeroplane along the axis of its fuselage (i.e. z axis) to simulate the pilot orienting themselves to 'up'. This is actually fairly easy to do by just adding an up and right vector at right angles to the forward (direction) vector

Here is the code (minus the orienting the up vector bit):

/**
 * The current position of the spaceship
 */
private Vertex3D currentPosition;

/**
 * The target position of the spaceship
 */
private Vertex3D targetPosition;

/**
 * The current direction in which the spaceship is travelling
 */
private Vector directionVector;

/**
 * The vector towards which the spaceship is turning
 */
private Vector targetVector;

/**
 * The right orientation vector
 */
private Vector rightOrientationVector;

/**
 * The up orientation vector
 */
private Vector upOrientationVector;

/**
 * Angle in radians by which directionVector turns towards TargetVector every tick
 */
private double turningCircle = 0.05f;

public Spaceship(Vertex3D target){

    currentPosition = new Vertex3D(0,0,0);

    // right hand coordinate system: ship is facing "away" from the camera
    directionVector = new Vector(currentPosition, 0,0,-1);
    rightOrientationVector = new Vector(currentPosition, 1,0,0);
    upOrientationVector = new Vector(currentPosition, 0,1,0);

    targetPosition = target;

}

    protected void tick(){
        incrementPosition();
        turn();
        draw();
    }


    protected void incrementPosition(){

        // get movement
        double velocity = getVelocity();

        // move
        currentPosition.mX(currentPosition.mX + directionVector.mX * velocity);
        currentPosition.mY(currentPosition.mY + directionVector.mY * velocity);
        currentPosition.mZ(currentPosition.mZ + directionVector.mZ * velocity);
    }


    private double[] cross = new double[3];
    private double[] newDir = new double[3];
    private Quaternion quat;

    protected void turn(){

        // update target vector relative to new position
        setTargetVector();

        // turn direction vector towards target vector
        MathsExtras.crossProduct(directionVector.mX, directionVector.mY, directionVector.mZ, targetVector.mX, targetVector.mY, targetVector.mZ, cross); 

        quat = new Quaternion(cross[0], cross[1], cross[2], turningCircle);

        quat.RotateVector(directionVector.mX, directionVector.mY, directionVector.mZ, newDir); 

        directionVector.mX = newDir[0];
        directionVector.mY = newDir[1];
        directionVector.mZ = newDir[2];

        direction.normalise();


        // update right orientation
        MathsExtras.crossProduct(direction.mX, direction.mY, direction.mZ, upOrientationVector.mX, upOrientationVector.mY, upOrientationVector.mZ, cross);

        rightOrientationVector.mX = cross[0];
        rightOrientationVector.mY = cross[1];
        rightOrientationVector.mZ = cross[2];

        rightOrientationVector.normalise();

        // update up orientation
        MathsExtras.crossProduct(rightOrientationVector.mX, rightOrientationVector.mY, rightOrientationVector.mZ, direction.mX, direction.mY, direction.mZ, cross);

        upOrientationVector.mX = cross[0];
        upOrientationVector.mY = cross[1];
        upOrientationVector.mZ = cross[2];

        upOrientationVector.normalise();

    }

    protected void setTargetVector(){
        targetVector.mX = targetPosition.getmX() - currentPosition.getmX();
        targetVector.mY = targetPosition.getmY() - currentPosition.getmY();
        targetVector.mZ = targetPosition.getmZ() - currentPosition.getmZ();
        targetVector.normalise();
    }

So, if you want to use the same code to say turn the camera to look at an object in a first person shooter, you could set the direction vector as the direction the player is looking in, and the currentPosition as the player/camera position, the target as the target object, and turningCirlce for the angle / how fast you want the camera to turn. In draw() you would just use lookAt(directionVector.mX + currentPosition.mX, directionVector.mY + currentPosition.mY, directionVector.mZ + currentPosition.mZ)

Upvotes: 1

Related Questions