Node:Transforms, Next:, Previous:Transforming Points, Up:Top



Transforms

When Points are transformed using shift(), shear(), or one of the other transformation functions, the world_coordinates are not modified directly. Instead, another data member of class Point is used to store the information about the transformation, namely transform of type class Transform. A Transform object has a single data element of type Matrix and a number of member functions. A Matrix is simply a 4 X 4 array1 of reals defined using typedef real Matrix[4][4]. Such a matrix suffices for performing all of the transformations (affine and perspective) possible in three-dimensional space.2 Any combination of transformations can be represented by a single transformation matrix. This means that consecutive transformations of a Point can be "saved up" and applied to its coordinates all at once when needed, rather than updating them for each transformation.

Transforms work by performing matrix multiplication of Matrix with the homogeneous world_coordinates of Points. If a set of homogeneous coordinates \alpha = (x, y, z, w) and

     Matrix M =
     a e i m
     b f j n
     c g k o
     d h l p
     
then the set of homogeneous coordinates \beta resulting from multiplying \alpha and M is calculated as follows:
     \beta = \alpha\times M = ((xa + yb + zc + wd),  (xe + yf + zg + wh),
     (xi + yj + zk + wl), (xm + yn + zo + wp))
     
Please note that each coordinate of \beta can be influenced by all of the coordinates of \alpha.

Operations on matrices are very important in computer graphics applications and are described in many books about computer graphics and geometry. For 3DLDF, I've mostly used Huw Jones' Computer Graphics through Key Mathematics and David Salomon's Computer Graphics and Geometric Modeling.

It is often useful to declare and use Transform objects in 3DLDF, just as it is for transforms in Metafont. Transformations can be stored in Transforms and then be used to transform Points by means of Point::operator*=(const Transform&).

     1. Transform t;
     2. t.shift(0, 1);
     3. Point p(1, 0, 0);
     4. p *= t;
     5. p.show("p:");
     -| p: (1, 1, 0)
     

When a Transform is declared (line 1), it is initialized to an identity matrix. All identity matrices are square, all of the elements of the main diagonal (upper left to lower right) are 1, and all of the other elements are 0. So a 4 X 4 identity matrix, as used in 3DLDF, looks like this:

     1 0 0 0
     0 1 0 0
     0 0 1 0
     0 0 0 1
     
If a matrix A is multiplied with an identity matrix I, the result is identical to A, i.e., A * I = A. This is the salient property of an identity matrix.

The same affine transformations are applied in the same way to Transforms as they are to Points, i.e., the functions scale(), shift(), shear(), and rotate() correspond to the Point versions of these functions, and they take the same arguments:

     Point p;
     Transform t;
     p.shift(3, 4, 5);
     t.shift(3, 4, 5);
     => p.transform == t
     p.show_transform("p:");
     -| p:
        Transform:
              0   0.707   0.707       0
         -0.866   0.354  -0.354       0
           -0.5  -0.612   0.612       0
              0       0       0       1
     t.show("t:");
     -| t:
              0   0.707   0.707       0
         -0.866   0.354  -0.354       0
           -0.5  -0.612   0.612       0
              0       0       0       1
     
     

Footnotes

  1. It is unfortunate that the terms ``array'', ``matrix'', and ``vector'' have different meanings in C++ and in normal mathematical usage. However, in practice, these discrepancies turn out not to cause many problems. Stroustrup, The C++ Programming Language, section 22.4, p. 662.

  2. In fact, none of the operations for transformations require all of the elements of a 4 X 4 matrix. In many 3D graphics programs, the matrix operations are modified to use smaller transformation matrices, which reduces the storage requirements of the program. This is a bit tricky, because the affine transformations and the perspective transformation use different elements of the matrix. I consider that the risk of something going wrong, possibly producing hard-to-find bugs, outweighs any benefits from saving memory (which is usually no longer at a premium, anyway). In addition, there may be some interesting non-affine transformations that would be worth implementing. Therefore, I've decided to use full 4 X 4 matrices in 3DLDF.