# CS315 Lab 3: 3D Transformations

## Highlights of this lab:

This lab is an introduction to Matrix Transformation

## Assignment

After the lab lecture, you have one week to:

• Practice modeling and viewing transformations with boxes.cpp and answer some questions
• Create fingers for a robot arm based on robot_arm.cpp

## Lecture Notes

### A. A Simplified View of the OpenGL Pipeline

Each vertex of polygons will pass through two main stages of transformations:

• Model view transformation (translation, rotation, and scaling of objects, 3D viewing transformation)
• Projection (perspective or orthographic)

There is one global matrix internally for each of the two stage above:

• Mmodelview
• Mprojection

Given a 3D vertex of a polygon, P = [x, y, z, 1]T, in homogeneous coordinates, applying the model view transformation matrix to it will yield a new coordinate:

P’ = [x’, y’, z’, 1]T = Mmodelview*P.

By applying projection to P’, a 2D coordinate in homogeneous form is produced:

P” = [x”, y”, 1]T = Mprojection*P’.

The final coordinate [x”, y”] will be a point on the screen to be drawn.

Each matrix can be, and should be, initialized to identity at the beginning of an application.

For example, setting the model view matrix to the identity matrix can be done by:

```glMatrixMode(GL_MODELVIEW); //selecting the global matrix to be modified
glLoadIdentity();           //set the currently selected global matrix to identity```

Similarly, we can set the projection matrix to identity as well:

```glMatrixMode(GL_PROJECTION);

These two global transformation matrices can be modified by the following elementary transformation functions as required by individual applications.

### B. Elementary Transformations

• Right-handed and left-handed coordinate system: With your right hand line your first two fingers up with the positive y axis and line your thumb up with the positive x asis. When you bend your remaining two fingers, the direction they point is the positive z axis in a right handed coordinate system. Compare to the figure below. The other system shown is a left-handed coordinate system. It is sometimes used in graphics texts. A consequence of using the right-handed system is that the negative z-axis goes into the screen.

• Right-handed coordinate system is used most often. In OpenGL, both the local coordinate system for object models (such as cube, sphere), and the camera coordinate system are defined in right-handed system.
• In the following discussion, we assume that the matrix mode is set to GL_MODELVIEW. Therefore, all transformation function calls apply to Mmodelview, unless the other is specifically mentioned.

#### Translation:

`glTranslate*(dx, dy, dz);`

Where [dx, dy, dz] is the translation vector.

The effect of calling this function is to concatenate the translation matrix defined by the parameters [dx, dy, dz] to the global model view matrix:

Mmodelview = Mmodelview * T(dx, dy, dz);

Where T(dx, dy, dz) =

In general, a new transformation matrix is always concatenated to the global matrix from the right. This is often called post-multiplication.

#### Rotation:

`glRotate*(angle, x, y, z)`

Where angle is the angle of counterclockwise rotation in degrees, and x, y and z define a vector(originating at the origin) to rotate about.

Typically we rotate about only one of the major axes, which simplifies x, y and z to be a unit vector.

For example, if we want to rotate about the x-axis, then x=1, y=0, and z=0.

The effect of calling a rotation matrix is similar to translation. For example, the function call:

`glRotatef(a, 1, 0, 0);`

will have the following effect:

Mmodelview = Mmodelview * Rx(a);

Where Rx(a) denotes the rotation matrix about the x-axis for degree a: Rx(a) =

Rotations about the y-axis or z-axis can be achieved respectively by functions calls:

```glRotatef(a, 0, 1, 0); // rotation about the y-axis
glRotatef(a, 0, 0, 1); // rotation about the z-axis```

#### Scaling

`glScale*(sx, sy, sz);`

where sx, sy and sz are the scaling factors along each axis with respect to the local coordinate system of the model. The scaling transformation allows a transformation matrix to change the dimensions of an object by shrinking or stretching along the major axes centered on the origin.

Example: to make the wire cube three times as high, we can stretch it along the y-axis by a factor of 3 by using the following commands.

```     // make the y dimension 3 times larger
glScalef(1, 3, 1);
// draw the cube
glutWireCube(1); ```
• It should be noted that the scaling is always about the origin along each dimension with the respective scaling factors.
• The effect of calling this function to the global model view matrix is similar to translation and rotation.

### C. The Order of Transformations

• In OpenGL, the order in which the transformations are applied is the opposite of the order in which they appear in the program. In order words, the last transformation specified is the first one applied. This is due to the post-multiplication described above. This property can be best illustrated by the following examples.
• The initial default position for the camera is at the origin, and the lens is looking into the negative z direction.
• The primitive object models provided by OpenGL, such as cube or sphere, are also defined at the origin with a unit size by default.
• The purpose of model view transformation is to allow a user to re-orient and re-size these objects and place them at any desired location.

Example: Suppose we want to rotate a cube 30 degrees and place it 5 units away from the camera for drawing. You might write the program intuitively as below:

```    // first rotate about the x axis by 30 degrees
glRotatef(30, 1, 0, 0);
// then translate back 5
glTranslatef(0, 0, -5);
// use the GLUT library's cube routine to draw
// a  cube centered(by default) at the origin```

The following figure shows that:

If you run this program, you might be surprised to find that nothing appears in the picture! Think about WHY.

If we modify the program slightly as below:

```    // first translate back 5
glTranslatef(0, 0, -5);
// then rotate about the x axis by 30 degrees
glRotatef(30, 1, 0, 0);
// use the GLUT library's cube routine to draw
// a  cube centered(by default) at the origin```

The following figure shows that:

### D. Modeling Transformation vs. Viewing Transformation

• OpenGL uses concepts of a modeling transformation and a viewing transformation.
• The modeling transformation is the product of the calculations for creating and laying out your model (making sure everything is correctly positioned and oriented relative to everything else in the model). The transformation functions glScale*(), glRotate*() and glTranslate*() can be used to alter the modeling matrix.
• The viewing transformation is the calculations for viewing the model (positioning the viewpoint so that you view the model from the orientation and position your desire). You could also use the combination of glScale*(), glRotate*() and glTranslate*() for viewing transformations. The following discussion explains how this approach works. However, it involves the concepts of local and global coordinates and could be very confusing to some students. I would like to suggest students to skip this part first (as I labeled it OPTIONAL), and proceed with the easy approach, gluLookAt(), discussed next.
OPTIONAL
• First let's look at the following code:
```    glTranslatef(0, 0, -5);
glRotatef(30, 0, 1, 0);
DrawTheModel();```

Using the local coordinate, we first move the local coordinate's origin down the negative z-axis by 5 units and then rotate that coordinate system about y-axis by 30 degrees.

Using the global coordinate, we first rotate the global coordinate system about its origin by -30 degrees, then move the global coordinate system's origin down the positive z-axis by 5 units. The model is fixed, but the global coordinate system is rotated and translated. The viewpoint locates at the origin of the global coordinate system. Remember that in the global coordinate approach, the order is reversed, and the orientation order is also reversed.

The following is the local approach to a rotation:

The following is the global approach to a rotation:

The gluLookAt() Function: define a viewing transformation

```void gluLookAt( GLdouble eyex, GLdouble eyey, GLdouble eyez,
GLdouble centerx, GLdouble centery, GLdouble centerz,
GLdouble upx, GLdouble upy, GLdouble upz )```
Parameters
```  eyex, eyey, eyez: specifies the position of the eye point;
centerx, centery, centerz: specifies the position of the reference point;
upx, upy, upz: specifies the direction of the up vector.```

The gluLookAt() function makes it easy to move both the "from" and the "to" points in a linear manner. For example, if you need to pan along the wall of a building located away from the origin and aligned along no axes in particular, you could simply take the "to" point to be one corner of the building and calculating the "from" as a constant distance from the "to" point. To pan along the building, just vary the "to" point.

• The Components of the Modeling Matrix: All this confusion over modeling and viewing transformation stems from the fact that OpenGL use one matrix to represent all the transformation matrices, both modeling and viewing, for everything that is drawn. The matrix is called the Modelview matrix. The transformation used to describe and the model used to describe the viewpoint's location and orientation coexist in one matrix, called the Modelview matrix in OpenGL. This matrix results in a simpler set of calculations in the graphics pipeline. Matrix multiplication is not commutative but rather associative, which means that the product of ((AB)C) is the same as (A(BC)). Thus OpenGL's Modelview matrix is logically the product of a viewing matrix and a modeling matrix.

Mmodelview = Mviewing * Mmodeling

What this means is that your viewing transformations must be entered into the Modelview matrix before modeling transformations.

### E. Manipulating the Matrix Directly

• Whichever method you use, you will almost always use the three commands glLoadIdentity(), glPushMatrix() and glPopMatrix(). The first replaces the current matrix with the identity matrix, and the next two commands are used to push and pop the current matrix. Both of glPushMatrix() and glPopMatrix() are applied to temporarily store matrices on a stack. OpenGL has three matrix modes: one for the Modelview, one for the texture, and one for the projection. Generally the stack depth is at least 32 for the Modelview matrix. Pushing a matrix pushes the current matrix stack down and duplicates the current matrix. Popping a matrix replaces the current matrix with the matrix on the top of the stack and removes that matrix from the stack.

Three OpenGL commands are provided to directly manipulate the current matrix: glLoadIdentity(), glMultMatrix*() and glLoadMatrix*(). The glMultMatrix*() is used to perform matrix multiplication. glMultMatrix*(m) multiplies the current matrix with the one specified in m. That is, if M is the current matrix and T is the matrix passed to glMultMatrix, then M is replaced with MT. glLoadMatrix*(m) replaces the current matrix with the one specified in m. In the array m, the elements are stored in column order, whereas arrays in C or C++ are stored in row order. Do not be confused about these.

### F. Viewport and Projection Transformations

• Once you have learned Modelview transformations, the next step is to understand projection modes and the Projection matrix.
• OpenGL has three matrices, any one of which may be the current active matrix.
• The function glMatrixMode() takes one of three enumerated parameters to select the Modelview matrix, the Projection matrix, or the Texture matrix.

#### Viewport Transformation

For viewport transformations, glViewport() takes four parameters, which are used to specify the lower-left corner coordinates and the width and height of the viewport. It is best to call it only once you know how big the window is. That means it should be in your resize function.

#### Projection Transformation

OpenGL provides two methods of converting 3D images into 2D ones.

• The first is orthographic, or parallel projection. You use this style of projection to maintain the scale of objects and their angles without regard to their apparent distance.
• The second is Perspective projection. This is the most popular choice in 3D graphics. OpenGL's perspective projection is created by a couple of commands, glFrustum() and gluPerspective.

Projection is handled by the GL_PROJECTION matrix. Before you modify what projection you are using you must switch to projection matrix mode. When you are done it is advisable to switch back to GL_MODELVIEW:

```    glMatrixMode(GL_PROJECTION);

//Set up projectyion

glMatrixMode(GL_MODELVIEW);
```

glFrustum()

```  void glFrustum( GLdouble left, GLdouble right, GLdouble bottom,
GLdouble top, GLdouble near, GLdouble far )

Parameters:
left, right:
Specify the coordinates for the left and right vertical
clipping planes;
bottom, top:
Specify the coordinates for the bottom and top horizontal
clipping planes;
near, far:
Specify the distances to the near and far depth
clipping planes.  Both distances must be positive.  ```

glFrustum() describes a perspective matrix that produces a perspective projection. (left, bottom, -near) and (right, top, -near) specify the points on the near clipping plane that are mapped to the lower left and upper right corners of the window, respectively, assuming that the eye is located at (0, 0, 0). -far specifies the location of the far clipping plane. Both near and far must be positive.

The following shows perspective viewing volume and the glFrustum() parameters

gluPerspective()

Although glFrustum() is powerful, it is not very intuitive. GLU provides a much simpler perspective command, called gluPerspective(). Like glFrustum() it generates a perspective viewing volume but in a much simpler way.

```  void gluPerspective( GLdouble fovy, GLdouble aspect,
GLdouble zNear, GLdouble zFar )

Parameters:
Fovy:

Specifies the field of view angle, in degrees, in the y
direction;

Aspect:

Specifies the aspect ratio that determines the field of view in
the x direction.  The aspect ratio is the ratio of x (width) to
y (height);

ZNear:

Specifies the distance from the viewer to the near clipping
plane (always positive);

ZFar:

Specifies the distance from the viewer to the far clipping
plane (always positive).

```

gluPerspective() specifies a viewing frustum into the world coordinate system. In general, the aspect ratio in gluPerspective should match the aspect ratio of the associated viewport. For example, aspect=2.0 means the viewer's angle of view is twice as wide in x as it is in y. If the viewport is twice as wide as it is tall, it displays the image without distortion.

The following shows perspective viewing volume and the gluPerspective() parameters

## Assignment

#### Goals of this assignment:

• Viewing Transformations: through gluLookAt
• Projection Transformations: through glFrustum, glPerspective, and glOrtho
• Modeling Transformations: rotation, translation, scaling, glPushMatrix, and glPopMatrix

### Part 1

Start with this code: boxes.cpp. As written, this program draws a basic coordinate system with a green x-axis, a red y-axis, and a blue z-axis.
With the initial camera settings you are looking directly down the z-axis so you will not see it.

Make the following changes. Write your answers to the questions in steps 1, 2, 4 and 11.

1. Comment out the gluLookAt() call and replace it with the glTranlatef() with parameters (0.0,0.0,-5.0). Is there any change in the display? Why? Why not?
2. Comment out both the gluLookAt() and glTranslatef() lines. What happens? Why?
3. Restore the gluLookAt() call.
4. Comment out the glFrustum() call and replace it with an equivalent gluPerspective() call. You may have to do some trigonometry to figure out the field of view. You should already know how to calculate the aspect ratio.
1. What is the original field of view angle? What happens when you change it to 30.0? Why?
2. The original aspect ratio is 1.0. What happens when you modify the aspect ratio to values higher or lower than 1.0?
5. Restore the original glFrustum() call. (Commenting out the gluPerspective())
6. Use a new color to draw a wirecube whose center is at (0, 0, 0). You can use glutWireCube(1.0). Add this to the "display" function.
7. Move this cube so that it is centered at (1, 0, 0).
8. Draw a second cube after the first, and rotate it 45 degrees around the y-axis.
9. Place this rotated cube directly above the first cube. It will be centered at (1, 1, 0). Be careful of the order of transformations.
10. The perspective view makes the two cubes look a little awkward. Try using orthographic projection instead of the glFrustum call. The function for that is: glOrtho (the arguments are similar to glFrustum, but you may want to send it larger left, right, bottom, and top values). See the picture for expected results:

Please leave commented gluPerspective and glFrustum calls in your program.
11. Rotate everything (using modeling transformations NOT gluLookAt) so that you are looking down at the top of the boxes and seeing the blue z-axis (and no red y-axis). See the picture for expected results:

If you wanted to leave your x and y axes unchanged, but still see the top of the boxes, like this:

how would you change your code?
12. Rotate everything so that you can see all three axes along with the two cubes. See the picture for expected results:

You may use different angles of course.

/5 marks

### Part 2

Click here for some code that draws a robot arm.

1. First build the program and see how it works. Try pressing lower and uppercase 'e' to move the elbow. Try pressing lower and uppercase 's' to move the shoulder
2. Now, add three fingers and a thumb to the robot.
Use glPushMatrix(); and glPopMatrix(); calls to separate the transformations for each digit. Do not attempt to "untransform" with an inverse rotate, translate or scale.
3. Finally, add some code that will make the finger and thumb move apart when 'f' is pressed and and together when 'F' is pressed. The center of rotation should be at the wrist.
Your completed robot hand might look something like the following.

4. BONUS: Add additional and separate controls for each finger. You may provide controls for knuckles if you wish.
5. BONUS: Use solid shapes, an appropriate projection matrix, basic lighting and the depth buffer to make your robot hand easier to look at.

/5 marks

### Deliverables

• Part 1
• A working version of the program. A single .cpp file will suffice.
• Document with written answers for the questions in Steps 1, 2, 4, and 11.
• Part 2
• A working version of the robot arm program. A single .cpp file will suffice.

## On-Line References

• Robot Arm Exercise: Neider, Jackie (1997) OpenGL Redbook, Chapter 3, USA: A-W Developers Press.
• Selected pictures: Fosner, Ron (1997) OpenGL Programming for Windows 95 and Windows NT, USA: Addison-Wesley books.