CS56 Computer Animation: Lab 4

In which, euler angles and rotation matrices become part of our 3D programming skillset

The goals of this lab are to


Get the source


This assignment has been added to your AnimationToolkit repository.

> cd cs56
> cd AnimationToolkit
> cd build
> cmake ..
> make 
> ../bin/a4-teapots

You should now have a new directory under assignments called a4-euler.


Assignment 4: You spin me round!

Due October 4


In this assignment, you will implement conversions between euler angles and matrices. To help test your conversions, you have been given a grid of spinning teapots as well as a console program for unit testing.

In the teapot viewer, you should see a grid of teapots. The UI on the top left allows you to enter test values. Each teapot tests a conversion function. This week, you will implement 6 of the teapots to match the top right. Next week, you will implement the remaining two teapots.


To Get Started: Read the Euler angle conversion notes



Question 0 (20 points): Converting between ZYX euler angles and matrices


Your basecode this week includes a Jupyter notebook which uses the SymPy python library to compute symbolic expressions for all 6 euler angle matrices. In class, we talked about how to go between ZYX euler angles and a matrix. Let's check our understanding of this process here.

Aside: Running the Jupyter Notebook You do not need to run the Jupyter notebook for your assignment this week. Instead, use the compiled output here. However, if you would like the run it, you can download it from here. To run, execute the following from the command line. This will open a page in your browser that will allow you to edit the notebook.

>jupyter notebook

Euler angles describe rotations as a sequence of rotations around each axis. Any sequence is valid: we can rotate first around X, then Y, then Z; or we can rotate first around Y, then X, and then Z.

In our notebook, we define rotation matrices for each of these rotations like so

# Define symbols
cx,sx = symbols('cx sx')
cy,sy = symbols('cy sy')
cz,sz = symbols('cz sz')

Rx = Matrix([ 
         [1, 0, 0], 
         [0, cx,-sx], 
         [0, sx, cx]])

Ry = Matrix([
         [ cy, 0, sy], 
         [ 0,  1, 0], 
         [-sy, 0, cy]])

Rz = Matrix([ 
         [cz, -sz, 0], 
         [sz,  cz, 0], 
         [0,    0, 1]]) 

Once we have each rotation matrix, we can compute the matrix corresponding to XYZ euler angles like so.

print("Rxyz")
pprint(Rx * Ry * Rz)

Rxyz
|      cy⋅cz             -cy⋅sz         sy  |
|                                           |
|cx⋅sz + cz⋅sx⋅sy   cx⋅cz - sx⋅sy⋅sz  -cy⋅sx|
|                                           |
|-cx⋅cz⋅sy + sx⋅sz  cx⋅sy⋅sz + cz⋅sx  cx⋅cy |

Let's take a look at this matrix and answers several questions about it. Please put the answers to the following questions in your readme for this week.

Q1: What is an orthogonal matrix? Show that

If you have shown that more columns have unit length or are perpendicular to the other two, you will receive extra credit.

Q2: What matrix element would be the easiest to get an angle back?

Q3: Recall that tan = sin/cos. How could we use this to get the other angles back?

Q4: What happens when the rotation around the Y axis is 90 degrees?

Q5: If the Y rotation is 90 degrees, how can we get the X and Z values back?

Q6: Show that when the rotation around Y is 90 degrees, the values for X and Z are not unique.

Q7: Consider the following matrix. What does each column represent?

|0   -cx⋅sz + cz⋅sx  cx⋅cz + sx⋅sz|
|0   cx⋅cz + sx⋅sz   cx⋅sz - cz⋅sx|
|-1        0               0      |

Q8: Consider the following matrix. Suppose we want to use this matrix to convert a character's local axes to world coordinates. How do we do this?

|0   -cx⋅sz + cz⋅sx  cx⋅cz + sx⋅sz|
|0   cx⋅cz + sx⋅sz   cx⋅sz - cz⋅sx|
|-1        0               0      |

Q9: Suppose we tried to extract euler angles from a matrix that was not orthogonal. What are some problems we would encounter?

Q10: What is the inverse of the rotation matrix corresponding to XYZ euler angles, in terms of Rx, Ry, and Rz matrices?


(12 points) Implement the conversions from euler angles to a matrix


To start, implement the function AMatrix3::fromEulerAnglesZYX(). You can find this function in libsrc/animation/AMatrix3-basecode.cpp. This function takes a parameter called angleRad which stores euler angles as a 3-tuple

To implement this function, you can take two approaches

AMatrix3 supports two types of syntax for accessing elements: indexing or by element name. For example,

std::cout << mM[0][0] << std::endl; // indexing notation
std::cout << m11 << std::endl; // lecture matrix notation
mM[row][col] = 0.4;
m11 = 3;
  1. (2 points) Implement AMatrix3::fromEulerAnglesZYX()
  2. (2 points) Implement AMatrix3::fromEulerAnglesXYZ()
  3. (2 points) Implement AMatrix3::fromEulerAnglesXZY()
  4. (2 points) Implement AMatrix3::fromEulerAnglesYZX()
  5. (2 points) Implement AMatrix3::fromEulerAnglesYXZ()
  6. (2 points) Implement AMatrix3::fromEulerAnglesZXY()

To run the unit tests from the build directory, type

build> ../bin/a4-testEuler 



(60 points) Implement the conversion from a matrix to euler angles


Using the formulations from notebook, extract the X, Y, and Z angles from the matrix. In your implementation, be carefull of edge cases around 90 degrees!

  1. (10 points) Implement AMatrix3::toEulerAnglesZYX()
  2. (10 points) Implement AMatrix3::toEulerAnglesXYZ()
  3. (10 points) Implement AMatrix3::toEulerAnglesXZY()
  4. (10 points) Implement AMatrix3::toEulerAnglesYZX()
  5. (10 points) Implement AMatrix3::toEulerAnglesYXZ()
  6. (10 points) Implement AMatrix3::toEulerAnglesZXY()

To run the teapots from the build directory, type

build> ../bin/a4-teapots 

To run the unit tests from the build directory, type

build> ../bin/a4-testEuler 


Extra Credit (Up to 4 points)


Option 1 Write unit tests to check your conversions (0.1 per unique test. A unique test should execute a different control path in each function). A simple test application could look like the following. Try to test edge cases too! Extend your CMakeLists.txt file to create a new test application. All you test code should be stored in assignments/a1-a2-rotations

void RunTest()
{
   // create a matrix from euler angles
   AMatrix3 m, m1;
   m.fromEulerAngles(angles); // note: you will need to set angles

   // convert from the matrix back to euler angles
   AVector3 test = m.toEulerAngles();

   // convert back to matrix again
   m1.fromEulerAngles(test);

   // Check whether m == m1 and print the result
}

void main(int argc, char** argv)
{
   RunTest();
}

Option 2 Now that your framework supports euler rotations, you can make new types of interesting demos! As in the last assignment, create an interesting 2D demo that rotates objects.

Option 3 Show that all the columns in Q1 have unit length and are orthogonal (1/3 each)


Submission Requirements (5 points)