**CS409/CS809 Tutorial 15: Transforming Coordinates**

1.
Start Microsoft Visual Studio 2015. Select File
/ New / Project…. Under the list on the left, choose Installed / Templates /
Visual C++. Then choose a Win32 Console Application. Change the name to Tutorial15
or some suitable name and change the location to a hard drive located on the
computer (typically C or D). You will need to remember this location for steps
3 and 5. Make sure the “Create folder for project” checkbox is __not__
checked and that the “Add to source control” is __not__ checked. Click OK.

· If the Chose Location window keeps popping up again, you aren’t allowed to save files at the location you chose. Select a different location, such as the desktop.

· Do not put your program on one of the external drives like H. The connection is slow, and your program may not compile correctly.

2.
Click Next> or Application Settings (they go
to the same place). Check the “Empty Project” box. Make sure the “Security
development lifecycle check” is __not__ checked. Click Finish.

3. Go to http://www2.cs.uregina.ca/~anima/409/Terms/201810/Tutorials/index.html and download the Tutorial15.zip and ObjLibrary.zip files to your project directory.

4. Unzip the 2 zip files. You will have a folder named ObjLibrary and 10 other files.

5. In Visual C++, go to Project/Add Existing Item… and select main15.cpp (and holding control, continue to select), Sleep.h, Sleep.cpp, and GetGlut.h.

6. Ensure that Solution Explorer is visible (View / Solution Explorer). In the Solution Explorer, select Tutorial15 (the project name) and right click it. Choose Add / New Filter. A new folder icon should appear in the Solution Explorer. Name it ObjLibrary. (This creates an imaginary folder in the project, but it would not create a Windows folder on the computer).

7. In the Solution Explorer, right click on the ObjLibrary folder icon and select Add / Existing item. Go into the ObjLibrary folder on Windows and select all files. The files should appear in the Solution Explorer inside the ObjLibrary folder. You should get 26 files, although we will not be using most of them for this tutorial.

8. Compile and run the original program. You should see 2 windows: a text console and a graphical window labeled “Transforming Coordinates”. The second window contains a set of XYZ axes, a checkerboard grid with one cell highlighted yellow, and a cyan marker above the highlighted cell. The arrow keys should move the marker and, with it, the highlight on the grid. Exit the windows. Closing the text window will close the graphical window, and usually vice versa.

· You may see a pop-up window that says "This project is out of date:". If so, click "Yes". This tells Visual Studio to compile your program before trying to run it.

9. For the remainder of the instructions, it is assumed that you compile and run regularly so that you can see what each change does.

*We will apply various transformations to
the grid as it is displayed. We will also change the calculations so that the
cell under the marker is still the one highlighted. In general the
calculations involve the opposite transformations in the same order.*

10. Add a global constant to represent the translation of the grid:

const Vector3 GRID_TRANSLATE(1.2,
0.0, 3.4);

11. In the display function, translate the grid according to this value:

glPushMatrix();

glTranslated(GRID_TRANSLATE.x, GRID_TRANSLATE.y, GRID_TRANSLATE.z);

drawGrid();

glPopMatrix();

The grid should now appear in a different place. The
most obvious effect of this is that the highlighted grid cell is now offset towards
you and to the right; it is no longer at the marker.

12. In the worldToGridCoordinates function, apply the reverse of the translation to the position.
This means translating it the opposite way:

position -=
GRID_TRANSLATE;

return position;

The highlighted grid cell should now be under the
marker again.

13. Add a function named radiansToDegrees to convert radians to
degrees:

double
radiansToDegrees (double radians)

{

return radians * 180 / 3.14159265359; // pi

}

Remember to add a function prototype:

double
radiansToDegrees (double radians);

If you are feeling ambitious, you could also add a
similar function to convert degrees to radians, although we will not need it
for this tutorial.

14. Add a global constant to represent the rotation of the grid around
the Y-axis:

const double
GRID_RADIANS_Y = 5.6;

15. In the display function, convert this value to degrees and rotate the grid around
the Y axis by the angle:

glPushMatrix();

glTranslated(GRID_TRANSLATE.x, GRID_TRANSLATE.y, GRID_TRANSLATE.z);

double grid_degrees_y = radiansToDegrees(GRID_RADIANS_Y);

glRotated(grid_degrees_y, 0.0, 1.0, 0.0);

drawGrid();

glPopMatrix();

The grid should now be displayed on an angle. The
highlighted cell will be in the wrong place and move in somewhat the wrong
direction (off by about 45 degrees) when you move the marker.

16. In the worldToGridCoordinates function, apply the reverse of the rotation to the position. This
means rotating it by the same angle around the same axis, but in the opposite direction:

position -=
GRID_TRANSLATE;

position.rotateY(-GRID_RADIANS_Y);

return position;

The highlighted grid cell should now be under the
marker again.

· Note that the Vector3 rotation functions assume that angles are in radians, while the glRotate* functions assumes that the angle is in degrees.

17. Add a global constant to represent the scaling of the grid:

const double
GRID_SCALE = 0.7;

18. In the display function, scale the grid by this value:

glPushMatrix();

glTranslated(GRID_TRANSLATE.x, GRID_TRANSLATE.y, GRID_TRANSLATE.z);

double grid_degrees_y = radiansToDegrees(GRID_RADIANS_Y);

glRotated(grid_degrees_y, 0.0, 1.0, 0.0);

glScaled(GRID_SCALE, GRID_SCALE, GRID_SCALE);

drawGrid();

glPopMatrix();

The grid should now appear to be smaller. The
highlighted cell will be in the wrong place and move too slowly.

19. In the worldToGridCoordinates function, apply the reverse of the scaling to the position. This
means diving the position by the scaling factor:

position -=
GRID_TRANSLATE;

position.rotateY(-GRID_RADIANS_Y);

position /= GRID_SCALE;

return position;

The highlighted grid cell should now be under the
marker again.

20. Change the display function to only scale the grid along the X axis:

glScaled(GRID_SCALE,
1.0, 1.0);

The grid should now appear to be distorted. The
highlighted cell may be right or wrong, depending on where it is.

· Note that the Y and Z scaling factors are 1.0, not 0.0. A scaling factor of 0.0 will flatten the object; two of them will turn it into a line.

21. Change the worldToGridCoordinates function to only apply the scaling to the X component of the
position:

position.x /=
GRID_SCALE;

The highlighted grid cell should now be under the
marker again.

22. Rename GRID_SCALE to GRID_SCALE_X so that its name accurately describes its new function.

*We will determine the position in world
space corresponding to a position in local space. In some sense, this requires
solving the opposite problem to the one solved by all steps so far. Thus, this
transformation is the reverse of the one above. Given a position expressed in
grid coordinates, we will determine the corresponding world coordinates.*

* *

23. Add a function named gridToWorldCoordinates function to
convert a position in grid coordinates to world coordinates:

Vector3 gridToWorldCoordinates
(Vector3 position)

{

// add transformations here

return position;

}

Remember to add a function prototype:

Vector3 gridToWorldCoordinates
(Vector3 position);

24. Add code to the display function to display a purple, wireframe cube at the corner of the
grid:

glColor3d(1.0, 0.0,
1.0); // purple

Vector3 cube_grid_coordinates(-GRID_HALF_SIZE, 0.0, -GRID_HALF_SIZE);

Vector3 cube_world_coordinates = gridToWorldCoordinates(cube_grid_coordinates);

glPushMatrix();

glTranslated(cube_world_coordinates.x,

cube_world_coordinates.y,

cube_world_coordinates.z);

glutWireCube(0.5);

glPopMatrix();

The cube should appear in the top left corner of the
screen, where the corner of the untranslated grid was.

25. Add the transformations from the worldToGridCoordinates function to the gridToWorldCoordinates function. The transformations should be in reverse order and each
transformation should be inverted. For a translation, the inverse
transformation is a translation of the same distance in the opposite direction.
For a rotation, the inverse transformation is a rotation around the same axis,
but the sign on the angle is reversed. For scaling, the inverse transformation
is multiplying instead of dividing (and vice versa):

position.x *=
GRID_SCALE_X;

position.rotateY(GRID_RADIANS_Y);

position += GRID_TRANSLATE;

return position;

The wireframe cube should now appear at the corner of
the transformed grid. However, it is not rotated or scaled itself.

*We will apply multiple sets of
transformations to the grid. In practice, each set of transformations would
usually be applied by a different function.*

26. Add global constants to represent another set of transformations the
grid:

const Vector3
GRID_TRANSLATE2(0.0, 0.0, -0.8);

const double GRID_RADIANS_Y2 = 0.9;

const double GRID_SCALE2 = 0.25;

27. In the display function, apply the second set of translations to the grid after
the first set. The scaling should apply to all the dimensions. Give the new
transformations their own pair of commands to push/pop the matrices nested
inside the first pair:

glPushMatrix();

glTranslated(GRID_TRANSLATE.x, GRID_TRANSLATE.y, GRID_TRANSLATE.z);

double grid_degrees_y = radiansToDegrees(GRID_RADIANS_Y);

glRotated(grid_degrees_y, 0.0, 1.0, 0.0);

glScaled(GRID_SCALE_X, 1.0, 1.0);

glPushMatrix();

glTranslated(GRID_TRANSLATE2.x, GRID_TRANSLATE2.y, GRID_TRANSLATE2.z);

double grid_degrees_y2 = radiansToDegrees(GRID_RADIANS_Y2);

glRotated(grid_degrees_y2, 0.0, 1.0, 0.0);

glScaled(GRID_SCALE2, GRID_SCALE2, GRID_SCALE2);

drawGrid();

glPopMatrix();

glPopMatrix();

The grid should now appear much smaller and in the
bottom right. The highlighted cell and the wireframe cube are, predictably,
far from the marker.

· The grid may also appear to be twisted in 3D space. This is an optical illusion caused by scaling non-uniformly and then rotating. The grid is still really on the same XZ plane as the red and blue axes.

28. In the worldToGridCoordinates function, apply the inverse of the new transformations in the same
order as in display. These should be after the first set of transformations:

position -=
GRID_TRANSLATE;

position.rotateY(-GRID_RADIANS_Y);

position.x /= GRID_SCALE_X;

position -= GRID_TRANSLATE2;

position.rotateY(-GRID_RADIANS_Y2);

position /= GRID_SCALE2;

return position;

The grid highlight should now be on the cell under the
marker again.

29. In the gridToWorldCoordinates function, apply the new transformations in reverse order compared
to in worldToGridCoordinates. The new transformations should be first:

position *=
GRID_SCALE2;

position.rotateY(GRID_RADIANS_Y2);

position += GRID_TRANSLATE2;

position.x *= GRID_SCALE_X;

position.rotateY(GRID_RADIANS_Y);

position += GRID_TRANSLATE;

return position;

The wireframe cube should be at the top right corner of
the grid. The cube appears large because it is closer and the grid is smaller.

30. Apply the grid scaling from the first set of transformations to the X and Z dimensions, but not to the Y dimension. Change the name of the constant to something like GRID_SCALE_XZ to reflect its new purpose. You will need to update the display, gridToWorldCoordinates, and gridToWorldCoordinates functions.

31. Display three more cubes at the other three corners of the grid. They will all use the same gridToWorldCoordinates function.

· Suggestion: Convert your code to display a wireframe cube into a function takes grid coordinates (as a Vector3) as a parameter. Then call it four times to draw the cubes at the four corners.

32. Add a third set of transformations. Make sure that the highlighted cell and wireframe cubes display in the correct places.

· Suggestion: Scale the grid by a factor greater than 1.0 (e.g. 4.0) to make it larger for the next step.

33. Use world coordinates to display an orange wireframe cube (glutWireCube) at the center of the highlighted grid cell. The position of the marker is specified in world coordinates. First, determine the position of the marker in grid coordinates. Then calculate the position of the cell center in grid coordinates. Then convert that position to world coordinates. Finally draw a cube at the world coordinates position.

· Hint: In grid coordinates, each cell has a size of 1.0.