Vectors in 3D

In Chapter 8, we discussed vectors and their interpretation in 2D. As you might have guessed, all of the things we discussed there still hold in 3D space as well. All we need to do is to add one more coordinate to our vector, namely the z-coordinate.

The operations we looked at with vectors in 2D can be easily transferred to 3D space. We specify vectors in 3D with a statement like the following:

v = (x, y, z)

Addition in 3D is carried out as follows:

c = a + b = (a.x, a.y, b.z) + (b.x, b.y, b.z) = (a.x + b.x, a.y + b.y, a.z + b.z)

Subtraction works exactly the same way:

c = a - b = (a.x, a.y, b.z) - (b.x, b.y, b.z) = (a.x - b.x, a.y - b.y, a.z - b.z)

Multiplying a vector by a scalar works like this:

a' = a × scalar = (a.x × scalar, a.y × scalar, a.z × scalar)

Measuring the length of a vector in 3D is also quite simple; we just add the z-coordinate to the Pythagorean equation:

|a| = sqrt(a.x ×a.x + a.y ×a.y + a.z ×a.z)

Based on this, we can also normalize our vectors to unit length again:

a' = (a.x / |a|, a.y / |a|, a.z / |a|)

All of the interpretations of vectors we talked about in Chapter 8 hold in 3D as well:

  • Positions are just denoted by a normal vector's x-, y- and z-coordinates.
  • Velocities and accelerations can also be represented as 3D vectors. Each component then represents a certain quantity of the attribute on one axis, such as meters per second in the case of velocity or meters per second per second for acceleration.
  • We can represent directions (or axes) as simple 3D unit vectors. We did that in Chapter 8 when we used the rotation facilities of OpenGL ES.
  • We can measure distances by subtracting the starting vector from the end vector and measuring the resulting vector's length.

One more operation that can be rather useful is rotation of a 3D vector around a 3D axis. We used this principle earlier via the OpenGL ES glRotatef() method. However, we can't use it to rotate one of the vectors that we'll use to store positions or directions of our game objects, because it only works on vertices that we submit to the GPU. Luckily, there's a Matrix class in the Android API that allows us to emulate what OpenGL ES does on the GPU. Let's write a Vector3 class that implements all of these features. Listing 11–1 shows you the code, which we'll again explain along the way.

Listing 11–1. Vector3.java, a Vector in 3D

package com.badlogic.androidgames.framework.math;

import android.opengl.Matrix;
import android.util.FloatMath;

public class Vector3 {
    private static final float[] matrix = new float[16];
    private static final float[] inVec = new float[4];
    private static final float[] outVec = new float[4];
    public float x, y, z;

The class starts with a couple of private static final float arrays. We'll need them later on when we implement the new rotate() method of our Vector3 class. Just remember that the matrix member has 16 elements and the inVec and outVec each have 4 elements.

The x, y, and z members defined next should be self-explanatory. They store the actual components of the vector:

    public Vector3() {
    }

    public Vector3(float x, float y, float z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public Vector3(Vector3 other) {
        this.x = other.x;
        this.y = other.y;
        this.z = other.z;
    }

    public Vector3 cpy() {
    return new Vector3(x, y, z);
    }

    public Vector3 set(float x, float y, float z) {
        this.x = x;
        this.y = y;
        this.z = z;
        return this;
    }

    public Vector3 set(Vector3 other) {
        this.x = other.x;
        this.y = other.y;
        this.z = other.z;
        return this;
    }

Like Vector2, our Vector3 class has a couple of constructors and setters and a cpy() method, so that we can easily clone vectors or set them from components calculated in our program.

    public Vector3 add(float x, float y, float z) {
        this.x += x;
        this.y += y;
        this.z += z;
        return this;
    }

    public Vector3 add(Vector3 other) {
        this.x += other.x;
        this.y += other.y;
        this.z += other.z;
        return this;
    }

    public Vector3 sub(float x, float y, float z) {
        this.x -= x;
        this.y -= y;
        this.z -= z;
        return this;
    }

    public Vector3 sub(Vector3 other) {
        this.x -= other.x;
        this.y -= other.y;
        this.z -= other.z;
        return this;
    }

    public Vector3 mul(float scalar) {
        this.x *= scalar;
        this.y *= scalar;
        this.z *= scalar;
        return this;
    }

The various add(), sub() and mul() methods are just an extension of what we had in our Vector2 class with an additional z-coordinate. They implement what we discussed a few pages ago. Straightforward, right?

    public float len() {
        return FloatMath.sqrt(x * x + y * y + z * z);
    }

    public Vector3 nor() {
        float len = len();
        if (len != 0) {
            this.x /= len;
            this.y /= len;
            this.z /= len;
        }
        return this;
    }

The len() and nor() methods are also essentially the same as in the Vector2 class. All we do is incorporate the new z-coordinate into the calculations.

    public Vector3 rotate(float angle, float axisX, float axisY, float axisZ) {
        inVec[0] = x;
        inVec[1] = y;
        inVec[2] = z;
        inVec[3] = 1;
        Matrix.setIdentityM(matrix, 0);
        Matrix.rotateM(matrix, 0, angle, axisX, axisY, axisZ);
        Matrix.multiplyMV(outVec, 0, matrix, 0, inVec, 0);
        x = outVec[0];
        y = outVec[1];
        z = outVec[2];
        return this;
    }

And here's our new rotate() method. As indicated earlier, it makes use of Android's Matrix class. The Matrix class essentially consists of a couple of static methods, like Matrix.setIdentityM() or Matrix.rotateM(). These operate on float arrays, similar to the ones we defined earlier. A matrix is stored as 16 float values, and a vector is expected to have four elements. We won't go into detail about the inner workings of the class; all we need is a way to emulate the matrix capabilities of OpenGL ES on the Java side, and that's exactly what this class offers to us. All of the methods work on a matrix and operate in exactly the same way as glRotatef(), glTranslatef(), or glIdentityf() in OpenGL ES.

The method starts off by setting the vector's components to the inVec array we defined earlier. Next, we call Matrix.setIdentityM() on the matrix member of our class. This will “clear” the matrix. With OpenGL ES, we used glIdentityf() to do the same thing with matrices residing on the GPU. Next we call Matrix.rotateM(). It takes the float array holding the matrix, an offset into that array, the angle we want to rotate by in degrees, and the (unit length) axis we want to rotate around. This method is equivalent to glRotatef(). It will multiply the given matrix by a rotation matrix. Finally, we call Matrix.multiplyMV(), which will multiply our vector stored in inVec by the matrix. This applies all of the transformations stored in the matrix to the vector. The result will be output in outVec. The rest of the method just grabs the resulting new components from the outVec array and stores them in the Vector3's members.

NOTE: You can use the Matrix class to do a lot more than just rotating vectors. It operates in exactly the same way as OpenGL ES in its effects on the passed-in matrix.

    public float dist(Vector3 other) {
        float distX = this.x - other.x;
        float distY = this.y - other.y;
        float distZ = this.z - other.z;
        return FloatMath.sqrt(distX * distX + distY * distY + distZ * distZ);
    }

    public float dist(float x, float y, float z) {
        float distX = this.x - x;
        float distY = this.y - y;
        float distZ = this.z - z;
        return FloatMath.sqrt(distX * distX + distY * distY + distZ * distZ);
    }

    public float distSquared(Vector3 other) {
        float distX = this.x - other.x;
        float distY = this.y - other.y;
        float distZ = this.z - other.z;
        return distX * distX + distY * distY + distZ * distZ;
    }

    public float distSquared(float x, float y, float z) {
        float distX = this.x - x;
        float distY = this.y - y;
        float distZ = this.z - z;
        return distX * distX + distY * distY + distZ * distZ;
    }
}

Finally, we have the usual dist() and distSquared() methods to calculate the distance between two vectors in 3D.

Note that we left out the angle() method from Vector2. While it is possible to measure the angle between two vectors in 3D, that does not give us an angle in the range 0 to 360. Usually, we get away with just evaluating the angle between two vectors in the x/y, z/y, and x/z plane by using only two components of each vector and applying the Vector2.angle() method. We won't need this functionality until our last game, so we'll return to the topic at that point.

We think you'll agree that we don't need an explicit example of using this class. We can just invoke it the same way we did with the Vector2 class in Chapter 8. On to the next topic: lighting in OpenGL ES.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.195.91