| ## Java 1.1 Unleashed # - 21 - Basic Animation Programming by Christopher A. Seguin IN THIS CHAPTER The Animator Class Simple Animation Double-Buffered Animation Advanced Tricks of the Trade Example Object Animator History, as the cliché claims, repeats itself. Consider this: Between 4,000 and 6,000 years ago, the Sumerians began communicating using pictograms. In 1827, Joseph Niepce produced the first photographs on a metal plate. Eighty-eight years later, the motion picture camera was created and in 1937, the first full-length animation feature was released. Since then, animation has transformed from a novelty to an art form. We regularly see animation in commercials, television, and movies. The history of the Web is similar. At first, Web pages could contain only text and links to other pages. In the early 1990s, a browser called Mosaic was released that added the ability to incorporate pictures and sound. Mosaic started a flurry of interest in the Internet. But after a while, even the carefully designed Web pages with elaborate background images and colored text began to grow stale. Java, a recent extension to the World Wide Web, allows programs to be added to Web pages. Animations have been available on the Web since the early versions of Mosaic, where Mosaic would download the MPEG file and launch a separate viewer. With the release of Netscape version 1.1, CGI files could use a push-and-pull method of creating animations. The browser would receive instructions to reread the information or read the next URL address after a set delay. The client could keep the connection open and push new information onto the browser every so often. However, this type of animation was available only on Netscape. And it was slow. One of the popular uses of Java is to create animations. Because the Java animations are on the page, they serve to call attention to the Web page, rather than away from the page the way a separate viewer does. Java is also faster than the Netscape method and works on any browser that supports Java. This chapter covers the following topics: The `Animator` class Design of simple animation systems Double buffering Advanced animation techniques The design of simple animation systems is explained with animated text and images. This chapter covers double buffering, which is the easiest way to eliminate animation flicker. The advanced animation techniques include in-betweens, backgrounds, z-order, and collision detection. ## The Animator Class Before we dive into the programming of animation applets, let's start with the hands-down easiest way to create an animation: use someone else's applet. Herb Jellinek and Jim Hagen at Sun Microsystems have created an `Animator` class, an applet that creates an animation. This class is provided as a demonstration program with the Java Development Kit. To use the `Animator` applet, do the following: 1. After installing Sun's Java Development Kit, copy the following three files to your classes directory: `Animator.class, ParseException.class`, and `ImageNotFoundException.class`. These files can be found in the examples provided with the JDK provided by Sun and are located on the CD-ROM that accompanies this book. They should be in a directory called `java/demo/Animator`. 2. Create the image and sound files for your animations. 3. Put the applet tag on your Web page. Table 21.1 shows the parameters that the `Animator` applet reads from your HTML file. 4. View your animation on your favorite Java-enabled browser. #### Table 21.1. Animator applet parameters.
Tag | Description | Default
`IMAGESOURCE` | The directory that contains | The same directory that the image file contains the HTML file
`STARTUP` | The image to be displayed while the program loads the other images | None
`BACKGROUNDCOLOR` | A 24-bit number that specifies the background | A filled, light-gray color of the rectangle that covers the entire applet
`BACKGROUND` | The image to be displayed in | A filled, light-gray the background rectangle that covers the entire applet
`STARTIMAGE` | The number of the first image | `1`
`ENDIMAGE` | The number of the last image | `1`
`NAMEPATTERN` | Specifies the name of the so `T%N.gif` | `T%N.gif` (`%N` means image files number, uses images called `T1.gif`, `T2.gif`,`T3.gif`,...`")`
`IMAGES` | A list of indexes of the images to be displayed, in the order in which they are to be displayed | None
`HREF` | The page to visit on a mouse click | None
`PAUSE` | The number of milliseconds for the pause between frames | `3900`
`PAUSES` | The number of milliseconds for the pause between each frame | The value of `PAUSE`
`REPEAT` | A boolean value: Does the animation cycle through the images? (yes or `true`/no or `false`) | `true`
`POSITIONS` | The coordinates of where each image will be displayed in the applet | (`0`,`0`)
`SOUNDSOURCE` | The directory that contains the sound files | The value of`IMAGESOURCE`
`SOUNDTRACK` | The background music | None
`SOUNDS` | The sounds to be displayed for each image in the animation | None
Most of the tags are straightforward in their use, but some tags need additional explanation. We'll begin with the images and then describe how to use the tags that accept multiple inputs. The default name of images used by the `Animator` class must start with the letter `T` followed by a number. For example, if you have three GIF files that form the changing part of the animation, you can name them `T1.gif`, `T2.gif`, and `T3.gif`. However, you can change this default by using the `NAMEPATTERN` parameter. For example, if your images are called `myImage1.jpg`, `myImage2.jpg`, and `myImage3.jpg`, you would use the following parameter:
    <PARAM NAME=NAMEPATTERN VALUE="myImage%N.jpg">
The background image and startup image have no constraints on their names. There are two ways to specify the order of the images. First, you can specify the first and last image with the `STARTIMAGE` and `ENDIMAGE` tags. If the value of the `STARTIMAGE` tag is greater than the value of the `ENDIMAGE` tag, the images are displayed starting at `STARTIMAGE` and decrementing to `ENDIMAGE`. Second, you can specify the order of the images with the `IMAGES` tag. This tag takes multiple inputs, so let's consider how to give multiple inputs to the `Animator` applet. Several tags take multiple inputs. The `Animator` class has implemented these tags using a `|` as a separator between values. For example, the `IMAGES` tag requires the list of numbers of the images to be displayed. If you want to display the images `T1.gif`, `T3.gif`, and `T2.gif` in that order, you would write this: `<PARAM NAME=IMAGES VALUE=1|3|2>` The `SOUNDS` tag works the same way except that values can be left blank. A blank value in the `SOUNDS` tag means that no sound is played for that image. The `PAUSES` tag also takes multiple inputs, but if an input is left out, it defaults to the standard pause between images. For example, the following statements display the first image for 1000ms (1000 milliseconds), the second image for 250ms, and the third image for 4ms:
    <PARAM NAME=PAUSE VALUE=250>
    <PARAM NAME=PAUSES VALUE=1000||4>
The `POSITION` tag is a set of coordinates. As with the `IMAGES` and `SOUNDS` tags, the coordinates are separated by a `|` character. The x and y values of the coordinate are separated by an `@` character. If a coordinate is left blank, the image remains in the same location as the previous image. For example, if you want to draw the first and second images at (30, 25), and the third image at (100, 100), you would write this code: `<PARAM NAME=POSITION VALUE=30@25||100@100>` The `Animator` class enables you to create an animation quickly and easily. If, however, you have more than one moving object or you want to draw your objects as the animation runs, you have to write your own animation applet. The next section begins with the design of an animator and gives four examples. ## Simple Animation Let's dive right into programming simple animations in Java. Although these animations may not be perfectly smooth in their presentation, they do explain the basic design of an animation. We will also look at what makes a good animation. Let's begin by creating an abstract animated object interface, and then create several examples of the animation in action. ### The AnimationObject Interface When writing a program in an object-oriented language, the first step is to decompose the problem into things that interact. The things, called objects, that are similar are grouped into classes. The class holds all the information about an object. Sometimes, classes are very similar, and you can have a class to represent the similarities of the class. Such a class is called a base class. If the base class doesn't actually store information, but provides a list of methods that all the members of the class have, the class is called an abstract class. Java is an objected-oriented language, so in creating a design for a program, first find similarities in the components of the program. When designing an animation, begin by looking for similarities. Each image or text message that moves is an object. But if you consider these objects, you find that they are very similar. Each object has to be able to paint itself in the applet window. In addition to painting the object, something about the objects is changing (or it wouldn't be an animation). So the object must know when to change. Let's create a class with the following two methods: `paint()`--directs the object to paint itself `clockTick()`--tells the object to change Java provides two ways to create an abstract class. First, if there is some basic method that is used for any object of the class, you can create an abstract class. The methods that are the same can be filled in. If there is no similarity in the actual methods, you can create an interface. The advantage of using an interface is that a class can inherit from multiple interfaces, but it can inherit only from a single class. Because a moving text object and an image have nothing in common other than the names of their `paint()` and `clockTick()` methods, I have created the following interface:
    public interface AnimationObject {
       public void paint (Graphics G, Applet parent);
       public void clockTick ();
    }
This skeleton enables you to simplify the design of the applet object. For example, the `paint()` routine just erases the screen and sends each animation object a `paint()` method:
    public void paint (Graphics g) {
       update (g);
       }
    public void update (Graphics g) {
       // Erase the screen
       g.setColor (Color.lightGray);
       g.fillRect (0, 0, nWidth, nHeight);
       g.setColor (Color.black);
       // Paint each object
       for (int ndx = 0; ndx < AnimatedObjects.length; ndx++)
           AnimatedObjects[ndx].paint (g, this);
       }
For now, assume that the `update()` method and the `paint()` method are essentially the same, although a description of the difference is given later in this chapter in the section on double buffering. The `update()` method is straightforward, but it may cause your animation to flicker. Code to fix the flicker is given in the section on double buffering. The `run()` method is only three steps. First, the applet tells each object that one time unit has passed, and then the applet repaints itself. Finally, the program pauses. Here's what the `run()` method looks like:
    public void run() {
            int ndx = 0;

            // Set the priority of the thread
            Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
            // Do the animation
            while (size().width > 0 &&
              size().height > 0 &&
              kicker != null) {

            for (ndx = 0; ndx < AnimatedObjects.length; ndx++)
               AnimatedObjects[ndx].clockTick ();

            repaint();

            try { Thread.sleep(nSpeed); }
               catch (InterruptedException e) {}

            }
    }
The hard part is initially creating the applet, and that depends on how difficult it is to create each of the animation objects. Let's start with moving text. ### Moving Text NOTE: The code in this section is in Example 21.1 in file `exmp21_1.java` on the CD-ROM that accompanies this book. Everyone has seen animated text, such as weather warnings that slide across the bottom of the TV screen during storms. Let's start with an animation object that moves a text message around the applet drawing area and consider why this is effective. Java provides the `drawString (String s, int x, int y)` routine in the `java.awt.Graphics` class that draws a string at a specific location. To animate text, the applet repeatedly draws the string at a different location. If we want to scroll text across the applet, what do we have to store? First, we need a text message. For this example, assume that the message slides only to the left. (It's easy to extend this code so that the message can also slide right, up, or down.) In addition to the message, we need some internal variables to store the x and y locations of where the message should be printed next. The next question is, "How do you compute when the message is no longer visible?" We have to know about the length of the message and the width of the applet to determine when the message disappears from view and where it should reappear. We won't be able to determine the length of the message until we have the `java.awt.Graphics` object, so we'll postpone this computation until the first time we `paint()` the text message. Let's begin by creating an object that stores each of these values:
    class TextScrolling implements AnimationObject {
        // Internal Variables
        String pcMessage;
        int nXPos;
        int nYPos;
        int nAppletWidth;
        int nMessageWidth;
Now we need to initialize these variables in the constructor method. The constructor needs the text message and the applet width. The other values are computed in the `paint()` method. Here's the constructor:
    public TextScrolling (String pcMsg, int nWide) {
            pcMessage = pcMsg;

            nAppletWidth = nWide;
            nMessageWidth = -1;

            nXPos = nWide;
            nYPos = -1;
            }
Use the `drawString()` method to draw the text message. The `paint()` routine is more complex, however, because we have to compute `nYPos` and `nMessageWidth`. The constructor assigned both of these variables the value `-1` to flag them as unknown values. Now that a `Graphics` object is available, their values can be computed:
    public void paint (Graphics g, Applet parent) {
           if (nYPos < 0) {
               // Determine the y position
               nYPos = (g.getFontMetrics ()).getHeight ();

               // Determine the size of the message
               char pcChars [];
               pcChars = new char [pcMessage.length()];
               pcMessage.getChars (0, pcMessage.length(),
                                   pcChars, 0);
               nMessageWidth = (g.getFontMetrics ()).charsWidth
                                   (pcChars, 0, pcMessage.length());
               }
           // Draw the object here
           g.drawString (pcMessage, nXPos, nYPos);
           }
TIP: Drawing in an applet is easy because the applet draws only the graphics that fall inside its boundaries. This process is called clipping--limiting the drawing area to a specific rectangle. All graphics output that falls outside the rectangle is not displayed. You can further limit the region where the graphics are drawn using `java.awt.Graphics.clipRect()`. Now, whenever the clock ticks, the message shifts to the left. You can do this by adjusting the `nXPos` variable. We reset the `nXPos` whenever the message is no longer visible:
    public void clockTick () {
           // Do nothing until the message width is known
           if (nMessageWidth < 0)
               return;
           // Move Right
           nXPos -= 10;
           if (nXPos < -nMessageWidth)
               nXPos = nAppletWidth - 10;
           }
    // END of TextScrolling Object
    }
At this point, I could point out the lack of computation in the `paint()` and `clockTick()` methods and say how it is important to avoid extensive computations during an animation. But either you already know that, or you would discover it very quickly with the first complex animation you write. How can you avoid complex computations? You have two options: Perform the computations offline Do each computation once and save the results In this animation object, the value of the variables `nMessageWidth` and `nYPos` were computed once in the `paint()` routine. Before then, the information wasn't available. Let's consider some more examples. First, we'll write two programs to display a series of images in an applet and to move a single image around an applet. These programs demonstrate the first possibility. For the second possibility, we draw and copy a stick person. ### Images In the past ten years, computer animations of many different objects have been created using the physical equations that model movement. Interactions between rigid bodies are easy to animate, but more advanced techniques have created realistic animations of rubber bands and elastic cubes that deform as they interact. The computations required for these are extensive and are not suitable for the online nature of applets. The first animation object uses the flip-book principle of animation. For this approach, you generate all the pictures in advance and allow Java to display the images in sequence to create the illusion of motion. This second approach is useful for rigid body motion and interactions, where you take a single image and move it around the applet drawing area. But first, let's review some information about images. #### The MediaTracker Class Images take a while to load into the computer's memory, and they look very strange if you display them before they are completely ready. Have you ever seen the top of a head bouncing around a Web page? Very unnerving `:-)` To avoid this gruesome possibility, the creators of Java have provided a `MediaTracker` class. A `MediaTracker` object enables you to determine whether an image is correctly loaded. NOTE: Although the `MediaTracker` class will eventually be able to determine whether an audio object has loaded correctly, it currently supports only images. The `MediaTracker` object provides three types of methods: Those that register or check in images Those that start loading images Those that determine whether the images are successfully loaded The methods that register images are named `AddImage()`; there are two versions of this method: `AddImage (Image` img`, int` groupNumber`)` begins tracking an image and includes the image in the specified group `AddImage (Image` img`, int` groupNumber`, int` width`, int` height`)` begins tracking a scaled image You can organize images with the group number. Doing so enables you to check logical groups of images at once. The methods that start loading the images are listed here: `checkAll (true)` starts loading all the images and returns immediately `checkID (int` groupNumber`, true)` starts loading all the images in the group specified by groupNumber and returns immediately `waitForAll()` starts loading all images and returns when all images are loaded `waitForID(int` groupNumber`)` starts loading all images in the group specified by groupNumber and returns when all images in the group are loaded NOTE: In `checkAll()` and `checkID()`, the last input is `true`. It is not a variable but the `boolean` constant. Because the routines that start with `check` return immediately, you can continue with other processing and occasionally monitor the progress with `checkID(groupNumber)` and `checkAll()`. TIP: Be careful not to monitor an image that has already been loaded. You should only track the loading of an image once. If you ask the media tracker to wait for an image that has already been loaded, it might never return. The final two methods that determine whether the images loaded successfully are shown here: isErrorAny() returns `true` if any errors were encountered loading any image isErrorID`(int`groupNumber`)` returns `true` if any errors were encountered loading the images in the specified group Now we are ready to start working with the image object animators. Let's begin with a changing image animation. Then we'll discuss a special type of changing image where all the individual frames are tiled in one large image and a different frame is shown at each time step. Finally, we'll create a moving image animation. #### Changing Images The flip-book method of animation in Java is the most popular on Web sites. Flip books contain pictures but no words. The first picture is slightly different from the second, and the second picture is slightly different from the third. When you thumb through a flip book as shown in Figure 21.1, the pictures appear to move. In this section, we create an applet that takes a series of images and repeatedly displays them in the applet window to create the illusion of motion. NOTE: The code in this section is in Example 21.2 in file `exmp21_2.java` on the CD-ROM that accompanies this book. This program has to store two values: the images to be displayed and the `MediaTracker` to determine whether the images are ready. Internally, we will also keep track of the number of the image to be displayed next:
    class ChangingImage extends AnimationObject {
       // Internal Variables
       Image ImageList [];
       int nCurrent;
       MediaTracker ImageTracker;
FIGURE 21.1. Thumbing through a flip book creates the illusion of motion. The constructor initializes the variables with the constructor's inputs and starts the animation sequence with the first image:
    public ChangingImage (Image il[], MediaTracker md,
                    Applet parent) {
           ImageList = il;
           nCurrent = 0;
           ImageTracker = md;
           }
As mentioned earlier, it is important to check that the image is available before it is drawn:
    public void paint (Graphics g, Applet Parent) {
           // Draw the object here
           if (ImageTracker.checkID(1)) {
               g.drawImage (ImageList [nCurrent], 100, 100, Parent);
               }
           else
              System.out.println
                 ("Not Ready Yet " + (nCurrent+1));
           }
Remember that this object is only one part of a possibly large animation; you may have to sacrifice the first few pictures to keep all the parts of the animation together. Therefore, the object doesn't check the `ImageTracker` to see whether the images are ready in the `clockTick` method:
    public void clockTick () {
           nCurrent++;
           if (nCurrent >= ImageList.length)
               nCurrent = 0;
           }
    // END of ChangingImage Object
    }
With this approach, most of the work is done ahead of time, either as you draw all the images or by a computer program that generates and saves the images. In Java, this method is how you animate objects with elastic properties or realistic lighting because of the amount of computation involved. #### Tiling Image Frames NOTE: The code in this section is in Example 21.3 in file `exmp21_3.java` on the CD-ROM that accompanies this book. One problem with loading multiple images is that you have to make a separate connection for each image file you want to download. This operation takes extra time because of the overhead involved in making a connection back to the server. One way to get around this delay is to place all the images side by side in a single image file. Figure 21.2 shows an example of laying the images side by side. FIGURE 21.2. Frames are stored side by side in a single image. With this arrangement, you have to read only one large image from the server--hence, you make only one connection. But how do you draw the image since you don't want to write all the images onto the screen at the same time? The solution is to use `java.awt.Graphics.clipRect()`. First, block out the area we want to draw in. This should have the x and y coordinates of where you want the image to be, and the size of the frame. Let's call the location of the image `nX` and `nY`; call the width and height of the frame `nFrameWidth` and `nFrameHeight`:
    public void paint (Graphics g, Applet Parent) {
       // Draw the object here
       g.clipRect (nXPos, nYPos, nFrameWidth, nFrameHeight);
       g.drawImage (Picture, nXImagePos, nYPos, Parent);
       }
The tricky part was recognizing this use of the `clipRect()` method. The only thing left is to explain the `nXImagePos` variable. All we have to do is shift what part of the image is in the visible part. The shifting is done in the variable `nXImagePos`. Let's say that we have `nImageWidth` that stores the width of the entire image (with all the frames); when we do a clock tick, we just decrease the `nXImagePos` until it becomes less than the `nImageWidth` parameter, at which point we reset it to zero:
    public void clockTick () {
        nXImagePos -= nFrameWidth;
        if (nXImagePos <= nImageWidth)
            nXImagePos = 0;
        }
Because that is so easy, why isn't it done all the time? Well, you will run into problems with this approach if you use multiple changing images in your animations. `clipRect()` computes the intersection of the current clipping rectangle (which initially is the entire area of the applet) and the rectangle that was just given as the input to `clipRect`. So there is no way to make the clipping region bigger! This `AnimationObject` must be the only one of its type, and it must be the last object to be painted. If only there was a way to break up the framed image once you've loaded it into the applet.... We don't have the mechanism yet to do this operation, but I promise to show you how before the end of the chapter. #### Moving Images NOTE: The code in this section is in Example 21.4 in file `exmp21_4.java` on the CD-ROM that accompanies this book. For rigid bodies, there is an easier way to create a 2D animation: You can take an image of the object and move it around the applet drawing area. An example of a rigid body is a rock or a table (they don't deform or change while they move). A cube of gelatin, on the other hand, wiggles as it moves and deforms when it runs into another object. The `MovingImage` class is very similar to the `ChangingImage` class described in the preceding section. The variables are a picture and the x and y locations where it will be drawn. In this object, the `nCurrent` variable keeps track of the location in the object's path, rather than the image:
    class MovingImage extends AnimationObject {
       // Internal Variables
       Image Picture;
       MediaTracker ImageTracker;
       int nCurrent;

       int pnXPath [];
       int pnYPath [];
The constructor for `MovingImage` is nearly identical to the constructor for `ChangingImage` except that it has two extra variables to save:
    public MovingImage (Image img, MediaTracker md,
                    int pnXs [], int pnYs [],
                    Applet parent) {
    Picture = img;
    nCurrent = 0;

    ImageTracker = md;

    pnXPath = pnXs;
    pnYPath = pnYs;
    }
Instead of changing images, we simply draw the image at the next location in the path:
    public void paint (Graphics g, Applet Parent) {
       // Draw the object here
       if (ImageTracker.checkID(1))
           g.drawImage (Picture,
                        pnXPath[nCurrent], pnYPath[nCurrent],
                        Parent);
    }
The `clockTick()` program is nearly identical to the method written for the `ChangingImage` object. ### The Copy Area NOTE: The code in this section is in Example 21.5 in file `exmp21_5.java` on the CD-ROM that accompanies this book. Remember that we are trying to minimize the amount of computation performed during an animation. If we don't have an image we can use, we have to draw the image using graphic primitives. Suppose that we want to slide a stick figure across the applet. Here is a small method that draws a stick person at a specified location: `public void drawStickFigure (Graphics g, int nX, int nY) {` g.drawOval (nX + 10, nY + 20, 20, 40); g.drawLine (nX + 20, nY + 60, nX + 20, nY + 100); g.drawLine (nX + 10, nY + 70, nX + 30, nY + 70); g.drawLine (nX + 10, nY + 150, nX + 20, nY + 100); g.drawLine (nX + 20, nY + 100, nX + 30, nY + 150); } The original stick figure is drawn in `black` on a `lightGray` background. To continue the animation, we can erase it by redrawing the figure in `lightGray` over the `black` figure, and then drawing a new figure in `black` a little to the right. For such an uncomplicated drawing, this method is effective. For the purpose of this illustration, however, let's animate the stick figure using the `copyArea()` method:
    public void paint (Graphics g, Applet Parent) {
           if (bFirstTime) {
                bFirstTime = false;
                drawStickFigure (g, nX, nY);
                }
           else {
                g.copyArea (nX, nY, 35, 155, 5, 0);
                }
           }
The first time the `paint()` method is called, the figure is drawn using the `drawStickFigure()` routine. After the first time, the `paint()` routine recopies the stick figure a few pixels to the right. To erase the old copy of the figure, some blank space is copied with the stick figure. The result: the figure slides across the applet. There are two problems with this animation so far. The first problem is that sometimes part of the stick figure is left behind. When you make a `repaint()` method call to draw the next frame in the animation, you sometimes get repainted and you sometimes do not. Whether or not the `repaint()` call is processed depends on how much additional computation is being done at the time. If multiple repaint requests have been made since the drawing thread has last been allowed to execute, it combines all of these repaints into a single call. To solve this problem, you just have to remember where the stick figure was last drawn in the applet window, and copy the figure with extra blank space from there. You can do this easily in the `paint()` routine:
    public void paint (Graphics g, Applet Parent) {
        static int nLastX;
        if (bFirstTime) {
            bFirstTime = false;
            drawStickFigure (g, nX, nY);
            nLastX = nX;
            }
        else {
            int nSkipped = nX - nLastX;
            g.copyArea (nX - (nSkipped - 5), nY, 30 + nSkipped, 155, nSkipped, 0);
            nLastX = nX;
            }
    }
The second problem is that our previous animations repeatedly cycle across the screen. Once our little figure is out of the viewing area, it is gone for good. If only there was a way to draw an image in Java and save it. Then we could use the animation techniques in the previous section to move the image around the applet drawing area repeatedly. Fortunately, such a facility is available in Java. In addition to enabling us to create an image offline so that it can be used repeatedly, this facility generates a cleaner flicker-free animation. ## Double-Buffered Animation Double buffering is a common trick to eliminate animation flicker. Instead of drawing directly onto the applet's drawing area where a person can see it, a second drawing area is created in the computer's main memory. The next frame of the animation is drawn on the drawing area in main memory. When the frame is complete, it is copied onto the screen where a person can see it. It sounds complicated, but it is really quite simple. To double buffer your animation, you have to do the following: Create an off-screen image and get the associated graphics object Draw on the off-screen graphics object Copy the off-screen image onto the applet's graphic object NOTE: An example that shows the flickering effect and the improvement created by using double buffering is in Example 21.6 in file `exmp21_6.java` on the CD-ROM that accompanies this book. The first step requires that you create an image in which you will do all the work. To create the off-screen image, you must know the height and width of the drawing area. Once that is determined, you can get the graphics object from the image with the `getGraphics()` method:
    offScreenImage = createImage(width, height);
    offScreenGraphic = offScreenImage.getGraphics();
The graphics object extracted from the image is now used for all drawing. This part of the program is the same as the `paint()` program explained in "Simple Animation," earlier in this chapter--except that, instead of using `g`, you use `offScreenGraphic:`
    // Erase the screen
    offScreenGraphic.setColor (Color.lightGray);
    offScreenGraphic.fillRect (0, 0, width, height);
    offScreenGraphic.setColor (Color.black);
    // Paint each object
    for (int ndx = 0; ndx < AnimatedObjects.length; ndx++)
        AnimatedObjects[ndx].paint (offScreenGraphic, this);
    Finally, you copy the off-screen image into the applet's graphics object:
    g.drawImage(offScreenImage, 0, 0, this);
You have succeeded in improving the clarity of your animation. You may wonder why this change improves the animation. After all, the number of pixels that are drawn has increased! There are three reasons for this improvement: Most machines have an efficient way to copy a block of bits onto the screen, and an image is just a block of bits. No extra computations interrupt the drawing of the picture. These extra computations come from drawing lines, determining the boundaries of a filled area, and looking up fonts. Video memory cannot be cached in the CPU, while an off-screen image can be anywhere in memory. All the image appears at once, so even though more work is done between frames, a human perceives that all the work is done instantaneously. Now we have reduced the flicker by creating an off-screen image. In the first section, the theme was to reduce the computation the applet performed. Using the off-screen image increases the computations but improves the visual effect. Now we will eliminate the extra computations in the program by using the off-screen image. ### The update() and paint() Methods NOTE: To see when the `paint()` and `update()` methods are called, the `ChangingImage` example has been modified to print a message to the output describing which method was called. This method is in Example 21.7 in `exmp21_7.java` on the CD-ROM that accompanies this book. Earlier, I said that the `paint()` and `update()` methods were essentially the same. In fact, most of the sample code that Sun Microsystems provides does basically what I did in the "Simple Animation" section: the `update()` method calls the `paint()` method. So what is the difference? The `paint()` method is called when the applet begins execution and when the applet is exposed. An applet is said to be exposed when more area or a different area can be viewed by the user. For example, when an applet is partially covered by a window, it must be redrawn after the covering window is removed. The removal of the covering window exposes the applet (changes the screen to enable the user to see more of a viewing area of a window). See Fig- ure 21.3 for an example. FIGURE 21.3. Moving another window exposes the applet. The `update()` method is called whenever a `repaint()` method is called. For example, in the `run()` method of the applet, a `repaint()` method is called on every iteration through the loop. So what does that mean? If the `paint()` method calls the `update()` method, the applet does extra work by recomputing what the image should be. Yet less than a second ago, `update()` created a perfectly good picture--and it is still available for `paint()` to use. It is better if the `paint()` method copies the image created by `update()` onto the screen again. Here is a more efficient pairing of `paint()` and `update()`:
    public void paint (Graphics g) {
        if (offScreenImage != null)
            g.drawImage(offScreenImage, 0, 0, this);
        }
    public void update (Graphics g) {
        if (offScreenImage != null) {
           // Erase the screen
           offScreenGraphic.setColor (Color.lightGray);
           offScreenGraphic.fillRect (0, 0, nWidth, nHeight);
           offScreenGraphic.setColor (Color.black);

          // Paint each object
          for (int ndx = 0; ndx < AnimatedObjects.length; ndx++)
               AnimatedObjects[ndx].paint (offScreenGraphic,
                                           this);
          g.drawImage(offScreenImage, 0, 0, this);
          }
    }
TIP: One problem with this approach is that the `paint()` method is called as the applet begins running. At this time in the execution of the applet, there is no image to display because `update()` has not yet been called. The effect: The first screen the user sees is a filled white rectangle that covers the entire applet. You can remedy this situation by printing a text message in the off-screen image when it is created. Because it takes only three lines of code to overload the `paint()` and the `update()` methods, we've been doing this from the very beginning. However, you don't have to overload the `update()` method. You could just put all your drawing commands in the `paint()` method, and your code would work just fine. The default method for `update()` erases the screen with the background color and calls the `paint()` method. And our earlier code has already showed us what happens if you do this--it creates lots of flicker. If you want a cleaner-looking animation, you should always overload the `update()` method. ### Breaking Tiled Images Earlier in this chapter, I said that the problem with using tiled images to store the frames of an animation is that you can use only one of them. But now that we have the mechanism to create an off-screen image, we can break the tiled image into multiple off-screen images after the image with the frames is loaded. The easy way to do this is to create a new object similar to the `ChangingImage` object. Most of the code is identical to the `ChangingImage` object except that once the `MediaTracker` reports that the image is completely loaded, the object should break the image into its separate frames. We can do this in the `prepare()` method. First, we create an array of the images called `ImageList`. Then we create an off-screen image for each. Now we just act as if we are doing double buffering--we get the graphic and draw each frame using the graphic:
    private void prepare () {
        ImageList = new Image [nFrameCount];
        Graphic tempGraphic;
        for (int ndx = 0; ndx < nFrameCount; ndx++) {
             ImageList [ndx] = createImage(nFrameWidth, nFrameHeight);
             tempGraphic = pimgFrames [ndx].getGraphics();
             tempGraphic.drawImage (imgAllFrames, -1 * ndx * nFrameWidth, 0, this);
             }
    }
The double-buffered approach is one of the most widely used algorithms to improve animation. Part of the reason that this algorithm is widely used is that it is so simple. The next section discusses other algorithms that help improve your animation or simplify your life. ## Advanced Tricks of the Trade The following sections present four tactics for better animations. First, you learn about in-betweens, which reduce the amount of time you spend creating the animation. Second, backgrounds provide an alternative method to erase the screen and spice up the animation. Third, we consider the order in which images are drawn in the applet to produce the illusion of depth. Finally, we discuss collisions. ### In-Betweens When the first movie-length animation was finished, it required over two million hand-drawn pictures. Because there were only four expert artists, it would have been impossible for these people to create the entire film. Instead, the master artists drew the main, or key, frames (the frames specified by the creator of the animation). Other artists drew the in-between frames (frames created to move objects between their key positions). This approach is used today to create animations, except that now the computer creates the in-between frames. Generally, a computer needs more key frames than does a human artist because computers don't have common-sense knowledge about how something should move. For example, a falling rock increases speed as it falls (which is obvious), but a computer does not know this obvious fact. You can compensate for the computer's lack of common sense by specifying how the objects move between key positions. Four common trajectories are shown in Figure 21.4. FIGURE 21.4. These graphs show four different trajectories for moving objects. To see how in-betweens work, consider Figure 21.5, which shows a ball moving upwards using the first acceleration trajectory from Figure 21.4. At first, the ball object moves slowly, but then the distance between successive positions of the ball increases. The successive positions of the balls are numbered, with 1 being the starting image and 10 being the final image. FIGURE 21.5. The motion of an image corresponds to the location on the trajectory graph. You can create these object paths with the following code:
    class InBetweenGenerator extends Object {

        public int[] InBetweens (int pnValues[], String sTypes[],
                                 int pnWidths [])
            {
            int pnResult [];
            int ndx;
            int nArraySize = 1;

    // Create the array of the correct size
    for (ndx = 0; ndx < pnWidths.length; ndx++) {
         nArraySize += 1 + pnWidths [ndx];
         }

    pnResult = new int [nArraySize];

    // Fill in the array
    int nItem = 0;
    for (ndx = 0; ndx < pnWidths.length; ndx++) {
         pnResult [nItem] = pnValues [ndx];
         fillIn (pnResult, nItem + 1,
                 pnWidths[ndx], sTypes [ndx],
                 pnValues[ndx], pnValues[ndx+1]);
         nItem += (pnWidths[ndx] + 1);
         }
    pnResult [nItem] = pnValues [pnWidths.length];
    return pnResult;
    }
First, we call the method `InBetweens()`, which creates an array and fills it in. Each segment of the array can contain a different type of motion. For each type of motion, we will fill in a separate portion of the array using the method `fillIn()`:
    void fillIn (int pnArray [], int nStart, int nHowMany,
                 String sType, int nLow, int nHigh)
        {
        double dIncr = 1.0 / ((double) (nHowMany + 2));
        double dTemp = dIncr;

        for (int ndx = 0; ndx < nHowMany; ndx++) {
             pnArray [ndx + nStart] = (int) (nLow +
                  (nHigh - nLow) * interpolate (sType, dTemp));
             dTemp += dIncr;
             }
    }
How these values are filled in depends on the type of interpolation occurring between each point. We can compute the actual value with the following method:
    double interpolate (String sType, double dValue)
       {
       if (sType.equalsIgnoreCase ("c")) {
           return dValue;
           }
       else if (sType.equalsIgnoreCase ("a")) {
           return dValue * dValue;
           }
       else if (sType.equalsIgnoreCase ("d")) {
           return (dValue * (2.0 - dValue));
           }
       else { // Both
           if (dValue < 0.5) {
              double dTemp = 2.0 * dValue;
              return 0.5 * dTemp * dTemp;
              }
       else {
            double dTemp = 2.0 * dValue - 1.0;
            double dTemp2 = 0.5 * dTemp * (2.0 - dTemp);
            return 0.5 + dTemp2;
            }
        }
       }
    // End of InBetweenGenerator
    }
In-betweens reduce the amount of information an animation artist must enter to create the desired animation. The next section presents a few hints for using a background image. Basically, a background image is a combination image that contains all the unchanging pictures. Using a background can reduce the computations involved in the animation. NOTE: One thing in-betweens allow you to do is to specify the velocity or acceleration of the object. ### Background Another trick animators use is to draw the background on one sheet of paper and then draw the characters and moving objects on a piece of plastic. Because the background remains the same, it is drawn once. By overlaying the different plastic pictures, you can create the illusion of movement in a world. The same trick can be used with a computer. You first create a background image and save it. Then, instead of erasing the image with the `fillRect()`, you use `drawImage()` to initialize the off-screen image and draw the moving parts on top of the background. The borders of your moving images should be transparent--just as though the characters were drawn on plastic. The GIF89a graphics image format allows you to designate one of the colors in a GIF image as transparent. When Java recognizes one of the pixels in the image as being the transparent color, it does not draw anything, so the background shows through. You can either create your images with a software program that allows you to specify the transparent color, or you can use a separate program to read the graphics file and set the transparent color. One program that allows you to specify the transparent color is called `giftrans`, available at anonymous ftp sites such as this one: `http://www-genome.wi.mit.edu/WWW/tools/graphics/giftrans/` This site contains a DOS executable file and the C source code. Many more sites have this program, and you can find at least 20 more by searching Lycos or Yahoo! with the keyword `giftrans`. NOTE: You can load either JPG or GIF images for animations in Java, but there is no facility to specify transparent regions in images stored in the JPG format. ### Z-Order You will notice in your animations that some objects appear to be in front of others because of the way they are painted. For example, the last object painted appears to be the closest object to the viewer. Such an arrangement is called a 2-1/2- dimensional picture. We can specify the order in which the objects are drawn by associating a number with each `AnimationObject`. This number reflects how far forward in the viewer's field of vision the object is. This number is called the z coordinate, or the z-order number. Because the x axis is generally from left to right on the screen, and the y axis is up and down, the z axis comes out of the screen at you. The z-order number can be stored in a base class from which the other animated objects are subclassed. Let's call this new object a `DepthObject`. Although we could store an integer in this class of objects, we might tempt users of the `DepthObject` to manipulate the number directly. Instead, we'll use good data-hiding techniques and require the implementation of a method that returns the z-order number:
    public interface DepthObject implements AnimationObject {
         public int queryZOrder ();
       }
The next step is to create an object to store all the animated objects and keep them in order. This object should be able to add a new animated object at any time, as well as paint each object and inform them all of the passage of time:
    public class ThreeSpaceAnimation {
       Vector vecAnimations;

       public int addElement (DepthObject doNew) {
       int nStart = 0;
       int nEnd = vecAnimations.size ();
       int nOrder = doNew.queryZOrder ();

       try {
           while (nStart + 1 <= nEnd) {
               int nCurrent = (nStart + nEnd) / 2;
               int nCurrentDepth = ((DepthObject) vecAnimations.elementAt
                                     (nCurrent)).queryZOrder ();
               if (nCurrentDepth < nOrder)
                   nStart = nCurrent;
               else if (nCurrentDepth == nOrder) {
                    nStart = nCurrent;
                    nEnd = nCurrent;
                    }
               else
                  nEnd = nCurrent;
               }
             if (((DepthObject) vecAnimations.elementAt (nStart)).queryZOrder () >
                    nOrder) {
                  // Insert before
                  vecAnimations.insertElementAt (doNew, nStart);
                  }
               else if (((DepthObject) vecAnimations.elementAt (nEnd)).queryZOrder ()
                          < nOrder) {
              // Insert after
              vecAnimations.insertElementAt (doNew, nEnd+1);
              }
            else {
                   // Insert between
                   vecAnimations.insertElementAt (doNew, nEnd);
                   }
                   } catch (ClassCastException cce) {}
            }
       public void clockTick () {
            allAnimationObjects = vecAnimations.elements ();
            while (allAnimationObjects.hasMoreElements ()) {
                 try {
                     AnimationObject aniObj = (AnimationObject)
                                               allAnimationObjects.nextElement ();
                     aniObj.clockTick ();
                     } catch (ClassCastException cce) {}
             }
        }
       public void paint (Graphics g, Applet parent) {
                                                allAnimationObjects = vecAnimations.elements ();
             while (allAnimationObjects.hasMoreElements ()) {
                 try {
                     AnimationObject aniObj = (AnimationObject)
                                               allAnimationObjects.nextElement ();
                     aniObj.paint (g, parent);
                     } catch (ClassCastException cce) {}
             }
       }
Before we finish with this class, note that the way `DepthObject` was created allows the object to change its depth at any time. To make changing the depth more efficient, we will ask the object to send a message to the `ThreeSpaceAnimation` to inform it of the change. The method to inform the object is called `informDepthChange` and it takes the object that is changing as input:
    public void informDepthChange (DepthObject obj) {
         vecAnimations.removeElement (obj);
         addElement (obj);
         }
### Collision Detection One final item to cover is how you can tell if two objects have collided. In many animations where the path of each object is predetermined, this question is not important. But sometimes, you may want to create an animated object controlled by the user (in a video game, for example). In such situations, it becomes important to determine when two objects collide. The easiest way to detect collisions is to simply consider whether the two images overlap. This calculation is easy because we can extract the size of the images from the images themselves. But it is not very accurate, especially if there are transparent pixels along the edge. For an example of this approach, see Figure 21.6. FIGURE 21.6. An example of collision detection using a rectangle covering the entire image. The hard way to detect a collision is to check each pixel in each image and see whether two non-transparent pixels are at the same location. Figure 21.7 shows this option. This is a very costly operation because you have to check each pixel; this approach is really not suited to an interpreted language like Java. FIGURE 21.7. Exact collision detection is possible if you compare nontrans- parent pixels. As a compromise, a third method is suggested. You can associate with each image a smaller rectangle that lies inside the image. This smaller rectangle more closely matches where the nontransparent pixels are (see Figure 21.8). There are two reasons that this algorithm is better. First, comparing two rectangles to determine whether they intersect is much quicker than comparing all the colored pixels of each image to determine if they overlap. Second, a large portion of the image near the edge may be transparent. Generally, you don't want to make this transparent region an invisible force field that transforms near misses into collisions. If you choose the correct rectangle for each image, the rectangle will cover most of the colored pixels and not too many of the transparent pixels. Choosing the best rectangle is the hard part, and I can't help you there, but we can plan to make collision detection a part of the animation design by creating an interface. This interface, called `Collidable`, returns the bounding rectangle. You determine whether an object can collide by casting it to an instance of this interface. If it can be cast, then you check its rectangle with the object that it collides with.
    public interface Collidable {
       public Rectangle queryHitArea ();
       }
With this interface, we can check to see whether two animated objects collide with the following code:
    if ((animatedObject1.queryHitArea()).intersects (animatedObject2.queryHitArea())) {
       // The two objects have collided
       }
    else {
       // The two objects missed each other
       }
This algorithm makes use of the `Rectangle` object provided in `java.awt`. FIGURE 21.8. Using a smaller rectangle for detecting collisions is faster and has fewer errors than using the image's entire area. ## Example Object Animator Now let's take some of the ideas discussed in this chapter and make a multiple-object animator. In this section, we'll see some of the highlights of this chapter. First, we want the code to be able to read from the HTML file the number and motion of the animation objects. Second, we'll review how some of the advanced animation techniques are used in this example. NOTE: The full source code for this example is provided on the CD-ROM that accompanies this book. The code for this example is in `exmp21_8.java`. To enable the program to read the motion specifications from the applet tag, we have to do the following: Store the images and animation objects in a vector Generalize the `ChangingImage` and `MovingImage` to a single object Add routines to the `init()` method to load the required information The first step is to store the images and the animation objects in a vector. The applet then dynamically stores however many objects or images there will be in the animation. For more information about how to use a vector, see Chapter 11, "The Utilities Package." The effect of an `AnimatedImage` object is a moving changing picture. It requires an array of images and two arrays for the path the object travels. However, the `AnimatedImage` object sometimes is used as a `ChangingImage` object, and it would be easier on the user if numbers could be passed for the path variables. The constructor has been overloaded for this purpose. The other change is the use of a vector to store the images, which allows us to store an arbitrary number of images. The `init()` method is standard, but the applet generates the tags it needs as it runs. It does this by concatenating strings with numbers: `String at = getParameter ("IL" + 3);` This arrangement enables us to load an arbitrary number of animation objects. For the tags and what they mean, consult Table 21.2. #### Table 21.2. Tags for exmp21_8.java (located on the CD-ROM).
Tag | Description
`IL?` | Image letter, what should prefix the image filenames
`IC?` | The number of images
`IO?` | Image offset, which image you should start with
`KF?` | The number of key frames
`X?_?` | The x coordinates of the key frame
`Y?_?` | The y coordinates of the key frame
`XT?_?` | The type of motion between x values
`YT?_?` | The type of motion between y values
`W?_?` | The number of points between values
The first `?` in the tag refers to the number of the animated object. The second `?` in the tag is the number of the frame. For example, `X3_7` refers to the x value of the third animated object in the seventh key frame. Now consider some of the advanced techniques and how they were used in this example. This program provides the functionality to create in-betweens and to use a background image. To keep the animation from flickering, this code also uses the double-buffered approach to painting the image. The paths of the moving objects are specified in the HTML file, but they are filled in as suggested by the user with the `InBetweenGenerator` object. This object requires three values: the key locations, how many frames to produce between each set of key locations, and the type of motion between each key location. It then generates an array of integers that represent the path that object traverses. To extend the `InBetweenGenerator` object to create another type of motion between endpoints, rewrite the `interpolation()` method to include your new motion. All the equations in this method take a number between 0 and 1, and return a number between 0 and 1. However, if you want the object to overshoot the source or the destination, you can create equations that produce values outside this range. Notice that a background image is an image that doesn't move and doesn't change. To create a background for the animation, just make the background image the first animated object. FOR MORE INFORMATION Here are some Web sites you can browse for more information about animations. The best collection of Java applets is Gamelan, and it has a page on animations. Its URL is as follows: `http://www.gamelan.com/Gamelan.animation.html` The following sites have many links to animations. Viewing some of these sites can give you ideas for your animations: `http://www.xm.com/cafe/AnimatePLUS/slideshow.html` Description of the slideshow applet `http://www.auburn.edu/~harshec/WWW/Cinema.htm` An example of the slideshow in action `http://www-itg.lbl.gov/vbart` BART schedule animation `http://www.intrinsa.com/personal/steve/ClickBoard/ClickBoard.htm` Interactive animation `http://www.sealevelsoftware.com/sealevel/javademo.ht` Animation of falling raindrops `http://www.dimensionx.com/dnx/StreamingAnimation/../index.htm` Smooth-loading animation `http://www.geom.umn.edu/~daeron/apps/flag.htm` United States flag blowing in the wind More information about animation can be found at this URL: `http://java.sun.com/people/avh/javaworld/animation/` ## Summary In this chapter, we created the `AnimationObject` interface that simplifies design. This animation class was then used to create moving text, to create a flip-book-style animation, to animate an image that contains frames, and to create a moving-image animation. Double buffering was used to eliminate flicker, and we discussed the difference between the `paint()` and `update()` methods. The final algorithms discussed were the use of in-betweens, background images, z-order numbers, and collision detection. What really launched Java into the spotlight for many people was its capability to perform animations on the World Wide Web. With the release of Netscape 2.0, Java is not the only way to create animations on Web pages. Increasingly, there are specialized programs that help you create animations that can be visible on Web pages. One of these is Shockwave for Director, which enables you to create animations in Director; Shockwave also allows the animations to be viewed. VRML is a language that specifies three-dimensional animations by specifying the locations, objects, and a viewing path. However, VRML currently requires special graphics hardware to view. It seems, then, that Java is still the least expensive way to create an animation.
| ©Copyright, Macmillan Computer Publishing. All rights reserved.