Lab 4: Animation

Animation is, quite simply, the displaying of different images over time (think flipbook). Animation is often used to create the illusion of movement (as in a cartoon character), or sometimes just to convey changing information (as in a digital clock).

Computer animation, then, simply requires that we draw different images in succession over time, in the same space. Since we already know how to draw images in Racket, all we need is:

  1. A way to express a sequence of images
  2. A mechanism for drawing these images at different points in time on the screen

It may not be obvious to you how to accomplish (1) just yet, but you already have the necessary tool: the function. A function, given some input value, can easily produce an image based on that input value. If we vary the input to such a function over time, then, we have our "sequence" of images.

The following function draws a square, rotated by the input angle, for instance:

(define (draw-rotated-square angle)
  (rotate angle (square 50 'solid 'red)))

Now for (2). It turns out that Racket provides us with a solution: the animate function. We use animate by providing it with the name of a function, which is then called with successive numerical values starting with 0. The named function is expected to return an image each time, which is simply drawn in a window that animate creates.

That's it. It's really that simple.

So, if we want to animate a rotating square, we simply have to do:

(animate draw-rotated-square)

Try evaluating that in your interactions area. You'll see a window pop up containing a rotating square. The square rotates approximately 28 degrees a second, because that's how often animate calls draw-rotated-square.

Unfortunately, if you wait long enough (try it), the program will stop because eventually draw-rotated-square is called with an argument equal to 360, which the rotate function doesn't like. Rotate only takes angles between 0 and 359. We can easily fix this by using the remainder function, as follows:

(define (draw-rotated-square t)
  (rotate (remainder t 360)
          (square 50 'solid 'red)))

Note that I've renamed the input to t, to reflect the fact that we're taking a time as input, which we are converting into an angle of rotation. Indeed, for this lab, all functions intended to be handed to animate will be functions of time. This is important! (It is also a little limiting, but we'll fix that later.)

One more detail: the first image returned by the function given to animate determines the size of the window. So, if we want to draw the square rotating inside a bigger area (so it doesn't "bounce" around), we can do this:

(define (draw-rotated-square t)
  (overlay (rotate (remainder t 360)
                   (square 50 'solid 'red))
           (empty-scene 150 150)))

Then do (animate draw-rotated-square) again.

And that's about all you need to know to start creating some interesting animations!


Note that for all following exercises, we ask that you follow the design recipe for every single function you write! Remember, this means Signature, Purpose, Examples, Template for all functions before you start implementing the function bodies! Form the habit now, and you'll reap the rewards later on.

Exercise 1: Countdown timer

For this exercise, write a program that animates a countdown timer, starting at 10 and counting down to 0. When it hits 0, the numbers stop changing (though your function will continue to be called with different values). The function breakdown is up to you, but we suggest that you have a function named draw-timer which takes an input t.

We will test your function by calling animate in the interactions area with draw-timer as an input. We will also, of course, check that you followed the design recipe for all functions in the program -- this goes for all the following exercises, too.

Exercise 2: Fun with sprites

For this exercise, locate (or create) a series of at least 3 successive sprites for a moving character, which are to be animated and moved across the screen from left to right (or right to left). When the character disappears off screen, it should reappear on the starting side and start over again.

We'll talk about sprites and go over an example of this in class.

Exercise 3: Clocks

For this exercise we ask you to implement an analog clock that starts with its hour, minute, and second hand initially positioned at 12 o'clock, and starts "ticking" when called from animate with t=0. It should act as a normal clock after that ... 60 seconds for one minute hand move, 60 minutes for one hour hand move. You may, of course, find it easier during testing to accelerate the movement (and accurate time-keeping is not required).

An easy way to create an independently rotateable "hand" is to draw a line on top of a transparent circle. E.g., you could do:

(define *transparent-layer* (circle 50 0 'white))
(define *second-hand* (overlay/align 'center 'top
                                     (rectangle 1 50 'solid 'black)
                                     *transparent-layer*))
(define *minute-hand* (overlay/align/offset 'center 'top
                                            (rectangle 3 40 'solid 'blue)
                                            0 -10
                                            *transparent-layer*))

Then the following:

(overlay (rotate -45  *minute-hand*)
         (rotate -180 *second-hand*)
         (circle 60 'outline 'black))

Would produce this image:

Clock

We strongly suggest breaking your program down into at least four different functions (all of which should take the time, t):

Note that an implementation which is particularly hard to understand (e.g., consisting of one large function) -- even if it works -- will not receive anywhere near full credit.

Exercise 4: Orbitals

This last exercise may be a bit of a challenge -- particularly if you forgo upcoming hints, but it'll be worth it.

Planetary orbits (e.g., of earth around our sun) can be described using Kepler's laws of planetary motion. The specifics of the laws and the derivation of related formulae are not important to us, but the bottom line is that the astronomer Johannes Kepler has provided us with a way to compute the position of a planet as a function of time.

This last things means ... that we can create a fairly accurate animation of planetary motion using our animate facility!

The planet's orbit follows the path of an ellipse, and can be described using the following constants:

  1. the semi-major axis (the radius of the ellipse at its widest point)
  2. the orbital period (how long it takes a planet to make it all the way around)
  3. a value between 0 (inclusive) and 1 (exclusive) that describes the shape of the ellipse --- 0 for circular, and close to 1 for relatively long and "flat".

A planet doesn't spend the same amount of time at different points on the orbital ellipse, however, and this is here Kepler comes in --- we will go over some of the details together (in and out of lab), but the gist of it is covered by the equations described here on Wikipedia.

Putting everything together, you will ultimately be implementing a function named draw-orbit that, using variables bound to constants in your program, will produce an animation like the following:

Orbital

The above animation, for reference, captures 15 frames of an orbital with a semimajor-axis of 150 (pixels) and eccentricity of 0.5.