# CS56 Computer Animation: Lab 8

In which, we implement blending and reorienting between motions

The goals of this lab are to

• Implement spliced motions
• Implement blends between similar motions
• Modify animations for a part of the body
• Align motion sequences for blending

## Get the source

The motion assignment has been added to your AnimationFramework repository. To get the source, run

> cd cs56/AnimationToolkit
> git pull
> cd build
> cmake ..
> make


You should now have a new directory under assignments called a8-blend.

### Question 1: Blend (5 points)

In this question, you will blend the walk motion with a strafe motion. Implement your solution in assignments/a8-blend/ABlend.cpp inside of the method blend().

// m1: First input motion
// m2: Second input motion
// alpha: blend value
// returns the result of m1 * (1-alpha) + m2
AMotion blend(const AMotion& m1, const AMotion& m2, double alpha)


Your implementation should be based on the code from class

duration = duration1 * (1-alpha) + duration2 * alpha
deltaT = 1/fps
for t from 0 to duration, increment by deltaT
APose pose1 = pose from motion1
APose pose2 = pose from motion2
APose newPose = lerp pose1 and pose2 by alpha
result.appendKey(newPose)
return result


To run the demo from the build directory, type

build> ../bin/a8-blend


Controls

• Press 'UP' to increase the blend factor _alpha
• Press 'DOWN' to decrease the blend factor

Your program should have the following features

1. blend() shoudl create and return a new motion. Use AMotion::appendKey to add poses to your blended motion
2. The motion returned by blend should have a framerate equal to motion1. Hint: Call setFramerate() before returning result.
3. The duration of the blended motion should be based on the blend factor, e.g. duration = duration1 * (1-alpha) + duration2 * alpha.
4. Use APose::Lerp to blend poses from motion1 and motion2

### Question 2: Splice (5 points)

In this question, you will modify the walk motion to have the arm motion from gangnam style dancing. Implement your solution in assignements/a8-blend/ASplice.cpp.

// orig: Starting input motion
// upper: Motion for the modifying the upper body (all joints which are descendants of the spine)
// alpha: blend value
// returns a new motion such that
//     the lower body matches the motion in 'orig'
//     the upper body is the result of blending the upper body motion with
//         the original's upper body motion, e.g. newupper = upper * (1-alpha) + orig * alpha
AMotion spliceUpperBody(const AMotion& orig, const AMotion& upper, double alpha)


For this question, use keys for blending instead of durations, e.g.

for i in range numKeys in orig
APose pose = orig.getKey(i)
// compute new pose and either call appendKey() or editKey() set the pose


To run the demo from the build directory, type

build> ../bin/a8-splice


Controls

• Press '0' to use 100% of the dance upper body motion
• Press '1' to use 50% of the dance upper body motion with 50% of the original
• Press '2' to see the original motion
• Press 'UP' to increase the blend factor _alpha
• Press 'DOWN' to decrease the blend factor
• NOTE: If you implement your solution so that key '0' shows the original instead of key '2', that is ok.

Your program should have the following features

1. spliceUpperBody() should create and return a new motion. Use AMotion::appendKey to add poses to your blended motion
2. The motion returned by blend should have a framerate equal to orig
3. The upper body consists of all descendants of the joint "Beta:Spine1"
4. Use AQuaternion::Slerp to blend local rotations for the upper body
5. Use the sequence of poses from upper starting at key ID = 120

### Question 3: Zombie Arms (10 points)

In this question, you will modify the walk motion to have zombie arms. Implement your solution in assignements/a8-blend/AZombieArms.cpp.

To run the demo from the build directory, type

build> ../bin/a8-zombie


Controls
• Press '0' to see the original motion
• Press '1' to see the arms with a constant rotation
• Press '2' to see the arms with an offset rotation

Part 1: Freeze Arms (5 points)

To freeze the arms, we will set both the shoulders and elbows to a constant rotation. Implement your solution in ComputeArmFreeze.

// motion: input motion
// returns a new motion with the shoulders and elbows outstretched
AMotion ComputeArmFreeze(const AMotion& motion)


### Freeze arms

Freeze arms should have the following features:

1. The left shoulder should local rotation equal to XYZ euler angles (-53, -88, -33)
2. The right shoulder should local rotation equal to XYZ euler angles (14, 88, -33)
3. Both elbows should local rotation equal to XYZ euler angles (0, 23, 0)
4. The motion returned should have a framerate equal to motion
5. You should modify the joints with names: "Beta:LeftArm", "Beta:RightArm" (shoulders), "Beta:LeftForeArm", and "Beta:RightForeArm" (elbows).

Part 2: Offset Arms (5 points)

To offset the arms, we will apply an offset rotation to shoulder's animation curve. We will freeze the elbows as before. Implement your solution in ComputeArmOffset.

// motion: input motion
// returns a new motion with the shoulders and elbows outstretched but moving
AMotion ComputeArmOffset(const AMotion& motion)


To compute the offset, we will use the local rotation of the should on the first frame to compute an offset rotation. $$R_{offset} = R_{desired} (R_i^j)^{-1}$$ where $R_i^j$ is the local to parent rotation for the shoulder joint. $R_{desired}$ is the target rotation we want for the joint. For the right joint, the desired rotation will be the XYZ euler angles (14, 88, -33). For the left joint, the desired rotation will be the XYZ euler angles (-53, -88, -33).

### Offset arms

Offset arms should have the following features:

1. The left shoulder's desired rotation is XYZ euler angles (-53, -88, -33)
2. The right shoulder's desired rotation is XYZ euler angles (14, 88, -33)
3. Both elbows should local rotation equal to XYZ euler angles (0, 23, 0). These frozen as before.
4. The motion returned should have a framerate equal to motion
5. You should modify the joints with names: "Beta:LeftArm", "Beta:RightArm" (shoulders), "Beta:LeftForeArm", and "Beta:RightForeArm" (elbows).

### Question 4: Reorient (5 points)

In this question, you will modify a motion so it starts in a new location and orientation. You will need to perform the same calculation in crossfade to align two motions. Implement your solution in assignements/a8-blend/AReorient.cpp

// motion: the input motion
// pos: the new starting position of the motion
// heading: the new starting orientation as an angle (radians) around the world UP axis (e.g. Y)
// returns a new motion whose starting pose has a root position and rotation that matches pos and heading
AMotion reorient(const AMotion& motion, const AVector3& pos, double heading)


To run the demo from the build directory, type

build> ../bin/a8-reorient


Controls
• Use the left and right arrow keys to rotate
• Use WASD to translate forward, back, left, and right

Your demo should have teh following features

1. The returned motion should have the same framerate as the input
2. The root position and heading of the first frame should match the desired position and heading. All subsequent frames should be offset so the resulting motions looks like the original.
3. The best solution should only align the headings. However, you may implement a simpler solution which sets the full XYZ orientation to match the heading. This will affect how motions where the root may tilt (such as jumps) are reoriented.

### Question 5: Crossfade (20 points)

In this question, you will implement a cross fade transition between two motins. You have a application with a GUI to help you test. You will implement your solution in libsrc/animation/AMotionBlender-basecode.cpp.

To run the demo from the build directory, type

build> ../bin/a8-crossfade


// Member function: AMotionBlender::blend
//
// param motion1: the starting motion of the transition
// param motion2: the ending motion of the transition
// param startKeyId: the frame of the starting motion at which to start blending
// param endKeyId: the frame of the ending motion at which to start blending
// param numBlendFrames: the number of frames in the blend
// param blend: the computed crossfade motion. This motion should contain
• The starting motion sequence. This consists of the keys from motion 1 in range [0, startKeyId)
• The cross fade sequence. This consists of the blended keys between motion 1 and motion 2. The blended motion will have numBlendFrames in it. The first frame will blend the startKeyId from motion 1 with the endKeyId from motion 2. The second frame will blend the (startKeyId+1) key from motion1 with the (endKeyId+1) from motion 2. Etc.
• The ending motion sequence. This consists of the keys from motion 2 in range [endKeyId+numBlendFrames, motion2.getNumKeys).

Part 1 (5 points) Implement AMotionBlender::append.

This method should grab the keys in range [startKeyId, endKeyId) from the input motion and append them to the output motion. Test your new function by extending AMotionBlender::blend() to append the keys [0, startKeyId) from motion 1 and the keys [endKeyId, motion2.getNumKeys) from motion 2. Your assignment should look as follows: the walk motion plays and then snaps back to the origin to play the jump.

Part 2 (5 points) Implement AMotionBlender::crossfade.

So far, the transition snaps from the first motion to the second motion. Fix this problem by implementing AMotionBlender::crossfade. This function should blend the frame between motion 1 and motion 2 and append the blended frames to 'blend'. Extend AMotionBlender::blend to call your new crossfade function. Now, your assignment should look as follows: the motion is smooth but the character slides to the origin to play the jump motion.

Part 3 (5 points) Align the positions in AMotionBlender::align.

Now we have a smooth result but the jump unrealistically moves back to the origin. We want the jump to occur at the character's current location! We will fix this by aligning the sequence from motion 2 with the start transition key from motion 1. Note that you can re-use the logic from reorient!

Translate the keys so that the second sequence is aligned with the first sequence. Extend AMotionBlender::blend to call align. Your assignment should now look as follows.

However, note that blending between a turn and jump still doesn't work! Let's fix that.

Part 4 (5 points) Align the root headings in AMotionBlender::align.

Reorient the keys (e.g. rotate and translate) so that the second sequence is aligned with the first. Now your blend should look good in all cases.

Testing tip: A correct crossfade should return the original motion whenever you crossfade between the same motion. For example, try blending two walk motions over the starting 10 frames. The result should look identical to the original motion.

## Extra Credit

Option 1 (2 points) Create a unique animation or character.

Option 2 (2 points) Infinite motion. Create a character that can walk or dance or jump forever by repeatedly blending the walking motion with itself.

Option 3 (2 points) Mirror. Try creating a mirrored motion which plays forward and backward.

Option 4 (4 points) Motion analysis. Detect when feet are in contact with the floor. Draw a cube at the location of the contact so we can visualize it.

Be sure to include a gif or video of your demo for full credit!