**CS409/CS809 Tutorial 6: Local Camera Coordinates**

1. Start Microsoft Visual Studio 2013. Select File / New / Project. Under “Project Type”, choose Templates / Visual C++. Then choose a Win32 Console Application. Change the name to Tutorial6 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 Tutorial6.zip and ObjLibrary.zip files to your project directory.

4. Unzip the 2 zip files. You will have a folder called ObjLibrary and 12 other files.

5. In Visual C++, go to Project/Add Existing Item… and select main6.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 Tutorial6 (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.

8. Compile and run the original program. You should see 2 windows: a text console and a graphical window labeled “Local Camera Coordinates”. There is a grid of coloured diamonds connected with lines across this second window. These coloured diamonds are part of a hollow cube around the camera, which will help us keep track of camera orientation. Exit the windows. Closing the text window will close the graphical window, and usually vice versa.

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.

10. We will be editing the file main.cpp. You do not need to change OrientationCube.h or OrientationCube.cpp.

11. Add an #include for "ObjLibrary/Vector3.h"

#include
"ObjLibrary/Vector3.h"

Add a using directive for ObjLibrary after the #includes:

using namespace
ObjLibrary;

12. Declare 3 global variable (before the main function) of the Vector3 type:

Vector3 forward( 0, 0,
-1);

Vector3 up ( 0, 1,
0);

Vector3 right ( 1, 0,
0);

This corresponds to the default OpenGL camera orientation.

13. In the display function, change
the call to gluLookAt to use the forward and up vectors for the look at point and up direction. We will create a
temporary variable to represent the point we want to look at.

look_at =
Vector3::ZERO + forward;

gluLookAt(0.0,
0.0, 0.0,

look_at.x, look_at.y, look_at.z,

up.x, up.y, up.z);

Here, Vector3::ZERO represents the origin. There should be no visible effect.

14. The right vector must be set to a specific value based on the forward
and up vectors. To calculate it automatically, change its declaration to:

Vector3 right = forward.crossProduct(up);

15. Try setting the forward and up vectors
to different values. The vectors must be part of an orthonormal basis, which
means they must have a norm (length) of 1.0 and be at right
angles to each other. If this condition is met, a correct value for right will
be calculated automatically. Changing the forward vector will
change the direction of the camera, and you will see octahedrons of different
colours. Changing the up vector will change the rotation of the camera, and thus where the
octahedrons are on the screen. Some possibilities to try are:

// same but upside
down

Vector3 forward( 0, 0, -1);

Vector3 up ( 0,
-1, 0);

// green octahedrons

Vector3 forward( 0, 1, 0);

Vector3 up ( 0,
0, 1);

// green octahedrons rotated 90 degrees

Vector3 forward( 0, 1, 0);

Vector3 up ( 1,
0, 0);

Setting the forward and up vectors to the
same axis will result in a plain grey window.

16. Change the forward and up vectors back to their original values:

Vector3 forward( 0,
0, -1);

Vector3 up ( 0,
1, 0);

17. In the special function under the GLUT_KEY_LEFT case in the switch statement, add a command to turn the camera to the local
"left" by rotating it. In order to rotate the camera around the up vector,
so we will need to update the forward and right vectors. They should each be rotated around the up vector
by an angle of 0.1 radians:

forward.rotateArbitrary(up,
0.1);

right .rotateArbitrary(up,
0.1);

18. Add similar commands under the GLUT_KEY_RIGHT case to rotate the camera the to the right. You will need one crucial change.

19. Add commands under the GLUT_KEY_UP case to rotate the
camera vertically. For this, we will have to rotate the forward
and up vectors around the right vector:

forward.rotateArbitrary(right,
0.1);

up .rotateArbitrary(right,
0.1);

20. Add similar commands under the GLUT_KEY_DOWN case to rotate the camera the down. See if you can rotate the camera to look at about the same place it started, but rotated approximately 90 degrees. You will not be able to rotate it exactly because 0.1 radians does not divide evenly into pi.

21. Declare another global variable of the Vector3
type to represent the camera's position in space:

Vector3 position(0,
0, 0);

22. Update the look_at variable in the display function. The camera will move, and we always want to look at a
position in front of it. Such a position can be calculated as the sum of position
and forward.

Vector3 look_at =
position + forward;

23. Modify the call to gluLookAt to place
the camera at position:

gluLookAt(position.x,
position.y, position.z,

look_at.x, look_at.y, look_at.z,

up.x, up.y, up.z);

24. In the keyboard function, add a case in the switch statement to move the camera in the forward direction when the
spacebar is pressed.

case ' ':

position += forward * 0.5;

break;

25. If the camera gets outside of the orientation cube, the user can get
lost. To fix this, we will add a command to turn the camera to face the
origin. In the keyboard function, add a case to the switch statement for the [H] key. The case should start by calculating a vector from the
camera's current position to the origin.

case 'h':

direction_to_origin
= origin - position;

break;

Declare the origin and direction_to_origin variable outside the switch statement:

Vector3 origin =
Vector3::ZERO;

Vector3 direction_to_origin;

just before the the switch(special_key) statement. Compilers sometimes generate errors if variables are
declared inside case statements.

26. Add a helper function named rotateCameraToVector.
It should take a vector representing the target facing direction as a
parameter. The purpose of this function is to rotate the camera so that the forward
vector is lined up with the target vector.

void rotateCameraToVector(const
Vector3& target_facing)

{

// code will go here

}

Include a function prototype with the other prototypes near the top of the
file.

void
rotateCameraToVector(const Vector3& target_facing);

27. If the target_facing vector is the zero vector, we do not have a direction, so we don't
know which way to turn. In that case, we will just do nothing.

if(target_facing.isZero())

return;

28. Calculate the axis to rotate the camera around. The needs to be at
right angles to the camera's current forward vector and to the target_facing vector. We can normally compute it as a cross product:

Vector3 axis =
forward.crossProduct(target_facing);

29. The special cases occur if the forward vector is
pointing exactly the same way or exactly the opposite way as the target_facing vector. If that happens, there are an infinite number of
equally-good axes, all at right angles to both vectors. The formula represent
this by setting axis to the zero vector. We don't care which axis to use in that case,
so we will just use the up vector, which will always be a possible axis when forward
and target_facing are parallel..

if(axis.isZero())

axis = up;

30. If, on the other hand, we did find an axis, we need to convert it to
a normal vector, i.e. a vector of length 1.0:

else

axis.normalize();

A normal vector is also called a unit vector.

31. Calculate the angle in radians to rotate the camera by. This can be
done using the getAngleSafe Vector3 function (which uses the dot product):

double radians =
forward.getAngleSafe(target_facing);

The Safe variant of Vector3 functions will return a default value (1.0 here) if one of
the vector parameters is a zero vector. It is also slightly slower than the
non-safe version.

32. Now that we have our axis and our angle, we will rotate all three of
our camera vectors around it:

forward.rotateArbitrary(axis,
radians);

up
.rotateArbitrary(axis, radians);

right .rotateArbitrary(axis, radians);

33. On the [H] key, call the rotateCameraToVector function with direction_to_origin as the paremter:

rotateCameraToVector(direction_to_origin);

34. You should be able to fly anywhere and then press [H] to be instantly facing the origin.

35. Add a double parameter to the rotateCameraToVector function to represent the maximum angle in radians to rotate in one
frame:

void
rotateCameraToVector(const Vector3& target_facing,

double max_radians)

Remember to update the prototype:

void
rotateCameraToVector(const Vector3& target_facing,

double max_radians);

Call the function with 0.1 radians as the maximum rotation angle:

rotateCameraToVector(direction_to_origin, 0.1);

36. Change the code that rotates the camera to rotate by an angle of max_radians instead radians. Save the code that radians; we will need it later.

forward.rotateArbitrary(axis,
max_radians);

up
.rotateArbitrary(axis, max_radians);

right .rotateArbitrary(axis, max_radians);

37. Test the program. When you hold [H], the camera should smoothly turn toward the origin and then flicker back and forth. This is because the camera is already facing very close to the target facing direction. Each frame it turns by max_radians, which is too far, so it ends up past the target facing direction. The next frame it turns back, only to turn too far again.

38. The solution here is to use the smaller of radians and max_radians, and the simpllest way to do this is to set one value to the
smaller of the two. Add a check just before you rotate the camera vectors to
ensure that radians is not larger than max_radians:

if(radians >
max_radians)

radians =
max_radians;

39. Change the camera rotations to rotate by the newly-modified radians
value:

forward.rotateArbitrary(axis,
radians);

up
.rotateArbitrary(axis, radians);

right .rotateArbitrary(axis, radians);

40. Holding [H] should now smoothly rotate the camera to face the origin.

41. Add commands to the keyboard function to make the camera roll while facing the same direction, allowing the user to flip the camera onto its side or upside-down. This requires rotating the up and right vectors around the forward vector. Use the [,] and [.] keys.

42. Add a command to the special function to restore the camera to its starting position and orientation. Use the [END] key, which uses a constant named GLUT_KEY_END.

43. Add a command to move the camera backwards on the [/] key.

44. Add commands to strafe the camera left and right. This means moving it sideways without turning it. Use the [A] and [D] keys for left and right.

45. Add commands to strafe the camera up and down on the [W] and [S] keys.

46. Add a red sphere to the world somewhere outside the colour cube. Make the camera rotate to face the red sphere on the [R] key.

47. Convert your camera to a class named something like CoordinateSystem. The global vectors will become member variables. Add constructors and functions to use the class. Modify your program to use this new class. A possible list of functions would be:

· Default constructor

· Initializing constructor

· Copy constructor

· Destructor

· Assignment operator

· const Vector3& getPosition () const

· const Vector3& getForward () const

· const Vector3& getUp () const

· Vector3 getRight () const

· Void setupCamera () const

· void init (const Vector3& position, const Vector3& forward, const Vector3& up)

· void setPosition (const Vector3& position)

· void moveForward (double distance)

· void moveUp (double distance)

· void moveRight (double distance)

· void setOrientation (const Vector3& forward, const Vector3& up)

· void rotateAroundForward (double radians)

· void rotateAroundUp (double radians)

· void rotateAroundRight (double radians)

· void rotateToVector (const Vector3& target_forward, double max_radians)

48. As noted above, the forward, up, and right vectors form an orthonormal basis for the coordinate system. One
implication of this is that they are always at right angles to each other, and
any one can be calculated from the other two. We need both the forward
and up vectors to set the camera, but the right vector is only
needed for a few calculations (tilting up and down and strafing right and
left). The value of the right vector can always be calculated as:

right = forward.crossProduct(up);

Remove the right vector from your class. When rotating around the right axis (up
and down arrows), you will need to calculate a temporary right
vector to rotate around. Otherwise, the forward and up vectors
will rotate around different axes.

**Warning:** It is very important that your getRight function
returns its result by value, not by constant reference. If it returns the
result by constant reference, the result (which is a local variable) may be
overridden by a different value before it is used. If this happens, it can
cause very insidious bugs.