D Transforms

To get your polygons to move around in 3D space (and maybe do some tricks), you apply 3D transforms to them. 3D polygon transforms usually consist of translation, scaling, and rotation.

  • Translation, or moving a polygon around in space, is achieved by adding the translation vector to each vertex in the polygon.
  • Scaling, or making a polygon grow or shrink in size (without changing its shape), is trivial if the center of the polygon is the origin. In this case, multiplying the scale factor by each vertex scales the polygon. However, if the center of the polygon is not the origin, this can also translate the polygon as well as scale. Scaling isn't a requirement of our 3D engine, so we'll skip this one.
  • Rotation involves rotating a polygon around the x-axis, the y-axis, the z-axis, or a combination thereof.

Rotations

First, take a look at the math involved in rotation—specifically, rotation around the z-axis. When you rotate around the z-axis, the z value of the point isn't changed, so it's just like doing a 2D rotation around the point (0,0), as in Screenshot. Every point in the polygon rotates around (0,0), which changes its position but not its distance from (0,0).

Screenshot Rotation around the z-axis is a simple 2D rotation around the point (0,0).

Java graphics 07fig12.gif


If we treat x and y as a magnitude, r, and an angle around the z-axis, Screenshot, then the x and y coordinates of the point that you want to rotate can be interpreted as follows:

x = r cos Screenshot
y = r sin Screenshot


Rotating counterclockwise by an angle, q becomes this (a visual example is in Screenshot):

x' = r cos (Screenshot+q)
y' = r sin (Screenshot+q)


Screenshot Rotating a point (x,y) by the angle q. The angle of the original point (x,y) is Screenshot, and the angle of the rotated point (x', y') is (Screenshot+q).

Java graphics 07fig13.gif


This reduces to the following:

x' = r cos Screenshot cos q – r sin Screenshot sin q
y' = r sin Screenshot cos q + r cos Screenshot sin q


Substituting the original formulas for x and y gives you this:

x' = x cos q – y sin q
y' = x sin q + y cos q


That's it. This formula enables you to rotate a point around the z-axis counterclockwise by the angle q. Rotating around the x-axis and the y-axis is similar. Here are all the rotation formulas: To rotate counterclockwise around the x-axis:

x' = x y' = y cos q – z sin q
z' = y sin q + z cos q


To rotate counterclockwise around the y-axis:

x' = z sin q + x cos q
y' = y z' = z cos q – x sin q


To rotate counterclockwise around the z-axis:

x' = x cos q – y sin q
y' = x sin q + y cos q
z' = z


A complete rotation transform involves three rotations: one each for the x-, y-, and z-axes. When you transform a polygon, just apply these formulas to each point in the polygon.

Encapsulating Rotation and Translation Transforms

Even though these rotation formulas are simple, keep in mind that the functions Math.sin() and Math.cos() don't come cheap: They're implemented using a lot of calculations and involve JNI calls, so calling these functions for every Vector3D you want to transform isn't too efficient. Instead, you can precompute the value of the sine and cosine of each angle whenever the transform changes. Because the transform changes only once per frame, at most, this greatly reduces the number of Math.sin() and Math.cos() calls. The Transform3D class in Listing 7.4 implements this. It contains all the information needed to apply translation and rotation transforms, but it doesn't actually apply transforms. Later, when you apply transforms, you can use the precomputed sine and cosine values to rotate all of your Vector3Ds.

Listing 7.4 Transform3D.java
package com.brackeen.javagamebook.math3D;
/**
 The Transform3D class represents a rotation and translation.
*/
public class Transform3D {
 protected Vector3D location;
 private float cosAngleX;
 private float sinAngleX;
 private float cosAngleY;
 private float sinAngleY;
 private float cosAngleZ;
 private float sinAngleZ;
 /**
 Creates a new Transform3D with no translation or rotation.
 */
 public Transform3D() {
 this(0,0,0);
 }
 /**
 Creates a new Transform3D with the specified translation
 and no rotation.
 */
 public Transform3D(float x, float y, float z) {
 location = new Vector3D(x, y, z);
 setAngle(0,0,0);
 }
 /**
 Creates a new Transform3D
 */
 public Transform3D(Transform3D v) {
 location = new Vector3D();
 setTo(v);
 }
 public Object clone() {
 return new Transform3D(this);
 }
 /**
 Sets this Transform3D to the specified Transform3D.
 */
 public void setTo(Transform3D v) {
 location.setTo(v.location);
 this.cosAngleX = v.cosAngleX;
 this.sinAngleX = v.sinAngleX;
 this.cosAngleY = v.cosAngleY;
 this.sinAngleY = v.sinAngleY;
 this.cosAngleZ = v.cosAngleZ;
 this.sinAngleZ = v.sinAngleZ;
 }
 /**
 Gets the location (translation) of this transform.
 */
 public Vector3D getLocation() {
 return location;
 }
 public float getCosAngleX() {
 return cosAngleX;
 }
 public float getSinAngleX() {
 return sinAngleX;
 }
 public float getCosAngleY() {
 return cosAngleY;
 }
 public float getSinAngleY() {
 return sinAngleY;
 }
 public float getCosAngleZ() {
 return cosAngleZ;
 }
 public float getSinAngleZ() {
 return sinAngleZ;
 }
 public float getAngleX() {
 return (float)Math.atan2(sinAngleX, cosAngleX);
 }
 public float getAngleY() {
 return (float)Math.atan2(sinAngleY, cosAngleY);
 }
 public float getAngleZ() {
 return (float)Math.atan2(sinAngleZ, cosAngleZ);
 }
 public void setAngleX(float angleX) {
 cosAngleX = (float)Math.cos(angleX);
 sinAngleX = (float)Math.sin(angleX);
 }
 public void setAngleY(float angleY) {
 cosAngleY = (float)Math.cos(angleY);
 sinAngleY = (float)Math.sin(angleY);
 }
 public void setAngleZ(float angleZ) {
 cosAngleZ = (float)Math.cos(angleZ);
 sinAngleZ = (float)Math.sin(angleZ);
 }
 public void setAngle(float angleX, float angleY, float angleZ)
 {
 setAngleX(angleX);
 setAngleY(angleY);
 setAngleZ(angleZ);
 }
 public void rotateAngleX(float angle) {
 if (angle != 0) {
 setAngleX(getAngleX() + angle);
 }
 }
 public void rotateAngleY(float angle) {
 if (angle != 0) {
 setAngleY(getAngleY() + angle);
 }
 }
 public void rotateAngleZ(float angle) {
 if (angle != 0) {
 setAngleZ(getAngleZ() + angle);
 }
 }
 public void rotateAngle(float angleX, float angleY,
 float angleZ)
 {
 rotateAngleX(angleX);
 rotateAngleY(angleY);
 rotateAngleZ(angleZ);
 }
}


Applying Transforms

Actually applying the transforms occurs in some methods you'll add to the Vector3D class, shown here in Listing 7.5.

Listing 7.5 Transform Methods for Vector3D.java
/**
 Rotate this vector around the x-axis the specified amount,
 using precomputed cosine and sine values of the angle to
 rotate.
*/
public void rotateX(float cosAngle, float sinAngle) {
 float newY = y*cosAngle - z*sinAngle;
 float newZ = y*sinAngle + z*cosAngle;
 y = newY;
 z = newZ;
}
/**
 Rotate this vector around the y-axis the specified amount,
 using precomputed cosine and sine values of the angle to
 rotate.
*/
public void rotateY(float cosAngle, float sinAngle) {
 float newX = z*sinAngle + x*cosAngle;
 float newZ = z*cosAngle - x*sinAngle;
 x = newX;
 z = newZ;
}
/**
 Rotate this vector around the y-axis the specified amount,
 using precomputed cosine and sine values of the angle to
 rotate.
*/
public void rotateZ(float cosAngle, float sinAngle) {
 float newX = x*cosAngle - y*sinAngle;
 float newY = x*sinAngle + y*cosAngle;
 x = newX;
 y = newY;
}
/**
 Adds the specified transform to this vector. This vector
 is first rotated, then translated.
*/
public void add(Transform3D xform) {
 // rotate
 addRotation(xform);
 // translate
 add(xform.getLocation());
}
/**
 Subtracts the specified transform to this vector. This
 vector is translated, then rotated.
*/
public void subtract(Transform3D xform) {
 // translate
 subtract(xform.getLocation());
 // rotate
 subtractRotation(xform);
}
/**
 Rotates this vector with the angle of the specified
 transform.
*/
public void addRotation(Transform3D xform) {
 rotateX(xform.getCosAngleX(), xform.getSinAngleX());
 rotateZ(xform.getCosAngleZ(), xform.getSinAngleZ());
 rotateY(xform.getCosAngleY(), xform.getSinAngleY());
}
/**
 Rotates this vector with the opposite angle of the
 specified transform.
*/
public void subtractRotation(Transform3D xform) {
 // note that sin(-x) == -sin(x) and cos(-x) == cos(x)
 rotateY(xform.getCosAngleY(), -xform.getSinAngleY());
 rotateZ(xform.getCosAngleZ(), -xform.getSinAngleZ());
 rotateX(xform.getCosAngleX(), -xform.getSinAngleX());
}


In this code, you'll notice the methods to "add" and "subtract" a transform. Adding a transform applies the transform as is. Subtracting a transform applies the inverse of a transform: Instead of rotating by q and adding location V, it rotates by –q and subtracts location V. You'll use the subtract methods for free camera movement later in this chapter. The add(Transform3D) and subtract(Transform3D) methods are also in the Polygon3D class. These methods simply transform every vertex in the polygon.

Rotation Order

Because each rotation occurs one axis at a time, the order of the axis rotation is important. Different rotation orders can have different end results. Here, when adding a transformation, the rotation occurs in the order x, z, y. Subtracting a transform rotates in the opposite order. This choice was arbitrary and provides predictable results for walking around a 3D scene in the style of a first-person shooter.

Screenshot


   
Comments