CS409/CS809 Tutorial 8: Calculating Up Vectors

This tutorial will show you how to automatically calculate and use up vectors for a local camera coordinate system.  As before, the demonstration will use the ObjLibrary to handle vectors.

The purpose of this tutorial is to allow you to create a complete coordinate system for an object if you know only the position and forward vector of the object.

This tutorial assumes that you have a completed copy of Tutorial 6 or 7.  The class developed in the exercises is assumed to be named CoordinateSystem and to be in a header and source file of the same name.  This tutorial assumes that your class does not have a variable for the right vector, but it should still work fine if it does.

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 Tutorial8 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.      Copy all your Tutorial 6 (or Tutorial 7) source files, header files, and files with names starting with glut or freeglut to your new project directory.  Rename the main6.cpp (or main7.cpp) file to main8.cpp.

4.      In Visual C++, go to Project/Add Existing Item… and change the drop-down box at the right that says All Files to choose Visual C++ Files.  There should be at least 12 files remaining (not counting folders), including main8.cpp.  Select them all and click Add.

·         Make sure that you add your copies of the files, not the original versions in your Tutorial 6 (or Tutorial 7).  If you add the wrong ones, things will get confusing very quickly and you will probably end up having to redo both tutorials.

5.      Ensure that Solution Explorer is visible (View / Solution Explorer). In the Solution Explorer, select Tutorial8 (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).

6.      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 you will not be using most of them for this tutorial.

7.      Compile and run the program.  You should see 2 windows: a text console and a graphical window.  The graphical window should contain a copy of your Tutorial 6 (or Tutorial 7).  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.

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

9.      We will be editing the file main8.cpp, CoordinateSystem.h, and CoordinateSystem.cpp.  You do not need to change OrientationCube.h or OrientationCube.cpp.

Creating a Coordinate System from Only a Forward Vector

10.  Add a helper function to the CoordinateSystem class named calculateUpVector.  It should take a vector representing the forward direction as a parameter and return an up vector.  There are an infinite number of possible up vectors for any given forward vector, and the purpose of this function is to consistently return the same one.  This function should be declared as private and const (it could be static instead).
Vector3 CoordinateSystem::calculateUpVector(const Vector3& local_forward) const
{
  // code will go here
}

Include a function prototype in the class header file.
  Vector3 calculateUpVector(const Vector3& local_forward) const;

11.  Add a constant local variable to the function to represent the ideal up vector.  We will always choose the closest possible up vector to this constant.  This will ensure that the same forward vector always gives the same up vector.
  static const Vector3 IDEAL_UP_VECTOR(0.0, 1.0, 0.0);
Always remember to declare local constants as static.  Otherwise, a new copy of will be created each time the function is called.

12.  If the forward vector is the zero vector, we have a problem somewhere else in the program.  In that case, we will just return our ideal up vector.
  if(local_forward.isZero())
       return IDEAL_UP_VECTOR;
Alternatively, we could use an assert statement to stop the program immediately.
  assert(!local_forward.isZero());

13.  Our goal now is to calculate the local up vector by rotating the local forward vector 90 degrees towards the ideal up vector.  Sometimes this will rotate it too far and sometimes not far enough to exactly match the ideal up vector, but it will always give the local up vector closest to the ideal up vector. We will accomplish this goal in the next few steps.

14.  As when we rotated the coordinate system in Tutorial 6, the rotation axis is the cross product of the vector we are starting with and the vector we are rotating towards.
  Vector3 axis = local_forward.crossProduct(IDEAL_UP_VECTOR);

15.  As in Tutorial 6, we have to deal with the special cases where the forward vector and ideal up vectors are parallel.  There will be an infinite number of possible up vectors, and we will just hard-code in a vector that we know is perpendicular to the ideal up vector.
  if(axis.isZero())
       return Vector3(1.0, 0.0, 0.0);

16.  If we did find an axis to rotate around, we must convert it to a normal vector, i.e., a unit vector:
  else
  {
       axis.normalize();
       // more code here
  }

17.  Since we want to rotate by 90 degrees, we will need the equivilent value in radians, which is half of pi.  It will be neatest if we add another local constant to our function with the first one:
  static const double HALF_PI = 1.5707963267948966;

18.  Now, at last, we are ready to calculate the local up vector.  We will rotate the forward vector half-pi radians around the axis and return the result.
       Vector3 local_up = local_forward.getRotatedArbitrary(axis, HALF_PI);
       return local_up;

Our
calculateUpVector function is now finished.

19.  Add a function named setOrientation to the CoordinateSystem class that takes a forward vector as a parameter.  It should use the calculateUpVector function to calculate the local up vector.  It should set the values of the forward and up member variables in the CoordinateSystem class. You may already have a function with the same name that takes 2 vectors as parameters from Tutorial 6. The new function will only take one parameter.

20.  On the [T] key, call the setOrientation function on the camera coordinate system with Vector3::getRandomUnitVector() as the parameter.  This will make the camera face in a random direction.  Since the IDEAL_UP_VECTOR is pointing roughly to the green, you should find that the green, cyan, yellow, and white side of the colour cube is roughly above you regardless of the random direction chosen.

Smoothly Rolling the Camera Upright

21.  Rolling a coordinate system refers to rotating it around its local forward vector.  This will change which direction is up without changing which direction is forward.

22.  Add a public member function to the CoordinateSystem class named rotateUpright.  It should take a maximum angle in radians as a parameter.  It will eventually resemble the rotateToVector function from Tutorial 6.
void CoordinateSystem::rotateUpright(double max_radians)
{
  // code will go here
}

Include a function prototype in the class header file.
  void rotateUpright(double max_radians);

23.  The first thing we will need is the desired direction for the up vector.  We can calulate this using the calculateUpVector function:
  Vector3 desired_up = calculateUpVector(forward);

24.  Next we need to calculate the axis to rotate around.  This will either be the forward axis or the inverse of the forward axis, depending on whether the best rotation is clockwise or counter-clockwise.  As usual, we will calculate the axis as a cross product:
  Vector3 axis = up.crossProduct(desired_up);

25.  There are the usual special cases where the current and.desired up vectors are parallel.  In these cases, the forward vector will always work, so we will just use that.
  if(axis.isZero())
       axis = forward;

26.  If, on the other hand, we did find an axis, we need to convert it to a normal vector, i.e. a unit vector:
  else
       axis.normalize();

27.  Calculate the angle in radians to rotate the coordinate system by.  Once we have that, we will have to limit it to be less than or equal to max_radians.
  double radians = up.getAngleSafe(desired_up);
  if(radians > max_radians)
       radians = max_radians;

28.  Now that we have our axis and our angle, we will rotate our local coordinate system around it:
  up.rotateArbitrary(axis, radians);
The forward vector will always be parallel to the axis of rotation, so we will not need to rotate that.  If your coordinate system includes a right vector, that will also need to be rotated.

29.  On the [U] key, call the rotateUpright function with 0.1 as the maximum rotation angle:
  case 'u':
       camera.rotateUpright(0.1);
       break;

30.  You should be able to fly anywhere and then press [U] to have the camera smoothly rotate back to upright.

Exercises

31.  Add another constructor to the CoordinateSystem class that takes a postion and a forward vector as parameters.  It should calculate an up vector automatically.

32.  Add a function to the CoordinateSystem class to choose a random up vector without changing the current forward vector.  This function could be used to roll the camera by a random amount. The easiest way to do this is by rotating it around its forward axis by a random angle between 0.0 and twice pi.  You can calculate a random number in the range [0, 1) as:
double random0to1 = rand() / (RAND_MAX + 1.0);
Run this function for the camera on the
[Y] key.

33.  If you have also completed Tutorial 7, initialize each teapot to have a random orientation.  Use the Vector3::getRandomUnitVector function to generate a random forward vector.  Then assign each teapot a random up vector.  The teapots should be a mix of all possible rotations.