COM1370 Computer Graphics

3D Animation project, due Thursday, August 27, 1998

Professor Futrelle, Northeastern U., College of Computer Science

I have created a "Pendulum" project, which is a slight variation of the earlier 3D project. The purpose of the pendulum project is to illustrate how hierarchical data structures can be transformed as a group, but with important and interesting variations. In particular, the Pendulum project has a pendulum swinging on the face of a clock, and it remains moving on the face of the clock and relative to the face of the clock as the clock tumbles in space.

The Code Warrior project is in the folder "Pendulum Sm98" on Ambassador and the combined sources are also available on the web.

Your assignment will be to replace the pendulum with a clock face with moving hands. The clock face should have four small squares around a circle marking 12, 3, 6, and 9 o'clock, as well as an hour and a minute hand turning around the center of the front face of the clock. (Of course the hands will have to turn much faster than real time in order to see some interesting motion, e.g., the passage of a couple of hours, in a one minute or so demo run of the code.) They'll have to move at least as fast as the pendulum does now.

The code that produces the pendulum now can be adapted to do the all of the above. The 6-sided rectangular solid for the clock already exists; no changes have to be made to it. Below, the critical parts of the data structures, transforms, and their code are sketched, as a guide for your work. I'll work backwards through the code, explaining each part. The bulk of the critical code is in 3D.cp, with further relevant code in graphics_classes.cp and linear_alg.cp. Most of the discussion below focuses on the existing code for the pendulum which you can read in the project on the Ambassador server. Main() calls do_it_all(), defined in 3D.cp. The basic pendulum definition code in do_it_all is:

// Set up the pendulum object
// Inserted last so it displays in front of the clock face
pendulum = new GPoly;
SetUpPendulum(pendulum);
SetPendulumMaster(pendulum, 0);
Clock->InsertChild(pendulum);

There is then a loop of 200 steps that tumbles the clock, by setting up three rotation matrices and composes them (forms their product) to tumble the clock (which is first moved to the origin, then tumbled, then moved back to the center of the screen). In that loop, the following pendulum code is executed:

SetPendulumMaster(pendulum, i);

To understand this call, we must understand that the rectangular clock solid is constructed with its lower left corner at the x,y,z origin. All subsequent operations are done with respect to this "master model". In a similar way, the pendulum is constructed with a master model that is placed on the master clock model. This is done by defining a tie-shaped polygon for the pendulum at the origin and including a "master transform" within the pendulum object that places it on the proper part of the clock model, "swung" at the appropriate angle.

Here's the code for the critical SetUpPendulum and SetPendulumMaster functions above. The first simply creates the pendulum at the origin (it is "upside down", since it is in screen coordinates, y increasing down the screen, and we want it to appear upright on the screen.)

void SetUpPendulum(GPoly *p) {

p->nPts =4;
p->rgbColor = Gdarkyellow;

// pendulum is "tie-shaped"
// note that it's created "upside down" for screen coordinates.
p->masterPts[0] = P(0.0, 0.0, 0.0);
p->masterPts[1] = P(pendulumHalfWidth, pendulumHeight, 0.0);
p->masterPts[2] = P(0.0, pendulumHalfWidth + pendulumHeight, 0.0);
p->masterPts[3] = P(-pendulumHalfWidth, pendulumHeight, 0.0);
for(int i = 0; i < 4; i++) // placeholders
p->polyPts[i] = new GPt;
tSwing = MakeIdent(); tPendulumTransform = new Transform;
tPlacePendulum = new Transform;
tPlacePendulum->SetTranslation(P(width/2, height/4, 0.0));
}

First a 4-sided polygon is created, various placeholders are created (to be set later) and a tPlacePendulum transformation is set that moves the "top" of the pendulum to the right place in space to set it on the clock model. The second important function is,

void SetPendulumMaster(GPoly *p, int istep) {
tSwing->SetRotZ(pendulumMag * sin(pendulumRate * istep));
Compose(tPendulumTransform, tPlacePendulum, tSwing);
p->ChangeMasterTransform(tPendulumTransform, replace);
}

Here, a z-rotation transformation is set to a value that depends on the step in the loop in which it will be called, the line of code,

SetPendulumMaster(pendulum, i);

As "time" progresses (each istep) the angle of the z-rotation oscillates in a periodic fashion, making the pendulum swing back and forth. For clock hands, the job will be simpler, since each clock hand rotates at a steady rate, a certain small constant angle per istep. Once the rotation is set, it is composed with the basic tPlacePendulum transform to produce the tPendulumTransform, which in turn is used to replace the master transform in the pendulum.

The next important thing that happens in do_it_all() is

Clock->PropagateTrans(tTemp2);

The transform tTemp2 is the one that will tumble the clock and all its components around its center (by moving it to the origin, tumbling , and moving back). Note in our first code sample above that the following statement appeared:

Clock->InsertChild(pendulum);

This placed the pendulum into the composite structure that already contained the 6 sides of the clock solid. Everything in this composite object (an instance of class GComp) is transformed as a unit, including the pendulum. The way this is done is that the transform is simply applied to all the children of the composite object (in graphics_classes.cp):

// Propagate transform to children
// Quite simple -- just propagates to each child.
void GComp::PropagateTrans(Transform *tr) {
for(int i = 0; i < nChildren ; i++)
children[i]->PropagateTrans(tr);
}

The reason this all works is that the tumbling transform, tTemp2, is composed with the master transform inside each object (a clock side, or the pendulum, or in your case a clock hand or hour mark) to create a propagated transform, also stored within each object. The master transform is left untouched -- it is simply reused each time to compose the current propagated transform which sets the exact position and orientation in 3-space at which the object will finally be displayed. Here's the code for a polygon, from graphics_classes.cp:

void GPoly::PropagateTrans(Transform *tr) {
MakeQDPoly(); // creates QD poly
}

The reference to GObj::PropagateTrans(tr) just uses the superclass function to update the matrix. But then the polygon points all have to be updated and the QuickDraw polygon itself has to be created. For example, the PropPoly() code is this, basically one matrix x vector for each of the four points (vectors) in our pendulum polygon,

void GPoly::PropPoly() {
for(int i_thPt = 0; i_thPt < nPts; i_thPt++)
{ polyPts[i_thPt]->zeroGPt();
for(int j_thCoord = 0; j_thCoord < 4; j_thCoord++)
for(int k_thCoord = 0; k_thCoord < 4; k_thCoord++)
polyPts[i_thPt]->ptCoords[j_thCoord] +=
propagatedTrans->matrix[j_thCoord][k_thCoord] *
masterPts[i_thPt]->ptCoords[k_thCoord]; }
// now calculate if front face is visible.
SetVisibility();
}

Note that we use backface detection with SetVisibility() at the end to see if the polygon is facing the viewer. If not, the visible flag is set to False and Draw() skips drawing the polygon at this orientation.

(Some tedious formatting was required to convert the document I had to HTML, so there may be a few typos in the above.)