CS 553 -- Winter Quarter 2009

Project #11: Keyframe Animation

100 Points

Due: March 16


This page was last updated: February 26, 2009


This project is about performing a keyframe animation. There are two ways you can do this to satisfy the requirements of this project.

Way #1 -- Create a Keyframe Animation Scene Choreography

  1. Put this project number and your name in the title bar.

  2. Do a keyframe animation. There need to be at least 500 frames in the animation and at least 7 keyframes that define the animation (this includes the start and the finish positions).

  3. The object to be animated is your choice of any 3D geometry. It must have enough linework (or polygon-work) in it to be able to tell that it is rotating (a smooth sphere is not a good choice). Being a 3D object, each keyframe position is defined by seven numbers: (x,y,z,thetax,thetay,thetaz,f), where f is the frame number. Note this uses Euler angles, even though axis-angle representations are usually better. Feel free to use axis-angle instead.

  4. You must also animate one other quantity (Q) such as color, alpha, texture parameter, scale factor, etc. So, you really have 8 numbers in each keyframe: (x,y,z,thetax,thetay,thetaz,Q,f)

  5. The (x,y,z,thetax,thetay,thetaz,Q,f) values for each keyframe are your choice and should be pre-defined in the program.

  6. Animate your object by interpolating (x,y,z,thetax,thetay,thetaz,Q) between the keyframes. Use the smooth interpolation method that we discussed in class. For your convenience, the method is re-covered below.

  7. Display the object's keyframe positions so you/I can see how well it passes through the keyframe positions.

  8. Make your entire animation finish in exactly 10 seconds, no matter which system I run it on, and no matter what the window size is.

  9. Allow the user to toggle between orthographic and perspective projections using the GLUI menu.

  10. Allow scaling and X-Y rotation of the 3D scene.

Way #2 -- Create a Keyframe Animation From Your Project 3, 4, 5, 6, 9, or 10

  1. Put this project number and your name in the title bar.

  2. Keyframe animate something. There needs to be at least 500 frames in the animation and at least 7 keyframes that define the animation (this includes the start and the finish positions).

  3. Exactly what gets animated is up to you. It could be a fly-through by animating the eye position (ex,ey,ez) and look-at position (lx,ly,lz). But, you must animate at least 7 quantities, so if you do the fly-through, you need one more thing. So, you need to animate ex,ey,ez,lx,ly,lz,Q,f), where f is the frame number. The other quantity (Q) could be color, alpha, texture parameter, scale factor, isovalue, a range slider parameter, etc.

  4. Animate your scene by interpolating between the keyframes. Use the smooth interpolation method that we discussed in class. For your convenience, the method is re-covered below.

  5. Make your entire animation finish in exactly 10 seconds, no matter which system I run it on, and no matter what the window size is.

  6. Allow the user to toggle between orthographic and perspective projections using the GLUI menu.

  7. Allow scaling and X-Y rotation of the 3D scene.

Suggestion:

If you are not sure how to approach this, do linear interpolation first. It is more intuitive and easier to code. And, most of the code is re-usable for the smooth interpolation.

Linear Interpolation:

The linear interpolation technique simply divides a straight line path from Keyframe #i to Keyframe #i+1 into the required number of intermediate frames and employs the linear blending equation to do the in-betweening. Suppose we are interpolating between Keyframe #i and Keyframe #i+1:

x = (1.-t) · X[i] + t · X[i+1] t = (float)( NowFrame - KeyFrame[i] ) / (float)( KeyFrame[i+1] - KeyFrame[i] ) 0.0 <= t <= 1.0 dt = 1. / (float)( KeyFrame[i+1] - KeyFrame[i] )

So, for example, to interpolate from keyframe #NowKeyFrame to keyframe #NowKeyFrame+1, you would need to do something like:

Global variables:


int NowKeyFrame;		// we are between keyframes NowKeyFrame and NowKeyFrame+1
int NowFrame;			// the current frame number

float X, Y, Z;
float Thetax, Thetay, Thetaz;



In your Idle Function:



<< Compute NowFrame based on the clock -- see below >>
<< Determine NowKeyFrame such that NowFrame falls between it and the next keyframe >>

float framesInThisInterval = (float)( Frames[NowKeyFrame+1].f - Frames[NowKeyFrame].f );
float t = (float)( NowFrame - Frames[NowKeyFrame].f )  /  framesInThisInterval;

X = (1.-t) * Frames[NowKeyFrame].x  +  t * Frames[NowKeyFrame+1].x;
Y = ...;
Z = ...;
Thetax = ...;
Thetay = ...;
Thetaz = ...;

glutSetWindow( GraphicsWindow );
glutPostRedisplay();




At the end of InitGlui():



GLUI_Master.set_glutIdleFunc( Animate );




In Display():



glPushMatrix();
	glTranslatef( X, Y, Z );
	glRotatef( Thetax,  1., 0., 0. );
	glRotatef( Thetay,  0., 1., 0. );
	glRotatef( Thetaz,  0., 0., 1. );
	glCallList( ObjectList );
glPopMatrix();


Smooth Interpolation:

The smooth interpolation technique that you will be using employs the Coons cubic curve (end points and end slopes). But, rather than you having to specify all of the slopes, the technique makes a reasonable approximation of them based on the surrounding points.

For example, the curve's parametric derivative at a point can be computed by the calculus chain rule. For example, the x parametric derivative, dx/dt, would be:

dx/dt = ( dx/dF ) * ( dF/dt )

dx/dF is the change in X per Frame and is taken over the interval between the previous keyframe and the next keyframe. dF/dt is the change in Frame per change in t and is taken over just the keyframe interval that we are looking at right now. Remember that this is only the X component -- you will also need to compute the Y, Z and the three theta components.

Suppose we are interpolating between keyframe #K and keyframe #K+1.

At keyframe #K:

dx/dt = { ( X[K+1] - X[K-1] ) / ( F[K+1] - F[K-1] ) }   ·   { ( F[K+1] - F[K] ) / ( 1. - 0. ) }

And at keyframe #K+1:

dx/dt = { ( X[K+2] - X[K] ) / ( F[K+2] - F[K] ) }   ·   { ( F[K+1] - F[K] ) / ( 1. - 0. ) }

So, for example, to interpolate from keyframe #NowKeyFrame to keyframe #NowKeyFrame+1, you would need to do something like:

Global variables:


int NowKeyFrame;		// we are between keyframes NowKeyFrame and NowKeyFrame+1
int NowFrame;			// the current frame number
struct keyframe
{
        int f;                          // frame #
        float x, y, z;                  // x, y, and z locations
        float ax, ay, az;               // angles in degrees
        float hue;                      // hue of the object (s=v=1.)
        float dxdf, dydf, dzdf;         // derivatives
        float daxdf, daydf, dazdf;      // derivatives
        float dhdf;                     // derivatives
} Frames[MAXKEYFRAMES] =

{
	. . .
};



In your Idle Function:


<< Compute NowFrame based on the clock -- see below >>
<< Determine NowKeyFrame such that NowFrame falls between it and the next keyframe >>

Ax = Frames[NowKeyFrame].x
Bx = Frames[NowKeyFrame].dxdt
Cx = -3.*Frames[NowKeyFrame].x + 3.*Frames[NowKeyFrame+1].x -
	2.*Frames[NowKeyFrame].dxdt - Frames[NowKeyFrame+1].dxdt
Dx = 2.*Frames[NowKeyFrame].x - 2.*Frames[NowKeyFrame+1].x +
	Frames[NowKeyFrame].dxdt + Frames[NowKeyFrame+1].dxdt

float framesInThisInterval = (float)( Frames[NowKeyFrame+1].f - Frames[NowKeyFrame].f );
float t = (float)( NowFrame - Frames[NowKeyFrame].f )  /  framesInThisInterval;

X = Ax + t * ( Bx + t * ( Cx + t * Dx ) );
Y = ...;
Z = ...;
Thetax = ...;
Thetay = ...;
Thetaz = ...;

glutSetWindow( GraphicsWindow );
glutPostRedisplay();




At the end of InitGlui():



GLUI_Master.set_glutIdleFunc( Animate );




In Display():



glPushMatrix();
	glTranslatef( X, Y, Z );
	glRotatef( Thetax,  1., 0., 0. );
	glRotatef( Thetay,  0., 1., 0. );
	glRotatef( Thetaz,  0., 0., 1. );
	glCallList( ObjectList );
glPopMatrix();


Timing of the Animation

Make your entire animation finish in exactly 10 seconds, no matter which system I run it on, and no matter what the window size is.

To do this, first define the cycle time in milliseconds:


// time in milliseconds for cycle to complete:

const int MSEC = { 10*1000 };

Then, convert the elapsed time into a global variable NowFrame that ranges from 0 to MAXFRAMES throughout the animation.


int NowFrame;

...

	if( AnimationIsOn )
	{
		// # msec into the cycle ( 0 - MSEC-1 ):

		int msec = glutGet( GLUT_ELAPSED_TIME )  %  MSEC;


		// turn that into the current frame number:

		NowFrame = (int)( (float)MAXFRAME * (float)msec / (float)MSEC );
	}

You then figure out which pair of keyframes NowFrame lies between and interpolate in there.

+5 Points Extra Credit

Show the shadow of something on a floor and/or a wall.

You can cast the shadow using a scaling trick. First, draw the 3D object normally. Then, in English:

  1. Scale by 0. in the direction perpendicular to the flat surface you are casting the shadow on.
  2. Translate to the flat surface.

Remember that OpenGL transformations get programmed in the reverse order of the English order.

The shadow can be drawn as a black object. It can be drawn as a flat colored object. Or, it can be alpha-blended into the floor color. All of these are OK for this project.

Grading:

Item Points
Something moves in a predictable way 30
Smooth transition at the keyframes 20
Animate all 7 quantities 30
Time the animation 20
Shadow Extra Credit 5
Potential Total 105

What Did The Sample Program Use for Animation Parameters?

This is here as an example only -- do not use these same parameters! Be more clever than Joe Graphics was.


// the keyframes:

struct keyframe
{
	int f;				// frame #
	float x, y, z;			// x, y, and z locations
	float ax, ay, az;		// angles in degrees
	float hue;			// hue of the object (s=v=1.)
	float dxdf, dydf, dzdf;		// derivatives
	float daxdf, daydf, dazdf;	// derivatives
	float dhdf;			// derivatives
} Frames[] =

{
	{	0,	20.,	50.,	0.,	0.,	0.,	0.,	  0.	},
	{	200,	650.,	650.,	600.,	0.,	0.,	450.,	120.	},
	{	300,	500.,	400.,	400.,	0.,	0.,	270.,	240.	},
	{	450,	80.,	650.,	800.,	-90.,	180.,	90.,	300.	},
	{	525,	400.,	650.,	0.,	225.,	-180.,	90.,	540.	},
	{	600,	650.,	50.,	200.,	225.,	0.,	90.,	180.	},
	{	800,	20.,	50.,	0.,	0.,	0.,	0.,	  0.	},
	{	-1								}
};