Description
Introduction
In this assignment, you will be working with data from the CMU Motion Capture Database.
The data we give you will only be in the form of text files, but with your programming and
math skills, you will bring this data to life!
The CMU motion capture data is typical of all the skeleton-based motion capture data you’ll
find in today’s games and movies. So, gaining some experience with this type of animation is
one of the most important goals of the assignment. The key concept you’ll need to work with
this data is understanding how to compose transformations together as you render a scene
graph. To keep things simple, our rendering will be a pretty simple stick figure.
The other important goal of the assignment is implementing a simple spline curve to define
the path of the character. We will see how existing animation data of a character moving
uniformly in a straight line can be modified to follow a specified trajectory instead.
In this assignment, you will learn:
• How transformations should be composed to create animated characters
• How to create, navigate, and render a hierarchical model
• How to implement a spline curve to represent smooth functions
• How to transform the speed and orientation of a motion-captured animation
CMU Motion Capture Database
The CMU Motion Capture Database contains 2,605 different motions, most recorded at 120Hz.
These motions range from the simple (person walking straight forward), to the complicated
(directing traffic), to the silly (someone doing the “I’m a little teapot” dance).
For this assignment, we’ve found a simple walk motion and chopped up one cycle of it so that
it repeats. Playing this back in a loop while translating the character uniformly along z results
in an infinite walk. First, you will get this to display by drawing the limbs of the character
correctly. Next, you will modify your program to make the character walk along an arbitrary
spline curve instead of along the z-axis. Note that the motion of the character’s right arm is a
little jittery in the original data, so don’t worry about that as long as the rest of the motion is
OK.
You are encouraged to search through the CMU database and see what other motions strike
your fancy. Once you complete the first half of the assignment, you should be able to simply
swap in new motion data files to view them. This can be fun!
The motions in the CMU database use a skeleton specified in .asf files and a separate
motion in an .amc file. The .asf files specify bone names, directions, lengths, and the
skeleton hierarchy. In a bone’s local coordinate system, the translation from the start of the
bone until the end can be found using the bone “direction” and “length” — our support code
provides a shortcut for this, look for getBoneVector() inside the Bone class. The length
of each bone and its resting direction does not change from frame-to-frame, but the angle of
the bone relative to its joint does change. The rotation angles to apply at each frame are
specified in .amc files.
Requirements and Grading Rubric
We provide code that parses and loads the CMU data into a hierarchical skeleton data
structure. We also provide a utility function that returns the bone’s current rotation relative
to its parent. We provide you with code that advances through the time in the animation. You
will need to implement functionality to composite the transformations in this skeleton to
connect the bones together and make your character walk. To accomplish this, you need to
add your own code to the Bone and Character classes in character.hpp. Look for the
draw() routine in each of these classes – this is where most of your code will go. The
comments in the support code should help you get started.
Think of Character as the root node for your character’s scene graph. For a human character, this root node usually corresponds to the pelvis bone. Therefore, the root node usually has 3 “bones” extending
from it, one for each of the legs and one for the torso. You need to draw each of these bones
within the root node’s coordinate frame, which will change frame-to-frame based on the
animation data. Inside each bone’s draw() routine, you’ll need to draw the bone itself (see
next paragraph) and then draw all of the bone’s children. Look back at the slides from class
and remember how we use glPushMatrix() and glPopMatrix() to render scene
graphs.
To draw a bone, first start with just drawing a line segment from the origin to
the bone vector. Once you get a stick figure made of lines animating, your
next task is to render solid bones instead. To help, we’ve implemented a
function Draw::unitCylinderZ() for you to use; as the name suggests,
it’s a cylinder with unit radius and length, with axis from the origin to (0,0,1).
You will need to apply a transformation to make its axis match the bone
vector. We suggest using a radius of 0.05, and adding spheres of the same
radius at both endpoints to fill in the ends. This creates a 3D shape known
informally as a “capsule”, shown on the right.
Next, you will implement a cubic Hermite spline by adding code to the Spline3 class. The
data the spline contains is simply a collection of control points, each at a specified time and
with a specified value and derivative. In this case, the value and derivative are vec3s
because the data we are interpolating is the translation of the character. You should
implement the getValue() and getDerivative() functions to evaluate the spline
functions at arbitrary times. If you get this correct, you should see the spline drawn on the
ground as a red curve.
Finally, change the code in main.cpp to make the character walk along the spline curve. For
this, you will need to do three things: (i) Translate the character to the current position on the
spline. (ii) Rotate the character about the y-axis to make it face along the current velocity (i.e.
the derivative) instead of along the z-axis. (iii) Change the amount the animation advances
each frame to be proportional to the current speed. This way, the character’s walk cycle
proceeds more slowly if its speed is low, and you won’t see the character’s feet “skate” on the
ground. The original speed of the character was baseSpeed =
glm::length(Config::baseVelocity), so the amount you advance the character
should be dt*currentSpeed/baseSpeed.
Here’s a quick summary of the programming requirements:
1. Hierarchical skeleton transformations: You need to traverse the hierarchical skeleton
model to render the skeleton. You need to composite the various rotations and
translations involved in the model to cause it to come together as a skeleton.
2. Bone rendering: You need to use cylinders capped with spheres to render each bone in
the model.
3. Spline evaluation: You need to complete the implementation of the cubic Hermite
spline, so that it shows up as a smooth curve.
4. Transformed animation: You need to modify the animation so that the character walks
along the spline facing in the direction of motion, with the animation speed adjusted
so that its feet do not skate unrealistically along the floor. (It’s OK if the feet partially
go through the floor, because that’s in the original data.)
Grading for this assignment will be based on the quality of your implementation for each of
these four requirements.
Implementation Notes
Drawing a bone in the right direction. Essentially, you have a shape lying along the z-axis,
and you can obtain a unit vector b in the direction of getBoneVector(). You must perform
a transformation so that the transformed z-axis lies along b. There are at least two different
ways to do this:
a. Directly construct a matrix whose columns are the transformed coordinate axes, as
you did in the midterm. Take an arbitrary vector a that’s not parallel to b, and use it to
construct two more unit vectors orthogonal to b and to each other. Use them as the
first three columns of a mat4 m (what should the fourth column be?) and apply it as a
transformation using glMultMatrix(&m[0][0]). Hint: you could start by taking a
= (1,0,0), then if it is too close to b or −b, change it to (0,1,0).
b. Alternatively, figure out the axis and angle of a rotation that aligns the unit vector z =
(0,0,1) with b. The simplest such rotation is as follows. Imagine the plane containing
the two vectors z and b. If we rotate z along this plane by an angle equal to the angle
between z and b, then it will end up coinciding with b! The axis of this
rotation is simply the normal to the plane, and the angle is something
you know how to get.
For reference, the first frame of the walk animation is shown on the right. If
you comment out the character->advance(…) line in
advanceState(), your character should look like this.
Splines with t0 ≠ 0, t1 ≠ 1. In class, we’ve derived the formula for cubic
Hermite interpolation f(t) with endpoints at 0 and 1. How can we reuse this
formula for a cubic Hermite spline, which has a whole sequence of control
points at different values t0, t1, t2, …? Here’s a simple solution:
1. Find the interval [ti, ti+1] within which the desired time t lies.
2. Rescale the interval so that ti maps to 0 and ti+1 maps to 1. Find the corresponding
value of t, say tr.
3. Now we are working with the standard interval [0, 1]. Apply the cubic Hermite
interpolation formula to evaluate f(tr), and return the result.
There is one thing to be careful about: When you rescale time from [ti, ti+1] to [0, 1], the
derivatives also get rescaled! (That is, the slope of a function changes if you squash it
horizontally.) So you will have to use multiply the derivatives at the endpoints by (ti+1 − ti)
when applying the interpolation formula. Conversely, if you are computing the derivative of
the spline function itself, you will have to divide the result by (ti+1 − ti) to get the correct value
when stretched back to [ti, ti+1].
You can tell that your spline interpolation is working correctly if the default spline defined in
the starter code matches the screenshot on the first page, and the “approximately circular
path” defined in the comments in main.cpp looks like a circle and not a diamond. To verify
that Spline::getDerivative() is working correctly as well, which you’ll need to get
the walking speed right, you could draw a sphere at f(t) (i.e. path->getValue(time)),
another sphere at f(t+1), and an arrow starting from f(t) along the vector f′(t) (i.e.
path->getDerivative(time)). The head of the arrow, f(t) + f′(t), should end up
somewhat close to f(t+1).
Above and Beyond
All the assignments in the course will include great opportunities for students to go beyond
the requirements of the assignment and do cool extra work. We don’t offer any extra credit
for this work — if you’re going beyond the assignment, then chances are you are already
kicking butt in the class. However, we do offer a chance to show off… While grading the
assignments the TAs will identify the best 4 or 5 examples of people doing cool stuff with
computer graphics. After each assignment, the selected students will get a chance to
demonstrate their programs to the class!
There are many ways you can go beyond the requirements in this assignment. You can
improve the model rendering, let multiple people walk (or dance) together, or any number of
other options. If your ideas for going beyond the requirements would make your code more
difficult for the TAs to grade, please help them by submitting a standard version of your
assignment first through the normal website link, and then email the TAs the fancier version
of your assignment.
Support Code
The webpage where you downloaded this assignment description also has a download link
for support code to help you get started. The support code for this assignment is a simple
program using the SDL-based engine, similar to the ones we have used before. You should
also download separately the zip file containing the mocap data files.
The support code defines a program structure and everything you need to read and parse the
mocap data. To make locating data files simpler, we have a header file called config.h
that contains absolute paths to your data files. You should edit this file with the full
path (e.g. “C:\Users\Turing\a4\data” or “/home/turing/a4/data”) where
you’ve placed the data files. We will modify this file appropriately when grading your
assignment.
Handing It In
When you submit your assignment, you should include a README file. This file should
contain, at a minimum, your name and descriptions of design decisions you made while
working on this project. If you attempted any “above and beyond” work, you should note that
in this file and explain what you attempted.
When you have all your materials together, zip up the source files and the README, and
upload the zip file to the assignment hand-in link on our Moodle site. You don’t need to
include the data files, which are pretty large and we have a copy of them anyway. Any late
work should be handed in the same way, and points will be docked as described in our
syllabus.
Data Credits
The motion capture data used in this assignment was obtained from the CMU Motion Capture
Database. This database, along with additional information, can be found at
http://mocap.cs.cmu.edu/.