CS409/CS809 Tutorial 15: Transforming Coordinates

This tutorial will show you how to determine where a world position is in the local coordinates system for an object.  We will only use the horizontal coordinates.

Setting up an OpenGL Project with the Obj Library of Files

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.

Translating the Coordinates

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.

Rotating the Coordinates

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.

Scaling the Coordinates

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.

Transforming from Local Coordinate to World Coordinates

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.

Nested Transformations

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.

Exercises

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.