# Introduction to Computer Science

## Animations

### Three Animation Examples

Consider the following three problems:

• A simple animation of a head with the left eye winking, while the right eye shifts side-to-side. Make the head as realistic as you can.
• A colorful cartoon of a steam railroad engine rolling smoothly along a track from the left to the right side of the screen. As the engine rolls it is to emit puffs of smoke.
• An animation in which a ball rolls along a shrinking spiral from the outside of the image towards the center, changing color gradually from pure red to pure blue.

Each animation has to be able to be resized (it should detect its height and width, not assume them). Each animation has to repeat without manual intervention.

The problems all involve animations, but differ in the required number of different images. The first problem needs only a few distinct images showing the same head with eyes in various states. The other two problems require a large number of distinct frames.

When there are only a few frames, one simple solution is to extend the buffered drawing approach we used to reduce flicker to allow for multiple Graphics objects, one per frame. We draw the frames and then run through them repeatedly, making a virtual flip-book.

```

//File:  FlipBook.java
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.applet.*;

public class FlipBook{

//Data Structures

private Image [] image_g;
public Graphics [] g;
public int curWidth=0, curHeight=0, curFrames=0;
private Graphics graphics;

// Constructor
public FlipBook( Graphics g_real,
int width, int height,
Applet a, int frames) {
int i;

image_g = new Image[frames];
g = new Graphics[frames];
for (i = 0 ; i < frames; i++ ){
image_g[i] = a.createImage(width, height);
g[i] = (image_g[i]).getGraphics();
}
graphics = g_real;
curWidth = width;
curHeight = height;
curFrames = frames;

}

// Put out the buffered image
public void ShowPage(ImageObserver observer, int frame) {
graphics.drawImage(image_g[frame],0,0,observer);
}

}

/* This is a simple flip-book based drawing of a head
with one eye shifting side-to-side and the other
winking.

H. J. Bernstein, 2 December 2001

*/
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.applet.Applet;

{
// Global data
FlipBook fb = null;

// Initialization
public void init() {
setBackground(Color.white);
}

// paint method to do the actual drawing
public void paint (Graphics g_real) {

// declare variables
Graphics g;       // a graphics object
Dimension size    // dimensions of the window
= getSize();
int frame;        // the current frame
int frames;       // the total number of frames
int cx = size.width,
cy = size.height,
diameter = (int)(.8*Math.min(cx, cy));

// If we have a 300 pixel image, we wil have 24 frames
// At 90000 pixels per frame, this means 2,160,000
// pixels.

frames = diameter/10;

// If the flip-book exists and is the right size
// use the pages we already created.

if (fb == null
|| fb.curFrames != frames
|| fb.curWidth != cx
|| fb.curHeight != cy ) {

fb = new FlipBook(g_real,cx,cy, this, frames);

for (frame = 0; frame < frames; frame++) {

g = fb.g[frame];

// Draw hair
g.setColor(Color.red);
g.fillOval(cx/2-diameter/3,(cy-diameter)/2-diameter/12,
2*diameter/3, diameter/3);

g.setColor(Color.yellow);
g.fillOval((cx-diameter)/2, (cy-diameter)/2,
diameter, diameter);

// Draw two ears

g.fillOval((cx-diameter)/2-diameter/10,cy/2-diameter/8,
diameter/5,diameter/4);
g.fillOval((cx+diameter)/2-diameter/10,cy/2-diameter/8,
diameter/5,diameter/4);

// Draw brow line and smile

g.setColor(Color.blue);
g.drawArc((cx-3*diameter/4)/2,(cy-3*diameter/4)/2,
3*diameter/4,3*diameter/4, 60, 60);
g.drawArc((cx-3*diameter/4)/2,(cy-3*diameter/4)/2,
3*diameter/4,3*diameter/4, -50, -80);

// Now draw two eyes
{ int shift;

shift = frame - frames/4;
if (shift > frames/2) shift = 3*frames/4 - frame;

g.setColor(Color.blue);

// left side eye (actually the right eye of the head)
// this eye will shift

g.drawOval((cx-3*diameter/8-diameter/5)/2,
(cy-diameter/10)/2,
diameter/5,diameter/10);
g.fillOval((cx-3*diameter/8-diameter/10)/2+shift,
(cy-diameter/10)/2,
diameter/10,diameter/10);

// right side eye

g.drawOval((cx+3*diameter/8-diameter/5)/2,
(cy-diameter/10)/2,
diameter/5,diameter/10);
g.fillOval((cx+3*diameter/8-diameter/10)/2,
(cy-diameter/10)/2,
diameter/10,diameter/10);

if (shift > -frames/6 && shift < frames/6) {
// go to half closed
g.setColor(Color.yellow);
g.fillArc((cx+3*diameter/8-diameter/5)/2-1,
(cy-diameter/10)/2-1,
diameter/5+2,diameter/10+2, 0, 180);
g.setColor(Color.blue);
if (shift > -frames/8 && shift < frames/8 ) {
// go to fully closed
g.setColor(Color.yellow);
g.fillArc((cx+3*diameter/8-diameter/5)/2-1,
(cy-diameter/10)/2,
diameter/5+2,diameter/10-1, 0, -180);
g.setColor(Color.blue);
g.drawArc((cx+3*diameter/8-diameter/5)/2-1,
(cy-diameter/10)/2,
diameter/5+2,diameter/10-1, 0, -180);
} else {
g.drawLine((cx+3*diameter/8-diameter/5)/2,cy/2,
(cx+3*diameter/8+diameter/5)/2,cy/2);
}
}

}
}
}

// display the entire flip-book
for (frame = 0; frame < frames; frame++) {

g = fb.g[frame];

fb.ShowPage(this,frame);
try {
Thread.sleep(50);               // sleep for 50 msec
} catch (InterruptedException t){}
}
try {
Thread.sleep(1000);              // sleep or 1 sec
} catch (InterruptedException t) {}
repaint();
}

}

```

For the other two problems, we need to be more careful in using memory. Each 300 by 300 pixel frame would need room for 90,000 pixels. If we use 200 frames, we would need 18,000,000 pixels at several bytes each. In such a case, it is more efficient to do our animation frames as lists of objects to be drawn, rather than arrays of pixels. If we do each frame's object list as an array, we need an array of arrays, i.e. a two dimensional array. We define a DisplayObjectList class:

```

//File: DrawingObjectList.java
//Uses: FlipBook.java

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.applet.*;

public class DrawingObjectList{

//Data Structures

private int [] [] dol;
private FlipBook fb;
private Graphics g;
public int curWidth=0, curHeight=0, curFrames=0;
public int curFrame = 0;

private static final int DOL_InitialSize = 100;
private static final int DOL_SetColor = 1;
private static final int DOL_DrawLine = 2;
private static final int DOL_DrawOval = 3;
private static final int DOL_FillOval = 4;
private static final int DOL_DrawArc  = 5;
private static final int DOL_FillArc  = 6;
private static final int DOL_DrawRect = 7;
private static final int DOL_FillRect = 8;

// Constructor
public DrawingObjectList( Graphics g_real,
int width, int height,
Applet a, int frames) {
int i;

fb = new FlipBook(g_real, width ,height, a, 1);
dol = new int[frames][DOL_InitialSize];
for (i=0; i<frames; i++) {
dol[i][0] = 0;
}
curWidth = width;
curHeight = height;
curFrames = frames;
}

//Add an object to a frame

private void addDO(int[] obj, int fr) {
int i, j;

if ( dol[fr][0] + obj.length > dol[fr].length -1 ) {
int old [];

j = dol[fr].length;
old = dol[fr];
dol[fr] = new int[2*j];
for (i=0; i <= old[0]; i++) {
dol[fr][i] = old[i];
}
}

j = dol[fr][0];
for (i = 0; i < obj.length; i ++ ) {
dol[fr][++j] = obj[i];
}
dol[fr][0] = j;

}

// Set a frame

public void setFrame( int frame ) {
curFrame = frame;
}

// Set a color in the current frame
public void setColor( Color c ) {
int [] cobj;
cobj = new int [5];
cobj[0] = DOL_SetColor;
cobj[1] = c.getRed();
cobj[2] = c.getGreen();
cobj[3] = c.getBlue();
/* cobj[4] = c.getAlpha(); */
}

// Set a color in a particular frame
// and update the current frame to that frame
public void setColor( Color c, int frame ) {
setFrame(frame);
setColor( c);
}

// Set a color in a range of frames
// and update the current frame to the last frame
public void setColor( Color c, int framelo, int framehi ) {
int i;
for (i = framelo; i <=framehi; i++) {
setFrame( i);
setColor( c);
}
}

// Draw a line in the current frame
public void drawLine( int x1, int y1, int x2, int y2 ) {
int [] lobj;
lobj = new int [5];
lobj[0] = DOL_DrawLine;
lobj[1] = x1;
lobj[2] = y1;
lobj[3] = x2;
lobj[4] = y2;
}

// Draw a line in a particular frame
// and update the current frame to that frame
public void drawLine( int x1, int y1, int x2, int y2,
int frame ) {
setFrame(frame);
drawLine(x1, y1, x2, y2);
}

// Draw a line in a range of frames
// and update the current frame to the last frame
public void drawLine( int x1, int y1, int x2, int y2,
int framelo, int framehi ) {
int i;
for (i = framelo; i <=framehi; i++) {
setFrame( i);
drawLine(x1, y1, x2, y2);
}
}

// Draw an oval in the current frame
public void drawOval( int x, int y, int xw, int yw ) {
int [] oobj;
oobj = new int [5];
oobj[0] = DOL_DrawOval;
oobj[1] = x;
oobj[2] = y;
oobj[3] = xw;
oobj[4] = yw;
}

// Draw an oval in a particular frame
// and update the current frame to that frame
public void drawOval( int x, int y, int xw, int yw,
int frame ) {
setFrame(frame);
drawOval(x, y, xw, yw);
}

// Draw an oval in a range of frames
// and update the current frame to the last frame
public void drawOval( int x, int y, int xw, int yw,
int framelo, int framehi ) {
int i;
for (i = framelo; i <=framehi; i++) {
setFrame( i);
drawOval(x, y, xw, yw);
}
}

// Fill an oval in the current frame
public void fillOval( int x, int y, int xw, int yw ) {
int [] oobj;
oobj = new int [5];
oobj[0] = DOL_FillOval;
oobj[1] = x;
oobj[2] = y;
oobj[3] = xw;
oobj[4] = yw;
}

// Fill an oval in a particular frame
// and update the current frame to that frame
public void fillOval( int x, int y, int xw, int yw,
int frame ) {
setFrame(frame);
fillOval(x, y, xw, yw);
}

// Fill an oval in a range of frames
// and update the current frame to the last frame
public void fillOval( int x, int y, int xw, int yw,
int framelo, int framehi ) {
int i;
for (i = framelo; i <=framehi; i++) {
setFrame( i);
fillOval(x, y, xw, yw);
}
}

// Draw an arc in the current frame
public void drawArc( int x, int y, int xw, int yw,
int alo, int deg ) {
int [] aobj;
aobj = new int [7];
aobj[0] = DOL_DrawArc;
aobj[1] = x;
aobj[2] = y;
aobj[3] = xw;
aobj[4] = yw;
aobj[5] = alo;
aobj[6] = deg;
}

// Draw an arc in a particular frame
// and update the current frame to that frame
public void drawArc( int x, int y, int xw, int yw,
int alo, int deg, int frame ) {
setFrame(frame);
drawArc(x, y, xw, yw, alo, deg);
}

// Draw an arc in a range of frames
// and update the current frame to the last frame
public void drawArc( int x, int y, int xw, int yw,
int alo, int deg,
int framelo, int framehi ) {
int i;
for (i = framelo; i <= framehi; i++) {
setFrame( i);
drawArc(x, y, xw, yw, alo, deg);
}
}

// Fill an arc in the current frame
public void fillArc( int x, int y, int xw, int yw,
int alo, int deg ) {
int [] aobj;
aobj = new int [7];
aobj[0] = DOL_FillArc;
aobj[1] = x;
aobj[2] = y;
aobj[3] = xw;
aobj[4] = yw;
aobj[5] = alo;
aobj[6] = deg;
}

// Fill an arc in a particular frame
// and update the current frame to that frame
public void fillArc( int x, int y, int xw, int yw,
int alo, int deg, int frame ) {
setFrame(frame);
fillArc(x, y, xw, yw, alo, deg);
}

// Fill an arc in a range of frames
// and update the current frame to the last frame
public void fillArc( int x, int y, int xw, int yw,
int alo, int deg,
int framelo, int framehi ) {
int i;
for (i = framelo; i <= framehi; i++) {
setFrame( i);
fillArc(x, y, xw, yw, alo, deg);
}
}

// Draw a rectangle in the current frame
public void drawRect( int x, int y, int xw, int yw ) {
int [] robj;
robj = new int [5];
robj[0] = DOL_DrawRect;
robj[1] = x;
robj[2] = y;
robj[3] = xw;
robj[4] = yw;
}

// Draw a rectangle in a particular frame
// and update the current frame to that frame
public void drawRect( int x, int y, int xw, int yw,
int frame ) {
setFrame(frame);
drawRect(x, y, xw, yw);
}

// Draw a rectangle in a range of frames
// and update the current frame to the last frame
public void drawRect( int x, int y, int xw, int yw,
int framelo, int framehi ) {
int i;
for (i = framelo; i <=framehi; i++) {
setFrame( i);
drawRect(x, y, xw, yw);
}
}

// Fill a rectangle in the current frame
public void fillRect( int x, int y, int xw, int yw ) {
int [] robj;
robj = new int [5];
robj[0] = DOL_FillRect;
robj[1] = x;
robj[2] = y;
robj[3] = xw;
robj[4] = yw;
}

// Fill a rectangle in a particular frame
// and update the current frame to that frame
public void fillRect( int x, int y, int xw, int yw,
int frame ) {
setFrame(frame);
fillRect(x, y, xw, yw);
}

// Fill a rectangle in a range of frames
// and update the current frame to the last frame
public void fillRect( int x, int y, int xw, int yw,
int framelo, int framehi ) {
int i;
for (i = framelo; i <=framehi; i++) {
setFrame( i);
fillRect(x, y, xw, yw);
}
}

// Put out the buffered image
public void ShowPage(ImageObserver observer, int frame) {
int i=1;
g = fb.g[0];
g.setColor(Color.white);
g.fillRect(0,0,curWidth-1,curHeight-1);
while (i <= dol[frame][0] ) {
switch (dol[frame][i]) {

case DOL_SetColor:
g.setColor(new Color(dol[frame][i+1],
dol[frame][i+2],
dol[frame][i+3]
/* ,dol[frame][i+4] */));
i += 5;
break;

case DOL_DrawLine:
g.drawLine(dol[frame][i+1], dol[frame][i+2],
dol[frame][i+3], dol[frame][i+4]);
i += 5;
break;

case DOL_DrawOval:
g.drawOval(dol[frame][i+1], dol[frame][i+2],
dol[frame][i+3], dol[frame][i+4]);
i += 5;
break;

case DOL_FillOval:
g.fillOval(dol[frame][i+1], dol[frame][i+2],
dol[frame][i+3], dol[frame][i+4]);
i += 5;
break;

case DOL_DrawArc:
g.drawArc(dol[frame][i+1], dol[frame][i+2],
dol[frame][i+3], dol[frame][i+4],
dol[frame][i+5], dol[frame][i+6]);
i += 7;
break;

case DOL_FillArc:
g.fillArc(dol[frame][i+1], dol[frame][i+2],
dol[frame][i+3], dol[frame][i+4],
dol[frame][i+5], dol[frame][i+6]);
i += 7;
break;

case DOL_DrawRect:
g.drawRect(dol[frame][i+1], dol[frame][i+2],
dol[frame][i+3], dol[frame][i+4]);
i += 5;
break;

case DOL_FillRect:
g.fillRect(dol[frame][i+1], dol[frame][i+2],
dol[frame][i+3], dol[frame][i+4]);
i += 5;
break;

}
}

fb.ShowPage(observer, 0);
}

}

```
The DisplayObjectList class provides a reasonable basis for the last two animations.

### Drawing a Train

We make a crude train which puts out puffs of smoke out of simple, colorful shapes. We draw the track in all the frame object lists eactly the same way, but the train itself needs to be shifted to the right for each frame, and the puffs of smoke need to rise.

```

//File: TrainApplet.java
//Uses: DrawingObjectList.java
//Uses: FlipBook.java

/* This is a simple object-list and flip-book based
drawing of train moving along a track
making puffs of smoke.

H. J. Bernstein, 2 December 2001

*/
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.applet.Applet;

public class TrainApplet extends Applet
{
// Global data
DrawingObjectList dol = null;

// Initialization
public void init() {
dol = null;
}

// paint method to to the actual drawing
public void paint (Graphics g_real) {

// declare variables
Graphics g;       // a graphics object
Dimension size    // dimensions of the window
= getSize();
int frame;        // the current frame
int frames;       // the total number of frames
int cx = size.width,
cy = size.height,
diameter = (int)(.8*Math.min(cx, cy));
int ii;

frames = (int)(cx*1.6);

// If the DOL exists and is the right size
// use the pages we already created.

if (dol == null
|| dol.curFrames != frames
|| dol.curWidth != cx
|| dol.curHeight != cy ) {

dol = new DrawingObjectList(g_real,cx,cy, this, frames);

// draw the rails
dol.setColor(Color.black,0,frames-1);
dol.fillRect(0,cy-diameter/20,cx-1,diameter/40,0,frames-1);
dol.fillRect(0,cy-diameter/20-diameter/8,cx-1,diameter/40,0,frames-1);

for(ii=0; ii< cx-diameter/12; ii += diameter/12) {
dol.drawLine(ii,cy-diameter/20,
ii+diameter/12,cy-diameter/20-diameter/8,
0, frames-1);
}

for (frame = 0; frame < frames; frame++) {
dol.setFrame(frame);

//Draw the back wheels
dol.setColor(Color.blue);
dol.fillOval(frame+diameter/12,
cy-diameter/20-diameter/5-diameter/8,diameter/5,diameter/5);
dol.fillOval(frame+diameter/12-diameter/6,
cy-diameter/20-diameter/5-diameter/8,diameter/5,diameter/5);
dol.fillOval(frame+diameter/12-3*diameter/6,
cy-diameter/20-diameter/4-diameter/8,diameter/4,diameter/4);

// Draw the boiler
dol.setColor(Color.red);
dol.fillRect(frame-3*diameter/6,
cy-diameter/10-3*diameter/8,3*diameter/4,diameter/4);

// Draw the cab
dol.setColor(Color.red);
dol.fillRect(frame-3*diameter/6,
cy-diameter/10-diameter/2,diameter/4,diameter/4);

// Every 40th frame, emit a puff of smoke, rising in
// following frames
if ( frame%40 == 0 ) {
for (ii = frame; ii < frames; ii++) {
int irise;
dol.setFrame(ii);
dol.setColor(Color.gray);
irise = (int)Math.sqrt(25.0*(ii-frame))+diameter/16;
dol.fillOval(frame-3*diameter/6+5*diameter/8,
cy-diameter/10-diameter/2-irise,
diameter/8,diameter/8);
}
dol.setFrame(frame);
}

// Draw the smoke stack
dol.setColor(Color.green);
dol.fillRect(frame-3*diameter/6+5*diameter/8,
cy-diameter/10-diameter/2,diameter/8,diameter/8);

// Draw the front wheels
dol.setColor(Color.blue);
dol.fillOval(frame,
cy-diameter/20-diameter/5,diameter/5,diameter/5);
dol.fillOval(frame-diameter/6,
cy-diameter/20-diameter/5,diameter/5,diameter/5);
dol.fillOval(frame-3*diameter/6,
cy-diameter/20-diameter/4,diameter/4,diameter/4);

}

}

// display the entire set of frames
for (frame = 0; frame < frames; frame++) {

dol.ShowPage(this,frame);
try {
Thread.sleep(20);               // sleep for 20 msec
} catch (InterruptedException t){}
}
try {
Thread.sleep(1000);              // sleep or 1 sec
} catch (InterruptedException t) {}
repaint();
}

}

```

### Drawing a Spiral

A spiral is a path that winds around a fixed center moving progressively further inwards or outwards. We describe it as a "parametric curve". We give the x and y coordinates of points on the spiral as functions of a parameter, t, which we think of as time. Let us start with the so-called logarithmic spiral, which, like the shell of a nautilus, repeats the same shape as the spiral grows, holding a constant angle between a tangent to the curve and a line to the center. We give the curve in terms of two constants u and s. The constant s gives the "speed" in radians of rotation per unit time. The constant u determines the angle of the tangent grows:

x(t) = r*cos(s*t)*exp(u*s*t)
y(t) = r*sin(s*t)*exp(u*s*t)

This spiral starts at radius r, when t=0.

The logarithmic sprial grows rapidly. We can form a spiral which grows more slowly, like the groves of a record, by making the radius of the spiral proportional to the angle of rotation around the center:

x(t) = p*cos(s*t)*s*t/(2*PI)
y(t) = p*sin(s*t)*s*t/(2*PI)

This sprial starts at the center and grows by p, the "pitch" for each rotation.

We can also make spirals by piecing together portions of circles or short straight lines.

We choose to make a spiral that grows at a constant pitch.

```

//File: SpiralApplet.java

/* This is a simple object-list and flip-book based
drawing of a ball following a spiral curve from the
outside to the center of an image.

H. J. Bernstein, 2 December 2001

*/
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.applet.Applet;

public class SpiralApplet extends Applet
{
// Global data
DrawingObjectList dol = null;

// Initialization
public void init() {
dol = null;
}

// paint method to to the actual drawing
public void paint (Graphics g_real) {

// declare variables
Graphics g;       // a graphics object
Dimension size    // dimensions of the window
= getSize();
int frame;        // the current frame
int frames;       // the total number of frames
int cx = size.width,
cy = size.height,
diameter = (int)(.8*Math.min(cx, cy));

frames = 36*5;

// If the DOL exists and is the right size
// use the pages we already created.

if (dol == null
|| dol.curFrames != frames
|| dol.curWidth != cx
|| dol.curHeight != cy ) {

dol = new DrawingObjectList(g_real,cx,cy, this, frames);

for (frame = 0; frame < frames; frame++) {

int i,xprev,yprev,x,y;

dol.setFrame(frame);

xprev = cx/2; yprev = cy/2;

dol.setColor(Color.green);
for (i = 0; i < frames-frame; i++) {
x = (int)(cx/2 + Math.cos(.17*(double)(i))
*(double)(i*diameter)/(frames*2.));
y = (int)(cy/2 + Math.sin(.17*(double)(i))
*(double)(i*diameter)/(frames*2.));
dol.drawLine(xprev,yprev,x,y);
xprev = x;
yprev = y;
}
dol.setColor(new Color((255*(frames-frame))/frames,0,
(255*frame)/frames));
dol.fillOval(xprev-diameter/20,yprev-diameter/20,
diameter/10, diameter/10);
}
}

// display the entire set of frames
for (frame = 0; frame < frames; frame++) {

dol.ShowPage(this,frame);
try {
Thread.sleep(20);               // sleep for 20 msec
} catch (InterruptedException t){}
}
try {
Thread.sleep(1000);              // sleep or 1 sec
} catch (InterruptedException t) {}
repaint();
}

}

```

Revised 23 November 2003