This week we reviewed Denis Zorin's lecture on nested matrices, and we also started talking about how to animate things over time.

I went over the point that the relative movements within
a complex nested geometry (such
as a human body or an automobile)
can be structured as
a tree.
As computer scientists we know that
in order to traverse a tree you use a stack,
and that in order to traverse a stack,
you need
`push()`

and
`pop()`

operations.

There are two distinct ways to
do this - either by using an explicit
stack and explicit
`push()`

and
`pop()`

methods,
or else to create
parent→child
relationships between objects, whereby
the child object is always rendered
relative to its parent.
In the latter case we use
recursive method calls to evaluate
children, children of children, etc.,
so the matrix stack is implicit,
because it is
implemented by the data stack
of recursive method calls.

In this lecture we focused only on the explicit approach, although you are free to use the implicit approach in your homework if you are feeling ambitious.

I had suggested implementing the stack via:

int ns = 0; Matrix S[] = new Matrix[100];As noted in class, it is sufficient to give a fixed length for the matrix stack because transformation nesting tends not to have unbounded depth.

In this approach, your primitive transforations operations:

rotateX(theta) rotateY(theta) rotateZ(theta) translate(x,y,z) scale(x,y,z)should modify the matrix on the top of the stack

`S[sp]`

.
You will also want to
*push* the current matrix
on the stack (ie: to store the current value
to we can modify a temporary copy) and
to *pop* the stack (ie: to restore
the stored value that we had pushed).
To do this, you need two more simple methods:

void push() { copy(S[sp], S[sp+1]); sp++; } void pop() { --sp; }where the method

`copy(Matrix src,Matric dst)`

above copies from its first argument to its second argument.
You should add code to your implementation of
the `push()`

and `pop()`

methods to check for stack overflow and underflow.

In order to apply the matrix on the top of
the stack to an object's matrix, you will also
want to provide a `transform(shape)`

method that copies the matrix on top of the
stack into that shape's transformation matrix
for this frame:

void transform(Shape shape) { copy(S[sp], shape.matrix); }

Your assignment is to implement some sort of cool and interesting nested mechanized figure, such as a person, or a robot, or a vehicle, or a bobbing bird or anything else that might be fun, and that you think the rest of the class would enjoy seeing.

In order to make things move over time,
you'll need some of the arguments in your
calls to your primitive matrix tranformation
methods to vary over time.
As we discussed in class,
one way to do this is to use a *key frame* approach,
in which you specify values at particular
key animation frames, and then interpolate values at
other times that lie between these key frames.

One particular way to do this, which we went
over in class, is to provide an array of
{key time, key value} pairs, and to
implement a method `eval(double keyData[][], double time)`

that extracts the time-varying animation value
at any arbitrary `time`

.

For now, we will define the interpolation between
key frames to be linear, so that the behavior of
the function as a whole is *piecewise linear*.
For example, suppose
we want a value to rise up from 0.0 at time 0.0
to 10.0 at time 5.0, hold steady at 10.0 until
time 15.0, and then fall back down to 0.0 at
time 20.0.
We would then define the doubly nested array:

double keyData[][] = { { 0.0, 0.0 }, { 5.0, 10.0 }, { 15.0, 10.0 }, { 20.0, 0.0 }, };Then at any given

`time`

we can
get the value by invoking ` eval(keyData, time) `

.
You will need to implement `eval`

.
If the argument `time`

is less than the first key time `keyData[0][0]`

then your method should just return the first
key value `keyData[0][1]`

.
Similarly,
if the argument `time`

is greater than the last key time `keyData[n-1][0]`

(where `n = keyData.length`

),
then your method should just return the last
key value `keyData[n-1][1]`

.

You can compute the linearly interpolated value between two successive key times by using ratios. Using the shorhand notation:

then the value of the interpolated function at a particularT= keyData[i][0] and_{i}V= keyData[i][1]_{i}

V_{time}= V_{i}+ (V_{i+1}- V_{i}) * (time - T_{i}) / (T_{i+1}- T_{i})

As a simple example of putting the above concepts into practice, here is code excerpted from a scene that contains a ball bouncing on top of a box that is sliding left to right:

... Shape box, ball; ... void initialize() { ... cube = new Shape(CUBE); box = new Shape(SPHERE); ... } ... double slidingData[][] = { { 0.0, -2.0 }, { 10.0, 2.0 }, { 20.0, -2.0 }, }; ... double bouncingData[][] = { { 0.0, 1.0 }, { 5.0, 2.0 }, { 10.0, 1.0 }, }; ... void animate(double time) { ... initializeMatrixStack(); ... push(); translate(eval(slidingData, time % 20.0), 0, 0); transform(box); push(); translate(0, eval(bouncingData, time % 10.0), 0); transform(ball); pop(); pop(); ... } ...