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
r = t+ r.
In homogeneous coordinates, the above translation equation may be written in the matrix form as





rx
ry
rz
1





=




1
0
0
tx
0
1
0
ty
0
0
1
tz
0
0
0
1










rx
ry
rz
1





,
where (rx,ry,rz,1)T and (rx,ry,rz,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 z-coordinate are scaled respectively by sx, sy, and sz, the scaling matrix is
S =




sx
0
0
0
0
sy
0
0
0
0
sz
0
0
0
0
1





.
Again, the transformation is called a uniform scaling if sx = sy = sz. 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 z-axis. Let the rotation angles about the x-, y-, and z-axis be a, b, and q respectively. Then, we have

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 y-axis such that it lies in the y-z plane (If you prefer, you may rotate the line about any other principle axis). Let g2 = ux2+uz2. The rotation matrix is given by:

A =












uz
g
0
- ux
g
0
0
1
0
0
ux
g
0
uz
g
0
0
0
0
1













.
It is noted that the y coordinate of the rotated line is still uy and the z coordinate is g. Next, we rotate the line about the x-axis such that it coincides with the z-axis. The rotation matrix is
B =




1
0
0
0
0
g
-uy
0
0
uy
g
0
0
0
0
1





.
Since the line is now coincident with the z-axis, the rotation about the line by q is given by
C =




cosq
-sinq
0
0
sinq
cosq
0
0
0
0
1
0
0
0
0
1





.
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-1B-1CBA. By the rule of product of matrices, we can derive that
R =




cosq+lux2
luxuy -uzsinq
luxuz +uysinq
0
luxuy +uzsinq
cosq+luy2
luyuz -uxsinq
0
luxuz -uysinq
luyuz +uxsinq
cosq+luz2
0
0
0
0
1





,
where, l = 1-cosq.

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

A =




0
0
0
-px
0
1
0
-py
0
0
1
-pz
0
0
0
1





.
Then, the combined transformation is A-1RA.

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 z-axis by the angle a, b, and q, the combined rotation matrix is given by:

R =



cosq
-sinq
0
sinq
cosq
0
0
0
1








cosb
0
sinb
0
1
0
-sinb
0
cosb








1
0
0
0
cosa
-sina
0
sina
cosa




=



cosbcosq
sinasinbcosq-cosasinq
cosacosqsinb+sinasinq
cosbsinq
sinasinbsinq +cosacosq
cosasinbsinq-sinacosq
-sinb
sinacosb
cosacosb




.

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

A =



a00
a01
a02
a10
a11
a12
a20
a21
a22




.
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 z-axis, we know that A is identical to R. Thus, if cos(b) 0, we have
tana = a21
a22
= sinacosb
cosacosb
= sina
cosa
tanb = -a20



a212+a222
= sinb



cosb2(sina2+cosa2)
= sinb
cosb

tanq = a10
a00
= sinqcosb
cosqcosb
= sinq
cosq

For cosb = 0, we have sin(b) = 1. If sinb = 1, then

R =



0
sin(a-q)
cos(a-q)
0
cos(a-q)
-sin(a-q)
-1
0
0




.
If sinb = -1, we have
R =



0
-sin(a+q)
-cos(a+q)
0
cos(a+q)
-sin(a+q)
1
0
0




.
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.

  1. Let q = 0.
  2. Compute a =  atan 2(a01,a02).
  3. 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 row-vector and transformed as X = XRT = X RxTRyTRzT. 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 

a00+a11+a22-1
2


,
u =



ux
uy
uz




= 1
2sinq




a21-a12
a02-a20
a10-a01




.

5.5  Reflection

In a 3D space, a reflection is used to generate a symmetric image about a plane. Three essential reflections are

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 x-axis, then perform the reflection to the y-z plane, and then rotate the normal back to its original position. The procedures are summarized below:

Therefore, the final transformation matrix is given by C = A-1BA. It is noted that C is symmetric since C2 = ( A-1BA)( A-1BA) = 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 = I-2n×nT =




1-2nx2
-2nxny
-2nxnz
0
-2nxny
1-2ny2
-2nynz
0
-2nxnz
-2nynz
1-2nz2
0
0
0
0
1





.
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-1CT.

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 non-planar 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 front-elevation, top-elevation, and side-elevation 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 x-y plane (i.e., the projectors are parallel to the z-axis), 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

Rz =




1
0
0
0
0
1
0
0
0
0
0
0
0
0
0
1





.
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 z-axis, we may also select the x- and y-axis as the direction of projectors, in which case similar matrices are used for the orthographic projection:
Rx =




0
0
0
0
0
1
0
0
0
0
1
0
0
0
0
1





,       Ry =




1
0
0
0
0
0
0
0
0
0
1
0
0
0
0
1





.
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 x-y plane and the projectors are defined by the direction vector d = (dx,dy,dz)T where dz 0. The projector draw from a given point p = (px,py,pz)T is defined by the line equation:
L = p+ld,
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 = -pz/dz. Substituting this value into the equation of the projector gives the following x and y coordinates of the projected point p:




px
py
pz




=



px-pzdx/dz
py-pzdy/dz
0




.
Therefore, the 4×4 transformation matrix for an oblique projection is equal to





1
0
-dx/dz
0
0
1
-dy/dz
0
0
0
0
0
0
0
0
1





.
.

Perspective projection: For perspective projections, all projectors pass through one point called the center of projection, which is sometimes referred to as the eye-point 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 pin-hole 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 x-y 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 = (px,py,pz)T is given by:

L = l(p-O) = l



px
py
pz




.
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 =



px
py
d




= l



px
py
pz




.
Since d = lpz, we have l = d/pz. Substituting this value into the above equation gives the x and y coordinates of the intersection point:
p =



dpx/pz
dpy/pz
d




.
By definition, p is the projection point we want to compute. The division by pz 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 two-dimensional photograph or a stylization of 3D reality. The perspective transformation we just discussed above can also be expressed by a 4×4 matrix:





1
0
0
0
0
1
0
0
0
0
1
0
0
0
1/d
0





.
This is the first time that the fourth row of the transformation matrix has been different from the row vector (
0
0
0
1
). 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 x-y 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:





1
0
0
0
0
1
0
0
0
0
1
0
0
0
1/d
0










px
py
pz
1





=




px
py
pz
pz/d





.
According to the rules of homogeneous coordinates, 3D coordinates are obtained by dividing the first three elements by the fourth, giving (dpx/pz,dpy/pz,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






1
0
0
0
0
1
0
0
0
0
0
0
0
0
1/d
1





.
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 z-axis. Actually, we may relax the restriction to let the center of projection (i.e., the viewpoint) be an arbitrary point V = (Vx,Vy,Vz)T so as to derive a more robust projection formula. Assume the projection plane is perpendicular to the z-axis 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.,

u = V-q
||V-q||
.
Then, the projection transformation matrix is given by






















1
0
- ux
uz
d ux
uz
0
1
- uy
uz
d uy
uz
0
0
- d
ruz
d(1+ d
ruz
)
0
0
- 1
ruz
1+ d
ruz






















.
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 commonly-used 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 two-dimensional 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 iso-metric 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.z-d);
      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 z-axis.
  • 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 x-axis.

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.x-vertex[i].x)+Math.abs(mos_pt_world.y-vertex[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 x-y or z = 0 plane. Your mouse cursor serves as the viewpoint. In order to simplify the implementation, we fix the z-coordinate 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("    View-distance:"));
      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 x-y 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:

  1. Anand, V.B., Computer Graphics and Geometric Modeling for Engineers, 1993, John Wiley & Sons, Inc.
  2. Ammeraal, L., Computer Graphics for Java Programmers, 1998, John Wiley & Sons, Inc.
  3. Foley, J.D. et al., Computer Graphics: Principles and Practice, 2nd ed., 1995, Addison-Wesley.
  4. Stephens, R., Visual Basic Graphics Programming, 1997, John Wiley & Sons, Inc.