The First Person or Euler Camera

The first-person or Euler camera is defined by the following attributes: The field of view in degrees. The viewport aspect ratio. The near and far clipping planes. A position in 3D space.

An angle around the y-axis (yaw).

An angle around the x-axis (pitch). This is limited to the range -90 to +90 degrees. Think how far you can tilt your own head and try to go beyond those angles! I'm not responsible for any injuries.

The first three attributes are used to define the perspective projection matrix. We did this already with calls to gluPerspective() in all of your 3D examples.

The other three attributes define the position and orientation of the camera in our world. We will construct a matrix from this as outlined in the previous chapter. Let's put all this together into a simple class. Listing 11-9 shows you the code.

Additionally we want to be able to move the camera in the direction it is heading. For this we need a unit length direction vector, which we can add to the position vector of the camera. We can create such a vector with the help of the Matrix class the Android API offers us. Let's think about this for a moment.

In its default configuration our camera will look down the negative z-axis. So its direction vector is (0, 0,-1). When we specify a yaw or pitch angle, this direction vector will be rotated accordingly. To figure out the direction vector we just need to multiply it with a matrix that will rotate the default direction vector just as OpenGL ES will rotate the vertices of our models.

Let's have a look at how all this works in code. Listing 11-9 shows you the EulerCamera class.

Listing 11-9. EulerCamera.java, a Simple First Person Camera Based on Euler Angles Around the x- and y-Axes package com.badlogic.androidgames.framework.gl;

import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLU; import android.opengl.Matrix;

import com.badlogic.androidgames.framework.math.Vector3;

public class EulerCamera {

final Vector3 position = new Vector3();

float yaw;

float pitch;

float fieldOfView;

float aspectRatio;

float near;

float far;

The first three members hold the position and rotation angles of the camera. The other four members hold the parameters used for calculating the perspective projection matrix. By default our camera is located at the origin of the world, looking down the negative z-axis.

public EulerCamera(float fieldOfView, float aspectRatio, float near, float far){ this.fieldOfView = fieldOfView;

this.aspectRatio = aspectRatio; this.near = near; this.far = far;

The constructor takes four parameters that define the perspective projection. We leave the camera position and rotation angles as they are.

public Vector3 getPosition() { return position;

public float getYaw() { return yaw;

public float getPitch() { return pitch;

The getter methods just return the camera orientation and position.

public void setAngles(float yaw, float pitch) { if (pitch < -90) pitch = -90; if (pitch > 90) pitch = 90; this.yaw = yaw; this.pitch = pitch;

public void rotate(float yawInc, float pitchInc) { this.yaw += yawInc; this.pitch += pitchInc; if (pitch < -90) pitch = -90; if (pitch > 90) pitch = 90;

The setAngles() method allows us to directly specify the yaw and pitch of the camera. Note that we limit the pitch to be in the range -90 to 90. We can't rotate our own head further than that, so our camera shouldn't be able to do that either.

The rotate() method is nearly identical to the setAngles() method. Instead of setting the angles it increases them by the parameters. This will be useful when we implement a little touchscreen-based control scheme in the next example.

public void setMatrices(GL10 gl) {

gl.glMatrixMode(GL10.GL_PROJECTION); gl.glLoadIdentity();

GLU.gluPerspective(gl, fieldOfView, aspectRatio, near, far);

gl.glMatrixMode(GL10.GL_MODELVIEW);

gl.glLoadIdentity();

gl.glTranslatef(-position.x, -position.y, -position.z);

The setMatrices() method just sets the projection and model-view matrices as discussed earlier. The projection matrix is set via gluPerspective() based on the parameters given to the camera in the constructor. The model-view matrix performs the "prophet-mountain" trick by applying a rotation around the x- and y-axes as well as a translation. All involved factors are negated to achieve the effect that the camera remains at the origin of the world looking down the negative z-axis. We thus rotate and translate the objects around the camera, not the other way around.

final float[] matrix = new float[16];

final float[] inVec = { 0, 0, -1, 1 }; final float[] outVec = new float[4]; final Vector3 direction = new Vector3();

public Vector3 getDirection() { Matrix.setIdentityM(matrix, 0); Matrix.rotateM(matrix, 0, yaw, 0, 1, 0); Matrix.rotateM(matrix, 0, pitch, 1, 0, 0); Matrix.multiplyMV(outVec, 0, matrix, 0, inVec, 0); direction.set(outVec[0], outVec[1], outVec[2]); return direction;

Finally we have the mysterious getDirection() method. It is accompanied by a couple of final members that we use for the calculations inside the method. We do this so that we don't allocate new float arrays and Vector3 instances each time the method is called. Consider those members to be temporary working variables.

Inside the method we first set up a transformation matrix that contains the rotation around the x- and y-axes. We don't need to include the translation, since we only want a direction vector, not a position vector. The direction of the camera is independent of its location in the world. The Matrix methods we invoke should be self-explanatory. The only strange thing is that we actually apply them in reverse order without negating the arguments. We do the opposite in the setMatrices() method. That's because we are now actually transforming a point the same way we'd transform our virtual camera, which does not have to be located at the origin and oriented so that it looks down the negative z-axis. The vector we rotate is (0,0,-1), stored in inVec. That's the default direction of our camera if not rotation is applied. All the matrix multiplications do is rotate this direction vector by the camera's pitch and roll so that it points in the direction the camera is heading toward. The last thing we do is set a Vector3 instance based on the result of the matrix-vector multiplication and return that to the caller. We can use this unit-length direction vector later on to move the camera in the direction it is heading.

Equipped with this little helper class we can write a tiny example program that allows us to move through a world of crates.

0 0

Post a comment