JMU
Basics of Computer Animation
in Java


Prof. David Bernstein
James Madison University

Computer Science Department
bernstdh@jmu.edu


Definitions
Techniques to Consider
Sampling Dynamic Visual Content

An Illustration

images/dynamic-sampling.gif
Terminology for Sampled Dynamics
Some Observations
Describing Dynamic Visual Content
Terminology for Described Dynamics
What's Involved?
A Simple Example

An Animated Rectangle

javaexamples/multimedia/AnimatedRectangle.java
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;

/**
 * A simple example of ad-hoc animation
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class AnimatedRectangle extends JComponent implements ActionListener
{
    private float        direction, height, width, x, y;
    private Line2D.Float ground;    
    private Timer        timer;


    /**
     * Default Constructor
     */
    public AnimatedRectangle()
    {
        super();

        width = 50.0f;
        height = 50.0f;

        x = 100.0f;
        y = 100.0f;
        direction = 1.0f;

        ground = new Line2D.Float(0f,480f,480f,480f);        

        timer = new Timer(10, this);
        timer.start();
    }



    /**
     * Handle actionPerformed messages generated by the Timer
     * (Required by ActionListener)
     *
     * @param evt    The ActionEvent
     */
    public void actionPerformed(ActionEvent evt)
    {
        if      (y <= 100)        direction = 1.0f;
        else if (y+height >= 480) direction = -1.0f;

        y += direction;

        repaint();
    }




    /**
     * Render this Painting
     *
     * @param g   The rendering engine to use
     */
    public void paint(Graphics g)
    {
       Graphics2D            g2;       
       Rectangle2D.Float     rectangle;

       g2 = (Graphics2D)g;
       g2.setColor(Color.BLACK);       
       g2.draw(ground);
       
       g2.setColor(Color.RED);       
       rectangle = new Rectangle2D.Float(x, y, width, height);
       g2.draw(rectangle);

    }
}
        
Another Simple Example

An Animated Curve

javaexamples/multimedia/AnimatedCurve.java
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;

/**
 * A simple example of ad-hoc animation
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class AnimatedCurve extends JComponent implements ActionListener
{
    private CubicCurve2D.Float   curve;
    private float[]              x, y;
    private Stroke               curveStroke;
    private Timer                timer;


    /**
     * Default Constructor
     */
    public AnimatedCurve()
    {
       super();

       curveStroke   = new BasicStroke(2.0f, 
                                       BasicStroke.CAP_ROUND,
                                       BasicStroke.JOIN_MITER);

       setBackground(Color.white);


       x = new float[4];
       y = new float[4];

       x[0] =   0.0f;   y[0] =   0.0f;
       x[1] = 100.0f;   y[1] = 100.0f;
       x[2] = 300.0f;   y[2] = 300.0f;
       x[3] = 400.0f;   y[3] = 400.0f;
        
       curve = new CubicCurve2D.Float();
       updateCurve();

       timer = new Timer(100, this);
       timer.start();
    }



    /**
     * Handle actionPerformed messages generated by the Timer
     * (Required by ActionListener)
     *
     * @param evt    The ActionEvent
     */
    public void actionPerformed(ActionEvent evt)
    {
       x[0] += 1.0f;
       y[0] += 1.0f;

       x[1] += 1.0f;
       y[1] -= 1.0f;

       x[2] -= 1.0f;
       y[2] += 1.0f;

       x[3] -= 1.0f;
       y[3] -= 1.0f;

       updateCurve();
       repaint();
    }




    /**
     * Render this Painting
     *
     * @param renderer   The rendering engine to use
     */
    public void paint(Graphics   g)
    {
       Graphics2D     g2;
       
       g2 = (Graphics2D)g;

       g2.setColor(Color.black);
       
       if (curveStroke != null) g2.setStroke(curveStroke);
       
       if (curve != null) g2.draw(curve);
    }



    /**
     * Update the points in the curve
     */
    private void updateCurve()
    {
       curve.setCurve(x[0],y[0],x[1],y[1],
                      x[2],y[2],x[3],y[3]);
    }

}
        
User Interaction
An Example of User Interaction

Whack-A-Duke

javaexamples/multimedia/WhackADuke.java
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.io.*;
import java.util.Random;
import javax.imageio.*;
import javax.swing.*;

/**
 * An example of animation with user interaction
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class WhackADuke extends    JComponent 
                        implements ActionListener, MouseListener
{
    private Image              head;    
    private int                direction, hits, speed, steps, x, y;
    private Random             rng;
    private Rectangle2D.Float  base;
    private Timer              timer;

    private final Color GOLD       = new Color(0xc2,0xa1,0x4d);

    private final Font  SCORE_FONT = new Font(Font.MONOSPACED, Font.PLAIN, 20);

    private final int HEAD_HEIGHT  = 70;
    private final int HEAD_WIDTH   = 70;
    private final int HIDDEN_STEPS = 10;

    /**
     * Default Constructor
     */
    public WhackADuke()
    {
       // Construct a random number generator
       rng = new Random();

       // Read the Madison head
       try
       {
          head = ImageIO.read(new File("madison.png"));
       }
       catch (IOException ioe)
       {
          head = null;          
       }

       // Construct the base
       base = new Rectangle2D.Float(0f, 400f, 500f, 100f);
       
       // Initialize
       direction = 0;
       x         = 0;
       y         = (int)base.getY();
       speed     = 2;

       // Make this object a MouseListener on itself
       addMouseListener(this);

       // Construct and start the timer
       timer = new Timer(10, this);
       timer.start();
    }

    /**
     * Handle actionPerformed messages (required by ActionListener)
     *
     * @param  event   The ActionEvent that generated the message
     */
    public void actionPerformed(ActionEvent event)
    {
       if (direction == 0) // The head isn't moving
       {
          steps++;
          if (steps > HIDDEN_STEPS) 
          {
             x = rng.nextInt(400);
             y = (int)base.getY();
             steps = 0;
             direction = -1;
          }
       }
       else if (direction == -1) // The head is moving up
       {
          y += direction * speed;

          if (y <= ((int)base.getY() - HEAD_HEIGHT))
          {
             direction = 1;
          }
       }
       else if (direction == 1) // The head is moving down
       {
          y += direction * speed;

          if (y >= (int)base.getY())
          {
             direction = 0;
          }
       }

       repaint();
    }

    /**
     * Handle mouseClicked messages (requried by MouseListener)
     *
     * @param event  The MouseEvent that generated the message
     */
    public void mouseClicked(MouseEvent event)
    {
       int       mouseX, mouseY;
       
       // Get the position of the mouse
       mouseX = event.getX();
       mouseY = event.getY();

       // Check for a whack
       if ((direction != 0) && 
           (mouseX >= x) && (mouseX <= x+HEAD_WIDTH) &&
           (mouseY >= y) && (mouseY<=  y+HEAD_HEIGHT))
       {
          hits++;
       }
    }

    /**
     * Handle mouseEntered messages (requried by MouseListener)
     *
     * @param event  The MouseEvent that generated the message
     */
    public void mouseEntered(MouseEvent event)
    {
    }

    /**
     * Handle mouseExited messages (requried by MouseListener)
     *
     * @param event  The MouseEvent that generated the message
     */
    public void mouseExited(MouseEvent event)
    {
    }


    /**
     * Handle mousePressed messages (requried by MouseListener)
     *
     * @param event  The MouseEvent that generated the message
     */
    public void mousePressed(MouseEvent event)
    {
    }

    /**
     * Handle mouseReleased messages (requried by MouseListener)
     *
     * @param event  The MouseEvent that generated the message
     */
    public void mouseReleased(MouseEvent event)
    {
    }

    /*
     * Render this JComponent
     *
     * @param g   The rendering engine to use
     */
    public void paint(Graphics g)
    {
       float                height, width;       
       Graphics2D           g2;
       Rectangle2D          background;
       

       g2 = (Graphics2D)g;

       // Clear the screen
       background = getBounds();
       width      = (float)background.getWidth();       
       height     = (float)background.getHeight();       
       g2.setColor(Color.WHITE);
       g2.fill(background);
       
       // Render the head
       g2.drawImage(head, x, y, null);
       
       // Render the base
       g2.setColor(GOLD);
       g2.fill(base);

       // Render the score
       g2.setColor(Color.BLACK);
       g2.setFont(SCORE_FONT);       
       g2.drawString("Score: "+hits, 300, 100);
    }
}
        
A More Object-Oriented Example

A Fish Class

javaexamples/multimedia/Fish.java
import java.awt.*;
import java.awt.image.*;
import java.util.*;


/**
 * A Fish that "swims" in an interesting way.
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class Fish
{
    protected BufferedImage[]     images;
    protected int                 initialSpeed, maxX, maxY, speed, x, y;
    protected int                 state, stateChange;
    protected int                 ticks, ticksInState;
    
    

    private static final int     INITIAL_LOCATION = -320;    
    private static final Random  rng = new Random();

    /**
     * Explicit Value Constructor
     *
     * @param threeImages  The three Image objects for this Fish
     * @param width        The width of the fishtank
     * @param height       The height of the fishtank
     * @param speed        The normal speed
     */
    public Fish(BufferedImage threeImages, int width, int height, int speed)
    {
       int     imageHeight, imageWidth;
        
       if (threeImages != null)
       {
          imageHeight = threeImages.getHeight(null);
          imageWidth  = threeImages.getWidth(null)/3;
          
          images = new BufferedImage[3];
          for (int i=0; i<3; i++)
          {
             images[i] = threeImages.getSubimage(i*imageWidth,0,
                                                 imageWidth,imageHeight);
          }
       }
       
       maxX = width;
       maxY = height;       

       x    = rng.nextInt(maxX);
       y    = rng.nextInt(maxY);

       this.speed        = speed;       
       this.state        = 0;       
       this.stateChange  = 1;       
       this.ticksInState = 20 - 2*speed;       
    }


    /**
     * Paint this Fish
     *
     * @param g   The rendering engine to use
     */
    public void paint(Graphics g)
    {
       ticks += 1;
       if (ticks > ticksInState)
       {
          ticks = 0;
          state += stateChange;
          if      (state == 2) stateChange = -1;
          else if (state == 0) stateChange =  1;
       }

       x += speed;
       
       if (x > maxX)
       {
          x     = INITIAL_LOCATION;
          y     = rng.nextInt(maxY);
       }

       if (images[state] != null) g.drawImage(images[state], x, y, null);
    }
}
        
A More Object-Oriented Example (cont.)

The FishTank

javaexamples/multimedia/FishTank.java
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import java.util.Random;
import javax.imageio.*;
import javax.swing.*;

/**
 * An example of animation
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class FishTank extends    JComponent 
                      implements ActionListener
{
    private BufferedImage      ocean;    
    private Fish[]             fish;    
    private Timer              timer;

    private static final Random  rng = new Random();


    /**
     * Default Constructor
     */
    public FishTank()
    {
       BufferedImage         threeImages;       

       try
       {
          ocean        = ImageIO.read(new File("ocean.png"));
          threeImages  = ImageIO.read(new File("fish.png"));
       }
       catch (IOException ioe)
       {
          ocean       = null;
          threeImages = null;          
       }

       fish = new Fish[5];       
       for (int i=0; i<5; i++) 
          fish[i] = new Fish(threeImages, 640, 480, rng.nextInt(3)+1);

       timer = new Timer(10, this);
       timer.start();
    }

    /**
     * Handle actionPerformed messages (required by ActionListener)
     *
     * @param  event   The ActionEvent that generated the message
     */
    public void actionPerformed(ActionEvent event)
    {
       repaint();
    }

    /*
     * Render this JComponent
     *
     * @param g   The rendering engine to use
     */
    public void paint(Graphics g)
    {
       Graphics2D           g2;
       

       g2 = (Graphics2D)g;

       if (ocean != null) g2.drawImage(ocean, 0, 0, null);

       for (int i=0; i<fish.length; i++) fish[i].paint(g);       
    }
}