CS409/CS809 Tutorial 8: Calculating Up Vectors
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.
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
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.
Alternatively, we could use an assert statement to stop the program immediately.
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.
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:
// 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
Vector3 local_up = local_forward.getRotatedArbitrary(axis, HALF_PI);
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.
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
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.
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:
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:
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:
30. You should be able to fly anywhere and then press [U] to have the camera smoothly rotate back to upright.
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.