Chapter 5
3D Transformation and Projection
5.1 Introduction
Mechanical components, telephone handsets, car bodies, and human sculptures are all 3D objects. In order to represent and manipulate these 3D objects, we need to study 3D geometric transformations such as 3D translation, scaling, rotation, and reflection. These fundamental transformations enable us to stretch a geometric object or change the representation of an object from one coordinate system to the other. However, such transformations alone do not enable us to display a 3D object in a 2D computer screen. Therefore, projections of 3D objects onto a 2D plane is also studied in this chapter.
We have seen the use of homogeneous coordinates in the previous chapter. It is said that the advantage of using homogeneous coordinate is to treat translation, scaling, rotation, and reflection in a uniform manner so that a series of transformations can be reduced to a single transformation matrix. For the same reason, the homogeneous coordinates is used in this chapter for analysis.
Based on our study on geometric transformations, we shall add a transformation class into our geometric library that creates commonly used transformation matrices. We shall also add transformation method into our GPoint and GVector classes so that we can call it to transform points and vectors.
5.2 Translation
A translation is often used to move the origin of one coordinate system to some specified position so as to form a new coordinate system. Let (O;x,y,z) and (O¢;x¢,y¢, z¢) denote two coordinate systems with the corresponding axes in these two systems being parallel. Assume the displacement vector from O to O¢ is denoted by t and a fixed point in the (O;x,y,z) and (O¢;x¢,y¢,z¢) systems by the position vector r and r¢ respectively. Then, r is related to r¢ by the equation
In homogeneous coordinates, the above translation equation may be written in the matrix form as

æ ç ç
ç ç è

 
ö ÷ ÷
÷ ÷ ø

= 
æ ç ç
ç ç è

 
ö ÷ ÷
÷ ÷ ø


æ ç ç
ç ç è

 
ö ÷ ÷
÷ ÷ ø

, 

where (r_{x},r_{y},r_{z},1)^{T} and (r_{x}¢,r_{y}¢,r_{z}¢,1)^{T} are the homogeneous representation of vector r and r¢.
5.3 Scaling
A scaling transformation is used to shrink or stretch the coordinate system so as to fit an image into your drawing area. If the x, y, and zcoordinate are scaled respectively by s_{x}, s_{y}, and
s_{z}, the scaling matrix is
Again, the transformation is called a uniform scaling if s_{x} = s_{y} = s_{z}. Otherwise, it is called a differential scaling. In engineering applications, a uniform scaling is more preferred since it shrinks or stretches an object without changing the proportion of the object.
Applying scaling transformation to an object changes usually the coordinates of any point on the object unless the point is at origin. In the case where the coordinates of a certain point (e.g., the center) of an object have to be preserved, we may first translate system such that this point coincides with the origin, then apply the scaling transformation, and then translate system back to where it was.
5.4 Rotations
For the purpose of analyzing or viewing a computer generated object, the end user of a CAD system often wants to rotate the object about the principle axes or an arbitrary line by a certain angle. To accomplish such requirements we need to study in this section two types of rotations: rotations about principle axes and rotation about a general line. When we understand how to derive a rotation matrix from the given axis and angle, we shall then consider the reverse operation: Extracting the rotation angle and axis direction from a known rotation matrix.
Rotations about axes: A 2D rotation takes place in a 2D plane. Therefore, there is no need to specify a rotation axis. To describe a 3D rotation, however, we usually need to indicate the axis about which the rotation is. The most essential 3D rotations are the rotations about the three principle axes, namely, the x, y, and zaxis. Let the rotation angles about the x, y, and zaxis be a, b, and q respectively. Then, we have
 rotation about the xaxis
R_{x} = 
æ ç ç
ç ç è

 
ö ÷ ÷
÷ ÷ ø

, 

 rotation about the yaxis
R_{y} = 
æ ç ç
ç ç è

 
ö ÷ ÷
÷ ÷ ø

, 

 rotation about the zaxis
R_{z} = 
æ ç ç
ç ç è

 
ö ÷ ÷
÷ ÷ ø

. 

Rotation about general line: For simplicity, we first consider a rotation about a line passing through the origin by the angle q. Assume this line is given by L = lu, where u is the direction unit vector. Then, the first step is to rotate the line about the yaxis such that it lies in the yz plane (If you prefer, you may rotate the line about any other principle axis). Let g^{2} = u_{x}^{2}+u_{z}^{2}. The rotation matrix is given by:
A = 
æ ç ç ç ç ç ç
ç ç ç ç ç ç è

 
ö ÷ ÷ ÷ ÷ ÷ ÷
÷ ÷ ÷ ÷ ÷ ÷ ø

. 

It is noted that the y coordinate of the rotated line is still u_{y} and the z coordinate is g. Next, we rotate the line about the xaxis such that it coincides with the zaxis. The rotation matrix is
Since the line is now coincident with the zaxis, the rotation about the line by q is given by
Therefore, the rotation by the angle q about the line passing through the origin is a combination of several rotations about the principle axes, i.e., R = A^{1}B^{1}CBA. By the rule of product of matrices, we can derive that
where, l = 1cosq.
We now consider a rotation about a general line by the angle q. Assume the line is given by L = p+lu. Then, the required transformation is a combination of translating p to the origin, rotating the object about the line passing through the origin, and translating p back to where it was. Let the translation matrix be A, where
Then, the combined transformation is A^{1}RA.
Extracting rotation angles: Given the rotation angles about the principle axes and
the sequence of rotations, we can derive a combined rotation matrix. For example, if the
rotation sequence is first about x, then y, then zaxis by the angle
a, b, and q,
the combined rotation matrix is given by:
R = 
æ ç ç
ç è

 
ö ÷ ÷
÷ ø


æ ç ç
ç è

 
ö ÷ ÷
÷ ø


æ ç ç
ç è

 
ö ÷ ÷
÷ ø



It is noted that the transformation is represented by 3×3 matrices. This is
because the rotation information does not appear in the fourth row and fourth column.
For simplicity, we thus write only the first 3×3 matrix for analysis. We now
assume that a generic rotation matrix is given by
It is then possible to extract a set of rotations angles from the A with respect to a certain
rotation sequence. For example, by assuming that the rotation sequence is first about the
x, then y, then zaxis, we know that A is identical to R. Thus, if cos(b)
¹ 0, we have
tana = 
a_{21} a_{22}

= 
sinacosb cosacosb

= 
sina cosa



tanb = 
a_{20}

= 
sinb
 Ö

cosb^{2}(sina^{2}+cosa^{2})


= 
sinb ±cosb



tanq = 
a_{10} a_{00}

= 
sinqcosb cosqcosb

= 
sinq cosq



For cosb = 0, we have sin(b) =
±1. If sinb = 1, then
If sinb = 1, we have
It is readily seen that an infinite number of solutions exists for sin(b) = ±1.
An arbitrary set of solutions may be computed as follows.
 Let q = 0.
 Compute a = atan 2(a_{01},a_{02}).
 If sinb < 0, then b = p/2. Otherwise, b = p/2.
It should be pointed out that in this document a vector X is considered
as a column vector and transformed as X¢ = RX. In some literature,
however, X is a rowvector and transformed as
X¢ = XR^{T} = X R_{x}^{T}R_{y}^{T}R_{z}^{T}. In this case, the computation of a, b, and q is done by swapping the subscript indices.
Since any 3D rotation can also be considered as a rotation through the angle q about the line passing through the origin, we can alternatively extract the angle q and line direction u from the given rotation matrix A. That is
q = acos 
æ ç
è

a_{00}+a_{11}+a_{22}1 2

ö ÷
ø

, 

u = 
æ ç ç
ç è

 
ö ÷ ÷
÷ ø

= 
1 2sinq


æ ç ç
ç è

 
ö ÷ ÷
÷ ø

. 

5.5 Reflection
In a 3D space, a reflection is used to generate a symmetric image about a plane. Three essential reflections are
 reflection to the yz plane
 reflection to the xz plane
 and reflection to the xy plane
It is seen that reflection about the principle plane is very simple. However,
it is quite involved to derive the transformation matrix of reflection to a general plane whose normal is n although the final result is amazingly simple. For simplicity, we assume that the plane passes through the origin. In this case, we may first rotate the normal of the plane such that it coincides with the xaxis, then perform the reflection to the yz plane, and then rotate the normal back to its original position. The procedures are summarized below:
 Computing a unit vector u = n×i/n×i, where i is the unit vector for the xaxis.
 Computing the angle between n and i by cosq = n·i.
 Rotating n through q about u using the matrix
where, l = 1cosq.
 Reflection to the yz plane whose matrix is
 Rotating n back to where it was using A^{1} = A^{T}.
Therefore, the final transformation matrix is given by C = A^{1}BA. It is noted that C is symmetric since C^{2} = ( A^{1}BA)( A^{1}BA) = I, where I denotes the identity (or unit) matrix. By the rule of product of matrices, we can derive that C is given by
C = I2n×n^{T} = 
æ ç ç
ç ç è

 
ö ÷ ÷
÷ ÷ ø

. 

If the plane does not pass through the origin, we may choose an arbitrary point on the plane and apply a translation T such that this arbitrary point coincides with the origin. After reflection, we then translate the image back by T^{1}. Therefore, the final transformation is T^{1}CT.
5.6 Projection
Although various 3D display devices exit, most computer graphics view surfaces are two dimensional planes. The solution to the mismatch between 3D objects and 2D displays is accomplished by the use of planar geometric projections, which transform 3D objects onto a 2D projection plane. Besides planar geometric projections there are other types of projections as well. For example, if a 3D object is projected onto a curved surface, the projection is then called nonplanar projection. In this book, we restrict ourselves to planar geometric projections and simply refer them to as projections hereafter.
Projection, just like all other transformations, is performed point by point. Straight lines, called projectors, are drawn through the points in 3D space and the projected point becomes the intersection between the projector and the plane of projection (or projection plane). The two most common projections are parallel and perspective projections. In what follows we shall study each of them in detail.
Parallel projection: The simplest projection is a parallel projection for which the projectors are all parallel to each other. If the projectors are also perpendicular to the projection plane, the projection is respectively called the orthographic (parallel) projection. Furthermore, if the direction of projectors is in the direction of a principle axis, the orthographic projection yields one of the three most commonly used projections: the frontelevation, topelevation, and sideelevation projections. In engineering drawing, these three orthographic projections are widely used to depict machine parts, assemblies, and buildings because distance and angles can be measured from the drawings. However, since each depicts only one face of an object, the 3D nature of the projected object can be difficult to deduce even if several projections of the same object are studied simultaneously. For an orthographic projection, the projection plane may be translated along the projectors without any change in the projection image. Therefore, without the loss of generality, we may assume that the projection plane passes through the origin. If the projection plane is chosen to be the xy plane (i.e., the projectors are parallel to the zaxis), the projected points will have the same x and y components as the original 3D points while the z component is set to zero. Representing this projection in a 4×4 transformation matrix gives
R_{z} = 
æ ç ç
ç ç è

 
ö ÷ ÷
÷ ÷ ø

. 

Observe that the elements in the third row and third column are all zero. This yields a singular matrix. This is expectable because the projection maps points in 3D space onto a 2D plane. The projection transformation has no inverse since it is impossible to reconstruct the 3D object from its 2D projection without additional information. Besides choosing the zaxis, we may also select the x and yaxis as the direction of projectors, in which case similar matrices are used for the orthographic projection:
R_{x} = 
æ ç ç
ç ç è

 
ö ÷ ÷
÷ ÷ ø

, R_{y} = 
æ ç ç
ç ç è

 
ö ÷ ÷
÷ ÷ ø

. 

If parallel projectors are not perpendicular to the projection plane, an oblique (parallel) projection is obtained. For simplicity, we assume that the projection plane is chosen to be the xy plane and the projectors are defined by the direction vector d = (d_{x},d_{y},d_{z})^{T} where d_{z} ¹ 0. The projector draw from a given point p = (p_{x},p_{y},p_{z})^{T} is defined by the line equation:
where l Î R is the parameter of the line equation. Thus, the projected point, where z = 0, is given by the parametric value of l = p_{z}/d_{z}. Substituting this value into the equation of the projector gives the following x and y coordinates of the projected point p¢:

æ ç ç
ç è

 
ö ÷ ÷
÷ ø

= 
æ ç ç
ç è

 
ö ÷ ÷
÷ ø

. 

Therefore, the 4×4 transformation matrix for an oblique projection is equal to
.
Perspective projection: For perspective projections, all projectors pass through one point called the center of projection, which is sometimes referred to as the eyepoint or viewpoint. The center of projection is conventionally placed on the opposite side of the projection plane from the object being projected. Thus, perspective projection works like a pinhole camera which forces the light rays to go through one single point. The difference in the case of camera is that the image is behind the center of projection; therefore, it is inverted. To simplify the algebra, the first step is to apply the translation of the scene so that the center of projection is at the origin. Then, rotations are applied until the projection plane becomes parallel to the xy plane. Since all projectors go through the origin O = (0,0,0)^{T}, the line equation of a projector that goes through a point p = (p_{x},p_{y},p_{z})^{T} is given by:
L = l(pO) = l 
æ ç ç
ç è

 
ö ÷ ÷
÷ ø

. 

The projection plane must be placed between the point and the origin and is assumed to be at the distance d from the origin. Accordingly, the equation of the projection plane is z = d and the intersection between L (the projector line) and the projection plane lies on the z = d plane. Therefore, we may write the intersection point as
p¢ = 
æ ç ç
ç è

 
ö ÷ ÷
÷ ø

= l 
æ ç ç
ç è

 
ö ÷ ÷
÷ ø

. 

Since d = lp_{z}, we have l = d/p_{z}. Substituting this value into the above equation gives the x and y coordinates of the intersection point:
By definition, p¢ is the projection point we want to compute. The division by p_{z} causes the perspective projection of farther objects to be smaller than that of closer objects. This effect is known as perspective foreshortening. Therefore, in a perspective projection relative dimensions are not preserved, and a distant line is displayed smaller than a near line of the same length. This enables human beings to perceive depth in a twodimensional photograph or a stylization of 3D reality. The perspective transformation we just discussed above can also be expressed by a 4×4 matrix:
This is the first time that the fourth row of the transformation matrix has been different from the row vector (
). In fact, the elements in the fourth row may be used for general perspective projections. Taking the center of projection as the origin and having the plane of projection aligned with the xy plane makes this form of the matrix particularly simple. It can be shown that this is the correct matrix by applying it to the position vector p:

æ ç ç
ç ç è

 
ö ÷ ÷
÷ ÷ ø


æ ç ç
ç ç è

 
ö ÷ ÷
÷ ÷ ø

= 
æ ç ç
ç ç è

 
ö ÷ ÷
÷ ÷ ø

. 

According to the rules of homogeneous coordinates, 3D coordinates are obtained by dividing the first three elements by the fourth, giving (dp_{x}/p_{z},dp_{y}/p_{z},d)^{T}, which is the correct answer.
An alternative formulation for the perspective projection places the projection plane at z = 0 as we did for parallel projections and the center of projection at z = d. In this case, we can similarly derive that the 4×4 transformation matrix is given by
It is noted that, when d tends to infinity (i.e., the center of projection is at infinity), the perspective projection matrix becomes the orthographic projection matrix.
The perspective projections discussed above restrict ourselves to some special cases in which the center of projection is either placed at the origin or on the zaxis. Actually, we may relax the restriction to let the center of projection (i.e., the viewpoint) be an arbitrary point V = (V_{x},V_{y},V_{z})^{T} so as to derive a more robust projection formula. Assume the projection plane is perpendicular to the zaxis at a distance d. We first compute the distance r between the viewpoint V and the point q = (0,0,d)^{T}, then the direction vector from q to V, i.e.,
Then, the projection transformation matrix is given by

æ ç ç ç ç ç ç ç ç ç ç ç
ç ç ç ç ç ç ç ç ç ç è

 
ö ÷ ÷ ÷ ÷ ÷ ÷ ÷ ÷ ÷ ÷ ÷
÷ ÷ ÷ ÷ ÷ ÷ ÷ ÷ ÷ ÷ ø

. 

It is readily seen that the above matrix generalizes all three projection matrices discussed previously. For example, if d = 0 and r®¥ we obtain the orthographic projection matrix.
5.7 Applications
We have discussed most commonlyused 3D transformations and projections. It is now the time to implement the transformation class and add it to our GeomLib directory. Let's call this transformation class the GMatrix4x4. The member variable of GMatrix4x4 is a 4×4 twodimensional array. Its member methods include 3D translation, scaling, rotation, mirror, and projections. These methods are straightforward implementation of the results discussed in the previous sections. We now type the following code:
package GeomLib;
public class GMatrix4x4
{
public double matrix[][];
// Constructor
public GMatrix4x4()
{
matrix = new double[4][4];
SetUnitMatrix();
}
// Initializing matrix to unit matrix.
public void SetUnitMatrix()
{
int i, j;
for (i=0; i<4; i++)
{
for (j=0; j<i; j++)
matrix[i][j] = 0.0;
matrix[i][i] = 1.0;
for (j=i+1; j<4; j++)
matrix[i][j] = 0.0;
}
}
// Setup transformation for isometric view:
public void SetIsoMetricView()
{
SetUnitMatrix();
matrix[0][0] = 0.935;
matrix[0][1] = 0.0;
matrix[0][2] = 0.354;
matrix[1][0] = 0.118;
matrix[1][1] = 0.943;
matrix[1][2] = 0.312;
}
public void DefByTranslate(GVector T)
{
SetUnitMatrix();
// Compute translation part (4th column):
matrix[0][3] = T.x;
matrix[1][3] = T.y;
matrix[2][3] = T.z;
}
public void DefByScale(double sx, double sy, double sz, GPoint p)
{
// Compute first 3 x 3 matrix:
SetUnitMatrix();
matrix[0][0] = sx;
matrix[1][1] = sy;
matrix[2][2] = sz;
// Compute translation part (4th column):
matrix[0][3] = p.x  matrix[0][0] * p.x;
matrix[1][3] = p.y  matrix[1][1] * p.y;
matrix[2][3] = p.z  matrix[2][2] * p.z;
}
public void DefByRotation(double angle, GVector U, GPoint p)
{
double sin_a, cos_a, temp;
sin_a = Math.sin(angle);
cos_a = Math.cos(angle);
temp = 1.0  cos_a;
U.Normalize();
SetUnitMatrix();
// Compute first 3 x 3 matrix:
matrix[0][0] = cos_a + temp * U.x * U.x;
matrix[0][1] = temp * U.x * U.y  U.z * sin_a;
matrix[0][2] = temp * U.x * U.z + U.y * sin_a;
matrix[1][0] = temp * U.x * U.y + U.z * sin_a;
matrix[1][1] = cos_a + temp * U.y * U.y;
matrix[1][2] = temp * U.y * U.z  U.x * sin_a;
matrix[2][0] = temp * U.x * U.z  U.y * sin_a;
matrix[2][1] = temp * U.y * U.z + U.x * sin_a;
matrix[2][2] = cos_a + temp * U.z * U.z;
// Compute translation part (4th column):
matrix[0][3] = p.x  (matrix[0][0]*p.x+matrix[0][1]*p.y+matrix[0][2]*p.z);
matrix[1][3] = p.y  (matrix[1][0]*p.x+matrix[1][1]*p.y+matrix[1][2]*p.z);
matrix[2][3] = p.z  (matrix[2][0]*p.x+matrix[2][1]*p.y+matrix[2][2]*p.z);
}
public void DefByMirror(GVector N, GPoint p)
{
N.Normalize();
SetUnitMatrix();
// Compute first 3 x 3 matrix:
matrix[0][0] = 1.0  2.0 * N.x * N.x;
matrix[1][1] = 1.0  2.0 * N.y * N.y;
matrix[2][2] = 1.0  2.0 * N.z * N.z;
matrix[1][0] = matrix[0][1] = 2.0 * N.x * N.y;
matrix[2][0] = matrix[0][2] = 2.0 * N.x * N.z;
matrix[2][1] = matrix[1][2] = 2.0 * N.y * N.z;
// Compute translation part (4th column):
matrix[0][3] = p.x  (matrix[0][0]*p.x+matrix[0][1]*p.y+matrix[0][2]*p.z);
matrix[1][3] = p.y  (matrix[1][0]*p.x+matrix[1][1]*p.y+matrix[1][2]*p.z);
matrix[2][3] = p.z  (matrix[2][0]*p.x+matrix[2][1]*p.y+matrix[2][2]*p.z);
}
// Create parallel projection matrix:
public void DefByParallel(GVector viewDir)
{
viewDir.Normalize();
SetUnitMatrix();
matrix[0][2] = viewDir.x / viewDir.z;
matrix[1][2] = viewDir.y / viewDir.z;
matrix[2][2] = 0.0;
}
// Create generalized perspective projection matrix:
public void DefByPerspective(GPoint viewPt, double d)
{
double rho, temp;
GVector viewDir = new GVector();
viewDir.SetVector(viewPt.x, viewPt.y, viewPt.zd);
rho = viewDir.GetLength();
viewDir.x /= rho;
viewDir.y /= rho;
viewDir.z /= rho;
SetUnitMatrix();
temp = viewDir.x / viewDir.z;
matrix[0][2] = temp;
matrix[0][3] = d * temp;
temp = viewDir.y / viewDir.z;
matrix[1][2] =  temp;
matrix[1][3] = d * temp;
temp = 1.0 / (rho * viewDir.z);
matrix[2][2] = d * temp;
matrix[2][3] = d * (d * temp + 1.0);
matrix[3][2] = temp;
matrix[3][3] = d * temp + 1.0;
}
}
We save the above code under the name GMatrix4x4.java in the GeomLib directory and compile it to get GMatrix4x4.class.
It is said that, no matter what geometric object is in consideration, transformations are
always performed point by point (or vector by vector). Therefore, we need to add the
transformation method into the GPoint and GVector classes before we can
implement some illustration applets. The code fragment we want to add into the
GPoint and GVector classes is listed below.
// Transformation:
public void Transform(GMatrix4x4 g4x4)
{
double P[]={0.0, 0.0, 0.0, 0.0};
for (int i=0; i<4; i++)
{
P[i] = g4x4.matrix[i][0]*x + g4x4.matrix[i][1]*y +
g4x4.matrix[i][2]*z + g4x4.matrix[i][3];
}
this.x = P[0] / P[3];
this.y = P[1] / P[3];
this.z = P[2] / P[3];
}
The above implementation is straightforward and very simple. However, it is inefficient since we do not consider the cases in which most elements of the fourth row and fourth column are zero.
Now, we are ready to implement some illustration applets. The first applet is called the ShowTransform in which there are Drag, Scale, Roate, and Mirror buttons that invoke the translation, scaling, rotation, and reflection transformation respectively. The object to be transformed is a pyramid made of five vertices. Brief description of each button is given below.
 Drag button: When it is clicked, you can drag the pyramid by moving your mouse while pressing down a mouse button.
 Scale button: When it is clicked, the default scale factor 5.2 is displayed in the text field. If you prefer, you may change the default value. By pointing your mouse to one of the vertices and clicking the mouse button, you then enlarge or shrink the pyramid. The vertex you are pointing to will remain in the same position.
 Rotate button: When it is clicked, the default rotation angle (20 degree) is displayed in the text field. If you prefer, you may change this default value. By pointing your mouse to one of the vertices and clicking the mouse button, the pyramid will be rotated about the line that passes through the vertex you are pointing to. The direction of the line is parallel to the zaxis.
 Mirror button: When it is clicked, you can point your mouse to one of the vertices and click the mouse button to reflect the pyramid about the plane that passes through the vertex you are pointing to. The normal of the plane is parallel to the xaxis.
With the above descriptions in mind it is then not difficult to understand the following source code.
import java.awt.*;
import java.awt.event.*;
import java.applet.Applet;
import GeomLib.*;
public class ShowTransform extends Applet
implements ActionListener, MouseListener, MouseMotionListener
{
int vtx_id;
double scale, angle;
Double Val;
boolean isDrag, isScale, isRotate, isMirror;
TextField field;
GPoint mos_pt_world=new GPoint(), vertex[] = new GPoint[5];
GVector vec = new GVector();
GMatrix4x4 g4x4 = new GMatrix4x4();
GService gs = new GService();
public void init()
{
scale = 5.2;
angle = 20.0;
vtx_id = 1;
isDrag = isScale = isRotate = isMirror = false;
setBackground(Color.lightGray);
gs.SetOrigin(0, getSize().height);
g4x4.SetIsoMetricView();
addMouseListener(this);
addMouseMotionListener(this);
setLayout(new FlowLayout(FlowLayout.RIGHT));
Button b;
b = new Button("Drag");
b.addActionListener(this);
add(b);
b = new Button("Scale");
b.addActionListener(this);
add(b);
b = new Button("Rotate");
b.addActionListener(this);
add(b);
b = new Button("Mirror");
b.addActionListener(this);
add(b);
add(new Label(" Scale/Angle:"));
field = new TextField(2);
field.addActionListener(this);
add(field);
vertex[0] = new GPoint(20, 20, 0);
vertex[1] = new GPoint(20, 20, 20);
vertex[2] = new GPoint(40, 20, 20);
vertex[3] = new GPoint(40, 20, 0);
vertex[4] = new GPoint(30, 40, 10);
}
public void actionPerformed(ActionEvent evt)
{
String buttonName = evt.getActionCommand();
if (buttonName.equals("Drag"))
{
isDrag = true;
isScale = isRotate = isMirror = false;
}
else if (buttonName.equals("Scale"))
{
field.setText(String.valueOf(scale));
Val = Double.valueOf(field.getText());
isScale = true;
isDrag = isRotate = isMirror = false;
}
else if (buttonName.equals("Rotate"))
{
field.setText(String.valueOf(angle));
Val = Double.valueOf(field.getText());
isRotate = true;
isDrag = isScale = isMirror = false;
}
else if (buttonName.equals("Mirror"))
{
isMirror = true;
isDrag = isScale = isRotate = false;
}
else
Val = Double.valueOf(field.getText());
}
// Required to declare listener interfaces:
public void mousePressed(MouseEvent evt){}
public void mouseReleased(MouseEvent evt){}
public void mouseEntered(MouseEvent evt){}
public void mouseExited(MouseEvent evt){}
public void mouseMoved(MouseEvent evt){}
// Check if the mouse position matches any control vertex:
public void mouseClicked(MouseEvent evt)
{
int i;
double dist;
gs.DCToWorld(evt.getX(), evt.getY(), mos_pt_world);
vtx_id = 1;
for (i=0; i<5; i++)
{
dist = Math.abs(mos_pt_world.xvertex[i].x)+Math.abs(mos_pt_world.yvertex[i].y);
if (dist < 3.0)
{
vtx_id = i;
break;
}
}
if (vtx_id > 1.0)
{
if (isMirror)
{
vec.SetVector(1.0, 0.0, 0.0);
g4x4.DefByMirror(vec, vertex[vtx_id]);
}
else if (isScale)
{
scale = Val.doubleValue();
if (scale <= 0.0)
scale = 1.0;
g4x4.DefByScale(scale, scale, scale, vertex[vtx_id]);
}
else if (isRotate)
{
angle = Val.doubleValue();
vec.SetVector(0.0, 0.0, 1.0);
g4x4.DefByRotation(angle*Math.PI/180.0, vec, vertex[vtx_id]);
}
repaint();
}
}
// Update selected the control vertex position:
public void mouseDragged(MouseEvent evt)
{
if (isDrag)
{
GPoint center = new GPoint(0, 0, 0);
for (int i=0; i<5; i++)
{
center.x += vertex[i].x;
center.y += vertex[i].y;
center.z += vertex[i].z;
}
center.x /= 5.0;
center.y /= 5.0;
center.z /= 5.0;
gs.DCToWorld(evt.getX(), evt.getY(), mos_pt_world);
vec.SetVector(center, mos_pt_world);
g4x4.DefByTranslate(vec);
repaint();
}
}
public void paint(Graphics g)
{
int i;
for (i=0; i<5; i++)
vertex[i].Transform(g4x4);
g.setColor(Color.blue);
for (i=0; i<3; i++)
{
gs.drawLine(g, vertex[i].x, vertex[i].y, vertex[i+1].x, vertex[i+1].y);
gs.drawLine(g, vertex[4].x, vertex[4].y, vertex[i].x, vertex[i].y);
}
gs.drawLine(g, vertex[4].x, vertex[4].y, vertex[3].x, vertex[3].y);
gs.drawLine(g, vertex[0].x, vertex[0].y, vertex[3].x, vertex[3].y);
g4x4.SetUnitMatrix();
}
}
Let's save the above code under the name ShowTransform.java in the GeomLib
directory and compile it to get ShowTransform.class. We then write a HTML file named
ShowTransform.html to invoke the applet. If everything works well, you should see an
applet similar to the one in Figure 5.1.
Our second applet is called ShowProjection that has a text field and two buttons:
Parallel and Perspective. The projection plane is the xy or z = 0 plane.
Your mouse cursor serves as the viewpoint. In order to simplify the implementation, we fix
the zcoordinate of the viewpoint to be the value indicated in the text field. This implies
that the viewpoint moves only on the z = d plane, where d is the value you specify in the
text field. When you click one of the two buttons, you can then drag your mouse around the
applet's drawing area to see how the cube is displayed on a 2D plane. The source code of
ShowProjection is given below.
import java.awt.*;
import java.awt.event.*;
import java.applet.Applet;
import GeomLib.*;
public class ShowProjection extends Applet
implements ActionListener, MouseMotionListener
{
double viewDist;
boolean isPerspective;
GPoint center=new GPoint(), mos_pt_world=new GPoint();
GPoint vertex[]=new GPoint[8], prj_vtx[]=new GPoint[8];
GMatrix4x4 g4x4 = new GMatrix4x4();
GService gs = new GService();
TextField field;
public void init()
{
isPerspective = false;
setBackground(Color.lightGray);
gs.SetOrigin(0, getSize().height);
gs.DCToWorld(getSize().width/2, getSize().height/2, center);
addMouseMotionListener(this);
setLayout(new FlowLayout(FlowLayout.RIGHT));
Button b;
b = new Button("Parallel");
b.addActionListener(this);
add(b);
b = new Button("Perspective");
b.addActionListener(this);
add(b);
add(new Label(" Viewdistance:"));
field = new TextField(4);
field.addActionListener(this);
add(field);
viewDist = 200.0;
field.setText(String.valueOf(viewDist));
vertex[0] = new GPoint(55, 35, 20);
vertex[1] = new GPoint(85, 35, 20);
vertex[2] = new GPoint(85, 65, 20);
vertex[3] = new GPoint(55, 65, 20);
vertex[4] = new GPoint(55, 65, 60);
vertex[5] = new GPoint(55, 35, 60);
vertex[6] = new GPoint(85, 35, 60);
vertex[7] = new GPoint(85, 65, 60);
for (int i=0; i<8; i++)
prj_vtx[i] = new GPoint();
}
public void actionPerformed(ActionEvent evt)
{
Double U = Double.valueOf(field.getText());
viewDist = U.doubleValue();
String buttonName = evt.getActionCommand();
if (buttonName.equals("Perspective"))
isPerspective = true;
else
isPerspective = false;
}
// Required to declare listener interfaces:
public void mouseMoved(MouseEvent evt){}
// Update selected the control vertex position:
public void mouseDragged(MouseEvent evt)
{
gs.DCToWorld(evt.getX(), evt.getY(), mos_pt_world);
mos_pt_world.z = viewDist;
if (isPerspective)
{
g4x4.DefByPerspective(mos_pt_world, 0.0);
}
else
{
GVector viewDir = new GVector();
viewDir.SetVector(mos_pt_world, center);
viewDir.Normalize();
g4x4.DefByParallel(viewDir);
}
repaint();
}
public void paint(Graphics g)
{
int i;
for (i=0; i<8; i++)
{
prj_vtx[i].SetPoint(vertex[i]);
prj_vtx[i].Transform(g4x4);
}
// Draw the face that is closer to the xy plane in blue:
g.setColor(Color.blue);
for (i=0; i<3; i++)
gs.drawLine(g, prj_vtx[i].x, prj_vtx[i].y, prj_vtx[i+1].x, prj_vtx[i+1].y);
gs.drawLine(g, prj_vtx[0].x, prj_vtx[0].y, prj_vtx[3].x, prj_vtx[3].y);
// Draw other faces in red:
g.setColor(Color.red);
for (i=3; i<7; i++)
gs.drawLine(g, prj_vtx[i].x, prj_vtx[i].y, prj_vtx[i+1].x, prj_vtx[i+1].y);
gs.drawLine(g, prj_vtx[0].x, prj_vtx[0].y, prj_vtx[5].x, prj_vtx[5].y);
gs.drawLine(g, prj_vtx[1].x, prj_vtx[1].y, prj_vtx[6].x, prj_vtx[6].y);
gs.drawLine(g, prj_vtx[7].x, prj_vtx[7].y, prj_vtx[4].x, prj_vtx[4].y);
gs.drawLine(g, prj_vtx[7].x, prj_vtx[7].y, prj_vtx[2].x, prj_vtx[2].y);
}
}
We save the above code under the name ShowProjection.java in the GeomLib
directory and compile it to get ShowProjection.class. We then write a HTML file
named ShowProjection.html to invoke the applet. If everything works well, you
should see an applet similar to the one in Figure 5.2.
The applets we created in this section, though very simple, have illustrated practical
applications of transformations and projections. Next time when you play with a commercial
CAD system and see tools such ``rooming'', ``rotating'', ``mirroring'', etc., you will know
how they are implemented and feel confident to give comments or suggestions.
5.8 Summary and references
We completed our discussion on geometric transformations in this chapter. As a result of
study, we implemented a new class, called the GMatrix4x4, in the geometric library.
This class takes care of creations of 3D transformations. To illustrate the use of this new
class, we created two applets ShowTranform and ShowProjection that allow
users to apply geometric transformations to the displayed object.
Suggested readings for this chapter are:
 Anand, V.B., Computer Graphics and Geometric Modeling for Engineers, 1993, John Wiley & Sons, Inc.
 Ammeraal, L., Computer Graphics for Java Programmers, 1998, John Wiley & Sons, Inc.
 Foley, J.D. et al., Computer Graphics: Principles and Practice, 2nd ed., 1995, AddisonWesley.
 Stephens, R., Visual Basic Graphics Programming, 1997, John Wiley & Sons, Inc.