Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Database

Composing Reactive Animations


Dr. Dobb's Journal July 1998: Composing Reactive Animations

Conal is a member of the Microsoft Research Graphics Group. He can be contacted at [email protected].


Sidebar: Models versus Presentations

There's no question that computer graphics -- especially interactive graphics -- is an incredibly expressive medium with potential beyond imagination. However, few people are able to create interactive graphics, so what might be a widely shared medium of communication is instead a tool for specialists. The problem is that authors still have to worry about how to get a computer to present content, rather than focus on the nature of the content itself. For instance, behaviors such as motion and growth are generally gradual, continuous phenomena; moreover, many such behaviors go on simultaneously. Computers cannot directly accommodate either of these basic properties, because they do their work in discrete steps rather than continuously, and they only do one thing at a time. Graphics programmers consequently have to bridge the gap between what an animation is and how to present it on a computer.

If the kind of programming in use today (like that described in the accompanying text box "Models versus Presentations" on page 25) is unsuitable for most potential authors, then we need to move toward a different form of programming. Alternative forms must give authors freedom of expression to say what an animation is, while invisibly handling details of discrete, sequential presentation. In other words, these forms must be declarative ("what to be"), rather than imperative ("how to do").

In this article, I present one such approach to declarative programming of interactive content. Fran (short for "functional reactive animation") is a high-level vocabulary that lets you describe the essential nature of an animated model, while omitting details of presentation. And because this vocabulary is embedded in a modern functional programming language (Haskell), the animation models are reusable and composable in powerful ways.

Fran is freely available (with source code) as part of the Hugs implementation of Haskell for Windows 95/NT (http://www .haskell.org/hugs/). Newer versions of Fran may be found at http://www.research.microsoft.com/~conal/Fran/. The underlying ideas form the basis of Microsoft's DirectAnimation, a COM-based programming interface accessible through conventional languages like Java, Visual Basic, JavaScript, VBScript, and C++. DirectAnimation is built into Internet Explorer 4.0, so you may already have it.

There are three ways you can experience this article:

  • In this printed version, examples have an accompanying sequence of snapshots. By scanning them from left to right, top to bottom (first row, second row, and so on), you'll get a sense of motion.
  • On the Web (http://www.research.microsoft.com/~conal/Fran/ tutorial.htm), examples are illustrated by animated GIFs, showing animation over time, but not interactivity. That version of this article also contains additional discussion and several animations not in the printed version.
  • Finally, you can run the examples and interact with or modify them. After installing Hugs (available at http://www .haskell.org/hugs/), double-click on the file tutorial.hs in the subdirectory lib\Fran\demos. At the > prompt, type "main" and press Enter. The examples will begin running. Press Spacebar, "n," or right arrow to advance to the next animation, and "p" or left arrow for the previous one. If you want to display just a single animation (leftRightCharlotte, for instance), then close the animation window and enter "display leftRightCharlotte". You can alter the definition in an editor, save the result, enter ":r" to the Hugs prompt, and "$$" again to display the new version. For 2D examples having a user argument u, use displayU instead of display. Similarly, for 3D examples, use displayG if there is no user argument, and displayGU if there is a user argument.

The First Example

I'll start with the animation in Figure 1 called leftRightCharlotte, which moves Charlotte from side to side. Listing One efines a value called leftRightCharlotte to be the result of applying moveXY to three arguments. (In most other programming languages, you would instead say something like "moveXY(wiggle,0,charlotte)".)

Listing One

leftRightCharlotte = moveXY wiggle 0 charlotte
charlotte = importBitmap "../Media/charlotte.bmp"  

The function moveXY takes x and y values and an image, and produces an image moved horizontally by x and vertically by y. All values may be animated. In this example, the x value is given by wiggle, a predefined smoothly animated number. Wiggle starts out at 0, increases to 1, decreases back past 0 to -1, and then increases to 0 again -- all in the course of two seconds, and then it repeats, forever. The second line defines charlotte by importing a bitmap file, making it available for use on the first line as the second argument to moveXY.

Although this example isn't a masterpiece, it is nonetheless a complete animation program in just two short lines of code.

Similarly, Figure 2 and Listing Two define an animation of Patrick moving up and down. To get the vertical movement, I've used a nonzero value for the second argument to moveXY. Rather than using wiggle, you use waggle, which is defined to be just like wiggle, but delayed by half a second.

Listing Two

upDownPat = moveXY 0 waggle pat
pat = importBitmap "../Media/pat.bmp"  

Listing Three

charlottePatDance = 
  leftRightCharlotte `over` upDownPat  

Figure 3 and Listing Three combine the two previous examples. The over operation glues two animations together, yielding a single animation, with the first one being over the second. Because I used waggle for upDownPat in this combined animation, Pat is at the center when Charlotte is at her extremes (and vice versa).

Composition

Composition is the principle of putting together simple things to make complex ones, then putting these together to make even more complex things, and so on. This building-block principle is crucial for making even moderately complicated constructions; without it, the complexity quickly becomes unmanageable.

Listings One through Three illustrate composition. I first built leftRightCharlotte out of charlotte, wiggle, and moveXY; then upDownPat out of pat, moveXY, and waggle. Finally, I built charlottePatDance out of leftRightCharlotte and upDownPat. A crucial point here is that when you make something out of building blocks, the result is a new building block in itself, and you can forget about how it was constructed.

There is a more powerful version of composition, based on defining functions. Listing Four, for instance, defines hvDance (for "horizontally and vertical dance"), which combines any two images, in the way that charlottePatDance combines charlotte and pat. Now you can give a new definition for the dancing couple that gives exactly the same animation: charlottePatDance = hvDance charlotte pat.

Listing Four

hvDance im1 im2 =
    moveXY wiggle 0 im1  `over` 
    moveXY 0 waggle im2

Having defined this generalized dance animation, you can go on to more exotic compositions. For example, you can take an animation produced by hvDance, shrink it, and put the result back into hvDance twice to make it dance with itself. As Figure 4 and Listing Five show, the result is pleasantly surprising. This example gives you a hint of how powerful it is to be able to define new animation functions. For instance, you could try charlottePatDance, stretched by a wiggly amount; see Listing Six(a). To prevent negative scaling, you take the absolute value of wiggle. Next, use hvDance again, but give it wiggly sized charlotte and pat. For visual balance, use wiggle and waggle; see Listing Six(b). Next, put Pat in orbit around a growing and shrinking Charlotte. To get a circular motion, use moveXY, with wiggle for x and waggle for y; see Listing Six(c).

Listing Five

charlottePatDoubleDance = hvDance aSmall aSmall 
  where
    aSmall = stretch 0.5 charlottePatDance  

Listing Six

<b>(a)</b>
dance1 = stretch (abs wiggle) charlottePatDance 

<b>(b)</b>
dance2 = hvDance (stretch wiggle charlotte) 
                  (stretch waggle pat)

<b>(c)</b>
patOrbitsCharlotte =
   stretch wiggle charlotte  `over` 
   moveXY wiggle waggle pat

As you may have surmised, wiggle and waggle are related to sine and cosine and defined as:

waggle = cos (pi * time)

wiggle = sin (pi * time)

The animated number time is a commonly used "seed" for animations and has the value t at time t. Thus, for instance, the value of wiggle at time t is equal to sin(t).

Rate-Based Animation

Up to now, the positions of animations have been specified directly. For instance, the definition of leftRightCharlotte says that Charlotte's horizontal position is wiggle.

In the physical universe, objects move as a consequence of forces. As Newton explained, force leads to acceleration, acceleration to velocity, and velocity to position. With computer animation, you have the freedom to ignore the laws of our universe. However, since animations are usually intended to be viewed by and interacted with by inhabitants of our own universe, they are often made to look and feel real by emulating Newtonian laws or simplifications and variations on them.

The key idea underlying Newton's laws and their variations is the notion of an instantaneous rate of change. Fran makes this notion available in animation programs. To illustrate rate-based animation, you can make Becky move from the left edge of the viewing window, toward the right, at a rate of one distance unit per second; see Figure 5 and Listing Seven.

Listing Seven

velBecky u = moveXY x 0 becky 
  where
    x = -1 + atRate 1 u

The local definition of x here (introduced as a where clause), follows a style you'll see in the following definitions. To express an animated value that starts out with a value x0 and grows at a rate of r, you say x0 + atRate r u. Here u is a "user", which is a Fran value that contains all user input and display update events. Rate-based animations require a user argument in order to give atRate a way of knowing when to start and how precisely to calculate value from rate. Unlike previous examples, this one can be displayed with displayU. To see this example, enter displayU velBecky.

In Listing Seven, Becky has a constant velocity, but with a little more effort you can give Becky a constant acceleration by providing a constant value for the rate of change of the velocity; see Listing Eight. In the definition of v, the "0 +" is unnecessary, but emphasizes that the initial velocity is zero.

Listing Eight

accelBecky u = moveXY x 0 becky 
   where
     x = -1 + atRate v u
     v =  0 + atRate 1 u

Listing Nine

mouseVelBecky u = move offset becky
   where
    offset = atRate vel u
    vel    = mouseMotion u

The notion of "rate" is useful not just in one dimension, but in two and three dimensions as well. In Listing Nine, I control Becky's 2D velocity with the mouse. When you hold the mouse cursor at the center of the view window, Becky stays still. As you move away from the center, imagine an arrow from the window's center to the mouse cursor. Becky moves in that direction and her speed will be equal to the arrow's length. This kind of imaginary arrow is referred to as a "vector" and is the same type of quantity as a two- or three-dimensional offset, velocity, or acceleration. In 2D, a vector can be thought of as having horizontal and vertical (X and Y) components, or as having a magnitude (length) and direction. This time, I use move, a variant of moveXY that takes a 2D offset vector. (If a vector v is x units horizontally and y units vertically, then "move v im" is equivalent to "moveXY x y im.") The offset vector starts out as the zero vector, and grows at a rate equal to mouseMotion, which is the offset of the mouse cursor relative to the origin of 2D space (which you see in the center of the view window).

In the real world, the position of an object may affect its speed or acceleration. In Listing Ten, Becky is chasing the mouse cursor. The further away it is, the faster she moves. The only difference from Listing Nine is that the velocity is determined by where the mouse cursor is relative to Becky's own position, as indicated by the vector subtraction.

For fun, you can generalize the beckyChaseMouse function in the same way that hvDance generalized charlottePatDance earlier; see Listing Eleven. Then chaseMouse becky is equivalent to beckyChaseMouse, as you can verify by typing displayU (chaseMouse becky) at the Hugs prompt.

Listing Ten

beckyChaseMouse u = move offset becky 
   where
    offset = atRate vel u
    vel    = mouseMotion u - offset

Listing Eleven

chaseMouse im u = move offset im
   where
    offset = atRate vel u
    vel    = mouseMotion u - offset 

For more fun, try the same, but replace becky with some of the animations that appeared earlier (leftRightCharlotte, charlottePatDance, and patOrbitsCharlotte); see Figure 6 and Listing Twelve.

Next make a chasing animation that acts like it is attached to the mouse cursor by a spring. The definition is similar to beckyChaseMouse. In Listing Thirteen, however, the rate is itself changing at rate accel (acceleration). This acceleration is defined like the velocity was in the previous example, but this time, some drag is also added. This tends to slow down Becky by adding some acceleration in the direction opposite to her movement. (Increasing or decreasing the "drag factor" of 0.5 in Listing Thirteen creates more or less drag.) The operator *^ multiplies a number by a vector, yielding a new vector that has the same direction as the given one but a scaled magnitude.

Listing Twelve

danceChase u =
   chaseMouse (stretch 0.5 charlottePatDance) u 

Listing Thirteen

springDragBecky u = move offset becky
   where
    offset = atRate vel u
    vel    = atRate accel u
    accel  = (mouseMotion u - offset) - 0.5 *^ vel 

As usual, these declarative animation programs are straightforward because they say what the motion is, in high-level, continuous terms, without struggling to accommodate the discreteness of the computer used to present them. In contrast, imperative animation programs must explicitly simulate rate-based animation by making lots of discrete steps -- accumulating approximations to the continuously varying forces, accelerations, and velocities -- to approximate motion. Doing an accurate and efficient job of all this approximation work is a tricky task. With systems like Fran, you just describe the continuous motion in terms of continuously varying rates, and trust Fran to do a good job with the approximation. (Not good enough to fly an airplane or control dangerous machinery, but good enough for an effective illustration or game.)

Composition-in-Time

Operations such as over and move support the principle of composition-in-space. Composition-in-time is equally valuable. Figure 7 and Listing Fourteen, for instance, define an orbiting animation, and then combine it with a version of itself delayed by one second. Instead of delaying, you can speed it up; see Listing Fifteen. You can even delay or slow down animations involving user input. In Listing Sixteen, one Jake tracks the mouse cursor, while the other follows the same path, but delayed by one second.

Listing Fourteen

orbitAndLater = orbit `over` later 1 orbit  
  where
    orbit = moveXY wiggle waggle jake

Listing Fifteen

orbitAndFaster = orbit `over` faster 2 orbit  
   where
    orbit = move wiggle waggle jake

Listing Sixteen

followMouseAndDelay u =
  follow `over` later 1 follow
   where
    follow = move (mouseMotion u) jake 

Next you can build an animated sentence, following the mouse's motion path. As a preliminary step, use delayAnims dt anims = overs (zipWith later [0, dt ..] anims) to define a delayAnims function, which takes a time delay dt and a list anims of animations, and yields an animation. Each successive member of the given animation list is delayed by the given amount after the previous member. The definition of delayAnims introduces a few new Fran elements. The Fran overs function is like over, but applies to a list of animations rather than just two. Animations earlier in the list are placed over ones later in the list. The notation [0, dt ...] means the infinite list of numbers 0, dt, 2 dt, 3 dt, and so on. Finally, zipWith applies to a given two-argument function the successive values from two given lists. You use it here to delay the first animation in anims by 0 seconds, the second by dt seconds, the third by 2dt seconds, and so on. Finally, overs combines them into a single animation. Figure 8 and Listing Seventeen present a simple use of delayAnims. Next, use delayAnims (Listing Eighteen) to define mouseTrailWords that makes animated sentences.

Listing Seventeen

kids u =
   delayAnims 0.5
     (map (move (mouseMotion u))
          [jake, becky, charlotte, pat]) 

Listing Eighteen

trailWords motion str =
  delayAnims 1 (map moveWord (words str))
  where
     moveWord word = move motion (
                      stretch 2 (
                        withColor blue (stringIm word) )) 

The Haskell words function takes a string apart into a list of separate words. The Haskell map function takes a function (moveWord) and a list of values (the separated words) and makes a new list by applying the function to each member of the list. The Fran stringIm function makes a picture of a string. I define the function moveWord locally to be the result of making a picture of the given word, using the Fran stringIm function, and moving it to follow the mouse. delayAnims then causes each of these mouse-following word pictures to be delayed by different amounts. Figure 9 and Listing Nineteen is a use of trailWords following a specified path, while Listing Twenty follows the mouse.

Listing Nineteen

flows u = trailWords motion 
             "Time flows like a river"
   where
     motion = 0.7 *^ vector2XY (cos time)
                               (sin (2 * time)) 

Listing Twenty

flows2 u = trailWords (mouseMotion u)
              "Time flows like a river" 

Reactive Animation

The animations presented to this point can be called "nonreactive" since they always do the same thing. A "reactive" animation, on the other hand, involves discrete changes due to events. To illustrate, you can make a circle that starts off red and changes to blue when the left mouse button is pressed.

Listing Twenty-One

redBlue u = buttonMonitor u `over`
             withColor c circle
   where
    c = red `untilB` lbp u -=> blue 

An informal reading of the last line of Listing Twenty-One (also see Figure 10) is that the color c is red until you press the left mouse button, then becomes blue. For a more literal reading, you must understand that there are really two new binary infix operators here -- untilB and -=> -- which can be used separately or together. Implied parentheses are around lbp u -=> blue. The -=> operator, which can be read as "handled by value," takes an event (lbp u) and a value (blue), and yields a new event. In this case, the new event happens when the left button is pressed, and has value blue. The untilB operator takes an animation of any type (the color-valued constant animation red), and an event (lbp u -=> blue), whose occurrence provides a new animation of the same type.

Cyclic Reactivity

To make Figure 10 more interesting, you can switch between red and blue every time the left button is pressed. As Listing Twenty-Two shows, you do this with the help of a cycle function that takes two colors (c1 and c2) and gives an animated color that starts out as c1. When the button is pressed, it swaps c1 and c2 and repeats (using recursion).

Listing Twenty-Two

redBlueCycle u = buttonMonitor u `over`
                  withColor (cycle red blue u)
                            circle
   where
    cycle c1 c2 u =
     c1 `untilB` nextUser_ lbp u ==> cycle c2 c1 

Listing Twenty-Two uses the operator ==>, which is a variant of -=>. This operator (which can be read as "handled with function") takes an event and function f. It works like -=>, but gets event values by applying f to event values from the event given to it. In this case, f is the cycle function applied to just two arguments, leaving the third (a user) to be filled in automatically (using ==>). The nextUser_ function turns lbp into an event whose occurrence information is a new user, corresponding to the remainder of the user u. The color arguments get swapped each time "around the loop."

For variety, Listing Twenty-Three uses three colors, and changes the circle's size smoothly.

Listing Twenty-Three

tricycle u =
    buttonMonitor u `over`
    withColor (cycle3 green yellow red u) ( 
      stretch (wiggleRange 0.5 1)
        circle )
   where
    cycle3 c1 c2 c3 u =
     c1 `untilB` nextUser_ lbp u ==> 
     cycle3 c2 c3 c1

Selection

Figure 11 and Listing Twenty-Four present a flower that starts out in the center and moves to the left or right when the left or right mouse button is pressed, returning to the center when the button is released.

The function bSign is defined to be -1 when the left button is down, +1 when the right button is down, and 0 otherwise (thanks to selectLeftRight). You can use bSign to control the rate of growth of an image. In Figure 12 and Listing Twenty-Five, pressing the left (or right) button causes the image to shrink (or grow) until released. Put another way, the rate of growth is 0, -1, or 1, according to bSign. A simple change to the grow function (Listing Twenty-Six) causes the image to grow or shrink at a rate equal to its own size. selectLeftRight, used to define bSign, is also the key ingredient in defining buttonMonitor (Listing Twenty-Seven), which gives button feedback.

Listing Twenty-Four

jumpFlower u = buttonMonitor u `over`
                moveXY (bSign u) 0 flower
flower = stretch 0.4
            (importBitmap "../Media/rose medium.bmp") 
bSign u = selectLeftRight 0 (-1) 1 u

Listing Twenty-Five

growFlower u = buttonMonitor u `over`
                stretch (grow u) flower  
grow u = size
  where 
   size = 1 + atRate rate u 
   rate = bSign u

Listing Twenty-Six

growFlowerExp u = buttonMonitor u `over`
                   stretch (grow' u) flower 
grow' u = size
  where 
   size = 1 + atRate rate u 
   rate = bSign u * size 

Listing Twenty-Seven

buttonMonitor u =
   moveXY 0 (- height / 2 + 0.25) (
    withColor textColor (
     stretch 2 (
     stringBIm (selectLeftRight "(press a button)" "left" "right" u)))) 
  where
    (width,height) = vector2XYCoords (viewSize u)

stringBIm turns an animated string into an image animation, which here gets enlarged, colored white, and moved down by a little less than half the window height.

selectLeftRight can itself be defined in terms of more basic functions, as in Listing Twenty-Eight. You use the conditional function condB to say that if the left button is down, use the left value, or if the right button is down, use the none value; otherwise use the none (constantB, which turns constants -- nonanimations -- into animations that never change).

Listing Twenty-Eight

selectLeftRight none left right u = 
   condB (leftButton u) (constantB left) ( 
     condB (rightButton u) (constantB right) ( 
       constantB none ))

3D Animation

Declarative animation applies to 3D as well, and the 2D operations I've used to this point -- importBMP, moveXY, and stretch -- have 3D counterparts. As a first 3D example, sphere = importX "../Media/sphere2.x" defines a sphere in which the function importX brings in a 3D model in "X-file" format, as used by Microsoft's DirectX. It is just as easy to import a teapot; see Figure 13 and Listing Twenty-Nine. I used stretch3 (a 3D counterpart to stretch) because the imported model was too small. Listing Thirty colors the teapot and makes it spin around the z- (vertical) axis.

Next, you can use the mouse to control the teapot's orientation. To do this, define mouseTurn to turn a given geometry g around the x-axis according the mouse's vertical movement, and around the z-axis according the mouse's horizontal movement, scaled by . Finally, as Figure 14 and Listing Thirty-One show, you apply mouseTurn to a green teapot.

Listing Twenty-Nine

teapot =
   stretch3 2 (importX "../Media/tpot2.x") 

Listing Thirty

redSpinningPot =
  turn3 zVector3 time (
    withColorG red teapot) 

Listing Thirty-One

mouseTurn g u =
   turn3 xVector3 y (
    turn3 zVector3 (-x) g)
  where
    (x,y) = vector2XYCoords (pi *^ mouseMotion u)
 mouseSpinningPot u =
   mouseTurn (withColorG green teapot) u 

You can also make teapots spin by controlling the rotation angle with the grow function, as in the growing flower examples. First, define spinPot, see Listing Thirty-Two, that takes (animated) color and angle and yields a colored, turning teapot. Then make a pot that spins one way when the left button is pressed, and the other way when the right button is pressed, using the grow function, and giving feedback with buttonMonitor; see Figure 15 and Listing Thirty-Three. renderGeometry, used here with a convenient default camera, turns a 3D animation into a 2D animation.

Listing Thirty-Two

spinPot potColor potAngle =
  turn3 zVector3 potAngle (
    withColorG potColor teapot) 

Listing Thirty-Three

spin1 u = buttonMonitor u `over`
           renderGeometry (spinPot red (grow u)) 
                          defaultCamera 

Additional spinning teapots will all have the general form of using the button monitor and rendering with the default camera. Rather than having to write several definitions, give the pattern a name. In Listing Thirty-Four, withSpinner takes a function as its first argument, and applies that function to the result of the grow function applied to the user argument. With this definition, you can write spin1 more simply; see Listing Thirty-Five. Another use of withSpinner is to make the color vary in hue and use the value from grow to determine the time-varying speed of rotation, so that the mouse buttons cause the turning to accelerate and decelerate (see Listing Thirty-Six).

Listing Thirty-Four

withSpinner f u =
   buttonMonitor u `over`
   renderGeometry (f (grow u) u)
                  defaultCamera 

Listing Thirty-Five

spin1 = withSpinner spinner1
  where
    spinner1 angle u = spinPot red angle 

Listing Thirty-Six

spin2 = withSpinner spinner2
  where
    spinner2 potAngleSpeed u =
      spinPot (colorHSL time 0.5 0.5)
              (atRate potAngleSpeed u)

In addition to visible geometry, you can add lights to a 3D model. In Listing Thirty-Seven, you combine a white sphere, which is visible but does not emit light, and a point light source, which is invisible but emits light. You color the sphere/light pair white, shrink it, and give it motion. For convenience, you express the motion path in terms of spherical coordinates, saying that the distance from the origin of space (which is also the center of the teapot) is always 1.5 units, the longitude is times the elapsed time, and the latitude is twice times the elapsed time. Consequently, you get a motion that meanders about, but maintains a fixed distance from the center of the teapot.

Listing Thirty-Seven

 sphereLowRes = importX "../Media/sphere0.x"
 movingLight =
    move3 motion (
     stretch3 0.1 (
      withColorG white (
       sphereLowRes `unionG` pointLightG)))
  where
   motion = vector3Spherical 1.5
              (pi*time) (2*pi*time)
 potAndLight =
   withColorG green teapot `unionG` movingLight 

Just for fun, replace the single moving light with five. A simple change suffices, if you add delayAnims3 -- a 3D variant of the 2D delayAnims. As Listing Thirty-Eight shows, the difference is that in the 3D version, you use unionGs instead of overs. With this function, you make a list of five copies of the moving light (see Listing Thirty-Nine), using the predefined Haskell function replicate, stagger them in time with delayAnims3, and combine them with a green teapot. Then slow down the animation to see it more clearly.

In Listing Forty and Figure 16 (a moving trail of colored balls), you define a single ball having a spiral motion, which traces the surface of an unseen sphere of radius 1.5 with a longitude angle changing ten times as fast as the latitude angle (five versus one-half radians per second). From this one moving ball, you make ten balls, each a differently colored version, and then stagger them in time with delayAnims3. The coloring function bColor produces evenly spaced hues.

Listing Thirty-Eight

delayAnims3 dt anims =
   unionGs (zipWith later [0, dt ..] anims) 

Listing Thirty-Nine

potAndLights =
   slower 5 (
    withColorG green teapot `unionG`
    delayAnims3 (2/5) (replicate 5 movingLight) )

Listing Forty

spiral3D = delayAnims3 0.075 balls
  where
    ball   = move3 motion (stretch3 0.1 sphereLowRes)
    balls  = [ withColorG (bColor i) ball
             | i <- [1 .. n] ]
    motion = vector3Spherical 1.5 (10*time) time
    n      = 20
    bColor i =
      colorHSL (2*pi * fromInt i / fromInt n) 0.5 0.5 

Listing Forty-One

spiralTurn = turn3 zVector3 (pi*time) (unionGs (map ball [1 .. n]))
  where
    n = 40
    ball i  = withColorG color (
               move3 motion (
                stretch3 0.1 sphereLowRes ))
     where
       motion = vector3Spherical 1.5 (10*phi) phi
       phi    = pi * fromInt i / fromInt n
       color  = colorHSL (2*phi) 0.5 0.5

As a final 3D example, Listing Forty-One presents another spiral. This time you form a static spiral, then turn it about the z-axis.

Related Work

My interest in functional animation originally started with Kavi Arya's "A Functional Approach to Animation," Computer Graphics Forum, 5(4):297-311 (December, 1986). Although elegant, Arya used a discrete model of time. The TBAG system, on the other hand, used a continuous time model, and had a syntactic flavor similar to Fran's; see "TBAG: A High Level Framework for Interactive, Animated 3D Graphics Applications," by Conal Elliott, Greg Schechter, Ricky Yeung, and Salim Abi-Ezzi (Proceedings of SIGGRAPH '94 July, 1994). Unlike Fran, reactivity was handled imperatively. Behaviors were created by means of constraint solving, and updated through constraint assertion and retraction. Concurrent ML introduced a first-class notion of events that can be constructed compositionally; see "CML: A Higher-order Concurrent Language," by John H. Reppy (Proceedings of the ACM SIGPLAN '91 Conference on Programming Language Design and Implementation, 1991). However, those events perform side-effects such as writing to buffers or removing data from buffers. In contrast, Fran event occurrences have associated values -- they help define what an animation is, but do not cause any side effects.

For examples of DirectAnimation, see http://www.microsoft.com/ie/ie40/demos and "Adding Theatrical Effects to Everyday Web Pages with DirectAnimation," by Salim AbiEzzi and Pablo Fernicola (Microsoft Interactive Developer, October 1997).

For background on Haskell, see Introduction to Functional Programming, by Richard Bird and Philip Wadler, (Prentice-Hall, 1987), "A Gentle Introduction to Haskell," by Paul Hudak and Joseph H. Fasel, SIGPLAN Notices, 27(5), May, 1992, and http://haskell.org/tutorial/index.html.

For information on Fran, refer to "Functional Reactive Animation," by Conal Elliott and Paul Hudak, Proceedings of the 1997 ACM SIGPLAN International Conference on Functional Programming (June, 1997), or the Fran web page at http://www.research .microsoft.com/mconal/Fran.

Conclusion

For interactive animation to expand into its potential as a medium of communication, it must become much easier to program. As this article illustrates, one step toward this goal is the replacement of imperative techniques ("how to do") with declarative ones ("what to be").

There are several features I haven't explored here, including sound, smooth flip-book animation, and cropping. There are also many opportunities for improvement: more features for 2D, sound, and 3D; improved efficiency; generation of animation "software components" to integrate with components written in more mainstream programming languages; and support for distributed, multiuser scenarios.

Acknowledgments

Todd Knoblock and Jim Kajiya helped to explore the basic ideas of behaviors and events. Sigbjorn Finne, Anthony Daniels, and Gary Shu Ling helped with the implementation during research internships. Alastair Reid made improvements to the Haskell code, and, along with Paul Hudak and John Peterson, provided helpful discussions about functional animation, how to use Haskell well, and lazy functional programming in general. Becky Elliott cut out the kid pictures, which appear with the kind permission of their owners Patrick, Charlotte, Becky, and Jake.

DDJ


Copyright © 1998, Dr. Dobb's Journal

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.