July 01, 1998
Composing Reactive AnimationsProgramming for greater freedom of expressionConal Elliott
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.
Conal is a member of the Microsoft Research Graphics Group. He can be contacted at conal@microsoft.com.
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:
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)".)
CompositionComposition 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.
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 AnimationUp 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.
Composition-in-TimeOperations 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. Figure 8 and Listing Seventeen present a simple use of delayAnims. Next, use delayAnims (Listing Eighteen) to define mouseTrailWords that makes animated sentences. Figure 9 and Listing Nineteen is a use of trailWords following a specified path, while Listing Twenty follows the mouse.Reactive AnimationThe 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.
Cyclic ReactivityTo 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).
SelectionFigure 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-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).
3D AnimationDeclarative 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 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-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-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-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.
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
|
|
|||||||||||||||||||||||||
|
|
|
|