JMU
Described Dynamic Visual Content
An Introduction with Examples in Java


Prof. David Bernstein
James Madison University

Computer Science Department
bernstdh@jmu.edu


Described Dynamics
Terminology for Described Dynamics
Quick Start
"Quick Start" (cont.)

Requirements of a Sprite

javaexamples/visual/dynamic/described/Sprite.java
        package visual.dynamic.described;

import java.awt.geom.Rectangle2D;


import event.MetronomeListener;
import visual.statik.TransformableContent;


/**
 * A Sprite is an "actor" on a Stage.
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public interface Sprite extends MetronomeListener,
                                TransformableContent
{
    /**
     * Returns a high precision bounding box
     *
     * @return   The bounding box
     */
    public abstract Rectangle2D getBounds2D();
}
        
"Quick Start" (cont.)

Structure of a Stage Class

javaexamples/visual/dynamic/described/Stage.java (Fragment: skeleton)
        package visual.dynamic.described;

import java.awt.*;
import java.util.*;


import event.*;
import visual.*;
import visual.statik.sampled.*;

/**
 * A Visualization that contains Sprite objects
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 *
 */
public class Stage extends    Visualization 
                   implements MetronomeListener
{
    private boolean       shouldRestart;
    private int           timeStep, restartTime, time;
    private Metronome     metronome;


    /**
     * Explicit Value Constructor
     *
     * @param timeStep    Amount of time between ticks (in milliseconds)
     */
    public Stage(int timeStep)
    {       
       super();
       
       this.timeStep     =  timeStep;
       time              = -timeStep;
       shouldRestart     =  false;
       restartTime       = -1;
       
       metronome = new Metronome(timeStep);
       // The first listener is notified last
       metronome.addListener(this);

       setBackground(Color.WHITE);
    }

    
    /**
     * Add a Sprite to this Stage
     *
     * @param sprite   The Sprite to add
     */
    public void add(Sprite sprite)
    {
       // Make the Sprite a MetronomeListener
       metronome.addListener(sprite);

       // Treat the Sprite as a SimpleContent and
       // add it to the Visualization
       super.add(sprite);       
    }    

    /**
     * Remove a Sprite from this Stage
     *
     * @param sprite   The Sprite to remove
     */
    public void remove(Sprite sprite)
    {
       metronome.removeListener(sprite);
       super.remove(sprite);       
    }    


    /**
     * Set the background color
     *
     * Note: If you want to have a background image just
     * make it the first SimpleContent
     *
     * @param background       The background color
     */
    public void setBackground(Color background)
    {
       Iterator<VisualizationView>    i;
       VisualizationView              view;
       
       i = getViews();
       while (i.hasNext())
       {
          view = i.next();
          view.setBackground(background);
       }
    }


    /**
     * Set the time at which the Stage should restart
     *
     * @param restartTime   In milliseconds (negative to not restart)
     */
    public void setRestartTime(int restartTime)
    {
       if (restartTime < 0)
       {
          this.restartTime = -1;
          shouldRestart = false;
       }
       else
       {
          this.restartTime = restartTime;
          shouldRestart = true;
       }
    }


    /**
     * Start the Stage
     */
    public void start()
    {
       metronome.start();
    }


    /**
     * Stop the Stage
     */
    public void stop()
    {
       metronome.stop();
    }
}
        
"Quick Start" (cont.)

Timer Events

javaexamples/visual/dynamic/described/Stage.java (Fragment: metronomeevents)
        
    /**
     * Handle "tick" events (required by MetronomeTickListener)>
     *
     * @param time  The time of the "tick"
     */
    public void handleTick(int time)
    {
       if ((shouldRestart) && (time > restartTime)) 
       {
          metronome.setTime(-timeStep);
       }
       
       repaint();
    }

        
"Quick Start" (cont.)
"Quick Start" (cont.)
"Quick Start" (cont.)

Structure of the AbstractSprite Class

javaexamples/visual/dynamic/described/AbstractSprite.java (Fragment: skeleton)
        package visual.dynamic.described;

import java.awt.*;
import java.awt.geom.*;
import java.util.*;
import javax.swing.*;

import event.*;
import visual.statik.TransformableContent;


/**
 * A Sprite is an "actor" on a Stage.
 *
 * In essence, a Sprite decorates a TransformableContent object,
 * providing it with additional capabilities.
 *
 * Note:
 *
 * We don't immediately delegate since the content and the
 * location/rotation/scale may change at any time and in any
 * order.  Instead, we set the location/rotation/scale before
 * rendering.
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public abstract class      AbstractSprite 
                implements MetronomeListener, 
                           Sprite,
                           TransformableContent
{
    protected boolean     rotationPoint, visible;
    protected double      angle, rotationX, rotationY;
    protected double      scaleX, scaleY, x, y;
    


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

       x      = 0.0;
       y      = 0.0;
       angle  = 0.0;
       scaleX = 1.0;       
       scaleY = 1.0;
       
       rotationPoint = false;       
       rotationX     = 0.0;
       rotationY     = 0.0;       
    }


    /**
     * Returns a high precision bounding box of the Content
     * either before or after it is transformed
     *
     * @param    ofTransformed    true to get the BB of the transformed content
     * @return   The bounding box
     */
    public Rectangle2D getBounds2D(boolean ofTransformed)
    {
       return getContent().getBounds2D(true);       
    }
    
    
    
    /**
     * Returns a high precision bounding box
     *
     * @return   The bounding box
     */
    public Rectangle2D getBounds2D()
    {
       return getBounds2D(true);       
    }
    



    /**
     * Gets the (current) visual content for this Sprite
     *
     * This method is called by various setters and the render()
     * method.
     */
    protected abstract TransformableContent getContent();
    


    /**
     * Handle a tick event 
     * (required by MetronomeListener)
     *
     * @param time  The current time (in milliseconds)
     */
    public abstract void handleTick(int time);




    /**
     * Initialize state variables
     */
    protected void reinitialize()
    {
       setScale(1.0);
       setRotation(0.0);
       setLocation(0.0, 0.0);

       setVisible(false);
    }


    /**
     * Set the location (on the Stage) of the Sprite 
     *
     * @param x   The horizontal location
     * @param y   The vertical location
     */
    public void setLocation(double x, double y)
    {
       this.x = x;
       this.y = y;
    }


    /**
     * Set the rotation angle and point to rotate around
     *
     * @param r  The new rotation angle
     * @param x  The x-coordinate of the point to rotate around
     * @param y  The y-coordinate of the point to rotate around
     */
    public void setRotation(double r, double x, double y)
    {

       rotationPoint = true;
       this.angle    = r;
       this.x        = x;
       this.y        = y;
    }


    /**
     * Set the rotation angle (the Sprite will rotate
     * around its midpoint)
     *
     * @param r  The new rotation angle
     */
    public void setRotation(double r)
    {
       rotationPoint = false;
       this.angle    = r;
    }



    /**
     * Set the scaling (enlargement, reduction) of the Sprite 
     *
     * @param sx  The scale in the x-dimension
     * @param sy  The scale in the y-dimension
     */
    public void setScale(double sx, double sy)
    {
       scaleX = sx;
       scaleY = sy;
    }
    

    /**
     * Set the scaling (enlargement, reduction) of the Sprite 
     *
     * @param s  The new scale
     */
    public void setScale(double s)
    {
       setScale(s, s);
    }




    /**
     * Set the visibility of this Sprite
     *
     * @param v true for visible, false for invisible
     */
    public void setVisible(boolean v)
    {
        visible = v;
    }

}
        
"Quick Start" (cont.)

Rendering

javaexamples/visual/dynamic/described/AbstractSprite.java (Fragment: render)
        
    /**
     * Render this Sprite
     *
     * @param g   The rendering engine to use
     */
    public void render(Graphics g)
    {
       double                   rx, ry;       
       Rectangle2D              bounds;       
       TransformableContent     tc;
       

       if (visible)
       {
          tc = getContent();          

          if (tc != null) 
          {
             // Find the point to rotate around
             if (rotationPoint)
             {
                rx = rotationX;
                ry = rotationY;                
             }
             else
             {
                bounds = tc.getBounds2D(false);             
                rx     = bounds.getWidth()/2.0;
                ry     = bounds.getHeight()/2.0;                
             }


             // Transform
             tc.setLocation(x, y);
             tc.setRotation(angle, rx, ry);
             tc.setScale(scaleX, scaleY);             

             // Render
             tc.render(g);       
          }
       }
    }
        
"Quick Start" (cont.)

An Example: FloatingSprite

javaexamples/visual/dynamic/described/FloatingSprite.java (Fragment: skeleton)
        package visual.dynamic.described;

import java.awt.*;
import java.awt.geom.*;
import java.util.*;

import visual.statik.TransformableContent;


/**
 * A simple Sprite
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 *
 */
public class FloatingSprite extends AbstractSprite
{
    private double                    maxX, maxY, x, y;    
    private Random                    rng;    
    private TransformableContent      content;
    

    /**
     * Explicit Value Constructor
     *
     * @param content   The static visual content
     * @param width     The width of the Stage
     * @param height    The height of the Stage
     */
    public FloatingSprite(TransformableContent content,
                          double width, double height)
    {
       super();
       this.content = content;
       maxX         = width;
       maxY         = height;       

       rng = new Random();       

       x = rng.nextDouble()*maxX;       
       y = 0.0;       
       setLocation(x, y);       

       setVisible(true);
    }

}
        
javaexamples/visual/dynamic/described/FloatingSprite.java (Fragment: getContent)
        
    /**
     * Get the visual content associated with this Sprite
     * (required by Sprite)
     */
    public TransformableContent getContent()
    {
       return content;
    }
        
javaexamples/visual/dynamic/described/FloatingSprite.java (Fragment: handleTick)
        
    /**
     * Handle a tick event (required by MetronomeListener)
     *
     * @param time  The current time (in milliseconds)
     */
    public void handleTick(int time)
    {
       double        n;
       
       n = rng.nextDouble();
       if      (n < 0.80)   y += 2.0;
       else if (n > 0.90)   y -= 1.0;       

       n = rng.nextDouble();
       if      (n < 0.20) x -= 1.0;
       else if (n > 0.80) x += 1.0;

       // Check if at the bottom
       if (y > maxY)
       {
          y  = 0.0;
          x = rng.nextDouble()*maxX;       
       }

       setLocation(x, y);       
    }
        
"Quick Start" (cont.)

Using the FloatingSprite

javaexamples/visual/dynamic/described/FloatingSpriteApp.java (Fragment: skeleton0)
        
       ContentFactory                  contentFactory;
       FloatingSprite                  sprite;
       int                             height, width;       
       JPanel                          contentPane;       
       Stage                           stage;
       TransformableContent            content;
       VisualizationView               stageView;       
              
       width   = 640;
       height  = 480;       

       // The Stage
       stage        = new Stage(50);
       stage.setBackground(new Color(255, 255, 255)); 
       stageView = stage.getView();
       stageView.setBounds(0,0,width,height);       

       // The Sprite
       contentFactory = new ContentFactory();
       content = contentFactory.createContent(
                    "/visual/dynamic/described/snowflake.gif", 
                    4, false);
       sprite  = new FloatingSprite(content, width, height);
       stage.add(sprite);

              
       // The content pane
       contentPane = (JPanel)rootPaneContainer.getContentPane();
       contentPane.add(stageView);

       // Start the dynamics
       stage.start();
        
Encapsulating Rule-Based Sprites

Put simple rules in the handleTick() method

Sprite Interactions
Sprite Interactions (cont.)

Rectangular Sprites

images/intersects3.gif
Sprite Interactions (cont.)

Non-Rectangular Sprites

images/intersects4.gif
Sprite Interactions (cont.)

Non-Convex Sprites

images/intersects2.gif
Sprite Interactions (cont.)

Using Bounding Boxes

images/intersects1.gif
Sprite Interactions (cont.)

Modifying the Sprite Class

javaexamples/visual/dynamic/described/AbstractSprite.java (Fragment: intersects)
        
    /**
     * Does the bounding box of this Sprite intersect the 
     * bounding box of the given Sprite? (given their current state)
     *
     * @param s  The other Sprite
     * @return   true if the two intersect
     */
    public boolean intersects(Sprite s)
    {
        boolean          retval;
        double           maxx, maxy, minx, miny;
        double           maxxO, maxyO, minxO, minyO;
        Rectangle2D      r;

        retval = true;

        r = getBounds2D();
        minx = r.getX();
        miny = r.getY();
        maxx = minx + r.getWidth();
        maxy = miny + r.getHeight();


        r = s.getBounds2D();
        minxO = r.getX();
        minyO = r.getY();
        maxxO = minxO + r.getWidth();
        maxyO = minyO + r.getHeight();


        if ( (maxx < minxO) || (minx > maxxO) ||
             (maxy < minyO) || (miny > maxyO) ) retval = false;
        
        return retval;
       
    }
        
Sprite Interactions (cont.)

The Protagonists

javaexamples/visual/dynamic/described/RuleBasedSprite.java
        package visual.dynamic.described;

import java.awt.*;
import java.awt.geom.*;
import java.util.*;

import visual.statik.TransformableContent;


/**
 * An abstract rule-based Sprite
 *
 * Note: Concrete descendents must implement the
 * handleTick() method
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 *
 */
public abstract class RuleBasedSprite extends AbstractSprite
{
    protected ArrayList<Sprite>         protagonists;    
    protected TransformableContent      content;

      
    /**
     * Explicit Value Constructor
     *
     * @param content   The static visual content
     */
    public RuleBasedSprite(TransformableContent content)
    {
       super();

       protagonists = new ArrayList<Sprite>();       
       this.content = content;
       setVisible(true);
    }

    /**
     * Add a "protagonist" Sprite to this Sprite
     *
     * @param protagonist   The "protagonist" Sprite to add
     */
    public void addProtagonist(Sprite protagonist)
    {
       protagonists.add(protagonist);       
    }

    /**
     * Get the visual content associated with this Sprite
     * (required by Sprite)
     */
    public TransformableContent getContent()
    {
       return content;
    }

    /**
     * Handle a tick event (required by MetronomeListener)
     *
     * @param time  The current time (in milliseconds)
     */
    public abstract void handleTick(int time);


    /**
     * Remove a "protagonist" Sprite from the collection of
     * "protagonists" this Sprite is concerned with
     *
     * @param protagonist   The "protagonist" Sprite to remove
     */
    public void removeProtagonist(Sprite protagonist)
    {
       protagonists.remove(protagonist);       
    }
}
        
Sprite Interactions (cont.)

An Example

javaexamples/visual/dynamic/described/Fish.java
        package visual.dynamic.described;

import java.awt.*;
import java.awt.image.RasterFormatException;
import java.awt.geom.*;
import java.util.Random;

import visual.statik.TransformableContent;


/**
 * A Fish that "swims" around and avoids its "protagonists"
 * (in a simple way)
 *
 * Note: This is a simple rule-based Sprite. 
 * Hence, it has rules in handleTick().
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 *
 */
public class Fish extends RuleBasedSprite
{
    protected double      maxX, maxY, speed, x, y;
    protected Random      rng;
    

    

    /**
     * Explicit Value Constructor
     *
     * @param content   The static visual content
     * @param width     The width of the Stage
     * @param height    The height of the Stage
     */
    public Fish(TransformableContent content,
                double width, double height)
    {
       super(content);
       maxX = width;
       maxY = height;       

       rng = new Random();
       
       x     = rng.nextDouble()*maxX;
       y     = rng.nextInt()*maxY;
       speed = 3.0;
    }

    


    /**
     * Handle a tick event (generated by the Stage)
     *
     * @param time  The current time (which is not used)
     */
    public void handleTick(int time)
    {
       Sprite     shark;

       shark = null;       
       if (protagonists.size() > 0) shark = protagonists.get(0);

       if ((shark != null) && (intersects(shark)))
       {
          speed = 20;
       }

       updateLocation(-50., 3.);
    }



    /**
     * Update the location
     *
     * @param initialLocation  The left-most location
     * @param initialSpeed     The speed to start at
     */
    protected void updateLocation(double initialLocation,
                                  double initialSpeed)
    {
       x += speed;
       
       if (x > (int)maxX)
       {
          x     = initialLocation;
          y     = rng.nextDouble()*maxX;
          speed = initialSpeed;          
       }

       // Set the location
       setLocation(x, y);
    }
    
}
        
javaexamples/visual/dynamic/described/Shark.java
        package visual.dynamic.described;

import java.awt.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.awt.image.RasterFormatException;
import java.util.Random;

import visual.statik.TransformableContent;


/**
 * A Shark that "swims" around
 *
 * Note: This is a simple rule-based Sprite. 
 * Hence, it has rules in handleTick().
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class Shark extends Fish
{

    /**
     * Explicit Value Constructor
     *
     * @param content   The static visual content
     * @param width     The width of the Stage
     * @param height    The height of the Stage
     */
    public Shark(TransformableContent content,
                 double width, double height)
   
    {
       super(content, width, height);

       speed      = 8.0;
    }



    /**
     * Handle a tick event (generated by the Stage)
     *
     * @param time  The current time (which is not used)
     */
    public void handleTick(int time)
    {
       updateLocation(-320.0, 6.0);
    }
    
}
        
javaexamples/visual/dynamic/described/FishTankApp.java
        package visual.dynamic.described;


import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import javax.swing.*;

import app.*;
import visual.VisualizationView;
import visual.dynamic.described.Fish;
import visual.dynamic.described.Shark;
import visual.dynamic.described.Stage;
import visual.statik.sampled.ImageFactory;
import visual.statik.sampled.Content;
import visual.statik.sampled.ContentFactory;


/**
 * An example of described dynamic visual content that uses:
 *
 *     Several RuleBasedSprite objects
 *     Interaction
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 *
 */
public class   FishTankApp
       extends AbstractMultimediaApp
{
    /**
     * The entry point
     */
    public void init()
    {
       BufferedImage                image;
       Content                      content;
       ContentFactory               factory;       
       Fish                         fish;
       ImageFactory                 imageFactory;
       int                          height, width;       
       JPanel                       contentPane;       
       Shark                        shark;
       Stage                        stage;
       VisualizationView            stageView;       


       width  = 640;
       height = 480;       

       factory      = new ContentFactory();       
       imageFactory = new ImageFactory();

       // The Stage
       stage = new Stage(50);
       stage.setBackground(Color.blue);
       content = factory.createContent(
          "/visual/dynamic/described/ocean.gif", 3, false);
       stage.add(content);
       stageView = stage.getView();
       stageView.setBounds(0,0,width,height);       

       // The Shark
       content = factory.createContent(
          "/visual/dynamic/described/shark.gif", 4, false);
       shark = new Shark(content, width, height);
       stage.add(shark);


       // The school of Fish
       // (Using the same BufferedImage object for all Fish)
       image   = imageFactory.createBufferedImage(
          "/visual/dynamic/described/fish.gif", 4);
       for (int i=0; i<6; i++)
       {
          content = factory.createContent(image, false);
          fish = new Fish(content, width, height);
          fish.addProtagonist(shark);       
          stage.add(fish);
       }
       
       
       // The content pane
       contentPane = (JPanel)rootPaneContainer.getContentPane();
       contentPane.add(stageView);



       stage.start();
    }


}
        
User Interaction
User Interaction (cont.)

A Change to the Visualization Class

javaexamples/gui/Visualization.java (Fragment: listeners)
        
    /**
     * Add a KeyListener
     *
     * @param kl   The listener to add
     */
    public void addKeyListener(KeyListener kl)
    {
       Iterator<VisualizationView>  i;
       VisualizationView            view;

       i = getViews();
       while (i.hasNext())
       {
          view = i.next();
          view.addKeyListener(kl);       
       }
    }
    

    /**
     * Add a MouseListener
     *
     * @param ml   The listener to add
     */
    public void addMouseListener(MouseListener ml)
    {
       Iterator<VisualizationView>  i;
       VisualizationView            view;

       i = getViews();
       while (i.hasNext())
       {
          view = i.next();
          view.addMouseListener(ml);       
       }
    }
    

    /**
     * Add a MouseMotionListener
     *
     * @param mml   The listener to add
     */
    public void addMouseMotionListener(
                        MouseMotionListener mml)
    {
       Iterator<VisualizationView>  i;
       VisualizationView            view;

       i = getViews();
       while (i.hasNext())
       {
          view = i.next();
          view.addMouseMotionListener(mml);       
       }
    }
    
        
User Interaction (cont.)

An Addictive (a.k.a. Boring) Game

javaexamples/visual/dynamic/described/Cupola.java (Fragment: skeleton)
        package visual.dynamic.described;

import java.awt.event.*;
import java.awt.geom.*;

import visual.statik.TransformableContent;

/**
 * The Cupola in the Balloon game.
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class Cupola extends    RuleBasedSprite 
                    implements MouseMotionListener
{
    private double     left, top;

    /**
     * Explicit Value Constructor
     *
     * @param content     The static visual content
     * @param stageWidth  The width of the Stage
     * @param stageHeight The height of the Stage
     */
    public Cupola(TransformableContent content, 
                  double stageWidth, double stageHeight)
    {
       super(content);
       Rectangle2D        bounds;       

       bounds = content.getBounds2D(false);
       top    = (stageHeight - bounds.getHeight());
       left   = (stageWidth  - bounds.getWidth())/2.0;
    
       setLocation(left, top);
    }

}
        
javaexamples/visual/dynamic/described/Cupola.java (Fragment: mouseMotionListener)
        
    /**
     * Handle a mouseDragged message
     * (required by MouseMotionListener)
     *
     * @param evt  The MouseEvent that generated the message
     */
    public void mouseDragged(MouseEvent evt)
    {
       mouseMoved(evt);       
    }


    /**
     * Handle a mouseMoved message
     * (required by MouseMotionListener)
     *
     * @param evt  The MouseEvent that generated the message
     */
    public void mouseMoved(MouseEvent evt)
    {
       this.left = (double)evt.getX();
    }
        
javaexamples/visual/dynamic/described/Cupola.java (Fragment: handleTick)
        
    /**
     * Handle a tick event (generated by the Stage)
     *
     * @param time  The current time (which is not used)
     */
    public void handleTick(int time)
    {
       setLocation(left, top);
    }
        
javaexamples/visual/dynamic/described/Balloon.java
        package visual.dynamic.described;

import java.awt.*;
import java.awt.geom.*;
import java.util.Random;

import visual.statik.TransformableContent;


/**
 * A Balloon that drops to the ground
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class Balloon extends RuleBasedSprite
{
    private double          left, speed, top;
    private int             maxY, maxX, minY;
    

    private static Random   rng = new Random();
    

    

    /**
     * Explicit Value Constructor
     *
     * @param content     The static content for the balloon
     * @param stageWidth  The width of the Stage
     * @param stageHeight The height of the Stage
     */
    public Balloon(TransformableContent content, 
                   double stageWidth, double stageHeight)
    {
       super(content);
       Rectangle2D        bounds;       

       bounds = content.getBounds2D(false);

       maxX   = (int)(stageWidth  - bounds.getWidth());
       maxY   = (int)(stageHeight);
       minY   = (int)(-bounds.getHeight());       

       left   = rng.nextInt(maxX);
       top    = minY;
       speed  = 1 + rng.nextInt(10);
    }


    /**
     * Handle a tick event (generated by the Stage)
     *
     * @param time  The current time (which is not used)
     */
    public void handleTick(int time)
    {
       Sprite   cupola;

       // Check for an intersection
       cupola = null;       
       if (protagonists.size() > 0) cupola = protagonists.get(0);

       if ((cupola != null) && (intersects(cupola)))
       {
          speed = 0;
          setVisible(false);
       }
       
       // Update the location
       top += speed;
       
       if (top > maxY)
       {
          left   = rng.nextInt(maxX);
          top    = minY;
          speed  = rng.nextInt(10);
       }
       
       
       // Set the location
       setLocation(left, top);
    }
}
        
A "Key Time" Approach

First describe "key times" and then describe how to interpolate between them

Traditional Cel Animation
Computerizing Cel Animation

Tweening is done by the computer

Design
Design (cont.)

A TweeningSprite Class

javaexamples/visual/dynamic/described/TweeningSprite.java (Fragment: skeleton)
        package visual.dynamic.described;

import java.awt.*;
import java.awt.geom.*;
import java.util.*;
import javax.swing.*;

/**
 * A TweeningSprite is a Sprite that contains "key times" and the ability
 * to generate "in between times"
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public abstract class TweeningSprite extends AbstractSprite
{
    private   double           frac;
    private   int              currentIndex, endState, lastTime;
    private   int              localTime, nextIndex, nextKT;
    protected Vector<Integer>  keyTimes;
    protected Vector<Point2D>  locations;
    protected Vector<Double>   rotations, scalings;
    

    public static final int REMAIN         = 0;
    public static final int REMOVE         = 1;
    public static final int REPEAT         = 2;

    


    /**
     * Default Constructor
     */
    public TweeningSprite()
    {       
       super();
       
       keyTimes  = new Vector<Integer>();
       locations = new Vector<Point2D>();
       rotations = new Vector<Double>();
       scalings  = new Vector<Double>();

       localTime =  0;
       lastTime  = -1;
       initialize();
    }

}
        
javaexamples/visual/dynamic/described/TweeningSprite.java (Fragment: addKeyTime)
        
    /**
     * Add a key time
     *
     * @param keyTime     The time
     * @param location    The location of the sprite at this time
     * @param rotation    The rotation of the sprite (null to align with path)
     * @param scaling     The scaling of the sprite at this time
     *
     * @return   The index of this key time (or -1 if the key time exists)
     */
    protected int addKeyTime(int keyTime, Point2D location,
                             Double rotation, Double scaling)
    {
       boolean     keepLooking;
       int         existingKT, i, index;


       existingKT  = -1;
       keepLooking = true;
       
       i = 0;
       while ((i < keyTimes.size()) && keepLooking)
       {
          existingKT = ((Integer)keyTimes.get(i)).intValue();
          
          if (existingKT >= keyTime) keepLooking = false;
          else                       i++;
       }

       if ((existingKT == i) && !keepLooking)  // Duplicate
       {
          i = -1;
       }
       else
       {
          keyTimes.insertElementAt(new Integer(keyTime), i);
          locations.insertElementAt(location, i);
          rotations.insertElementAt(rotation, i);
          scalings.insertElementAt(scaling, i);
       }
       
       return i;
    }
        
Location Tweening
images/tweening_location.gif
Location Tweening (cont.)

A TweeningSprite Class

javaexamples/visual/dynamic/described/TweeningSprite.java (Fragment: tweenLocation)
        
    /**
     * Determine the current 'tweened location
     * 
     * @param currentIndex   The index of the current key time
     * @param nextIndex      The index of the next key time
     * @param frac           The interpolation fraction
     */
    protected void tweenLocation(int currentIndex, int nextIndex, 
                                 double frac)
    {
       double          x, y;
       Point2D         currentKTLocation, nextKTLocation;
       
       currentKTLocation = locations.get(currentIndex);
       nextKTLocation    = locations.get(nextIndex);

       x = currentKTLocation.getX() + 
           frac*(nextKTLocation.getX()- currentKTLocation.getX());
       y = currentKTLocation.getY() + 
          frac*(nextKTLocation.getY() - currentKTLocation.getY());

       setLocation(x, y);
    }
        
Rotation Tweening
images/tweening_rotation.gif
Rotation Tweening (cont.)

More of the TweeningSprite Class

javaexamples/visual/dynamic/described/TweeningSprite.java (Fragment: tweenRotation)
        
    /**
     * Determine the current 'tweened rotation
     * 
     * @param currentIndex   The index of the current key time
     * @param nextIndex      The index of the next key time
     * @param frac           The interpolation fraction
     */
    protected void tweenRotation(int currentIndex, int nextIndex, 
                                 double frac)
    {
       double          currentKTRotation, nextKTRotation, r;
       Double          rotation;
       Point2D         currentKTLocation, nextKTLocation;


       rotation = rotations.get(currentIndex);

       if (rotation == null)  // aligned with the line segment
       {
          currentKTLocation = locations.get(currentIndex);
          nextKTLocation    = locations.get(nextIndex);

          r=Math.atan((nextKTLocation.getY()-currentKTLocation.getY())/
                      (nextKTLocation.getX()-currentKTLocation.getX()) );
       }
       else                  // pure rotation tweening
       {
          currentKTRotation = rotation.doubleValue();

          rotation = rotations.get(nextIndex);
          if (rotation == null)
          {
             nextKTRotation = currentKTRotation;
          }
          else
          {
             nextKTRotation    = rotation.doubleValue();
          }
          
          r = currentKTRotation + frac*(nextKTRotation-currentKTRotation);
       }
       
       setRotation(r);
    }
        
Rotation Tweening (cont.)

An Example of Pure Rotation Tweening

javaexamples/visual/dynamic/described/Airplane.java
        package visual.dynamic.described;


import java.awt.geom.*;
import java.awt.image.*;

import visual.statik.sampled.*;


/**
 * An Airplane
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 *
 */
public class Airplane extends SampledSprite
{

    /**
     * Default Constructor
     *
     */
    public Airplane()
    {
        super();
        Content            content;        
        ContentFactory     factory;
        
        factory = new ContentFactory();
        content = factory.createContent(
                          "/visual/dynamic/described/airplane.gif", 4);
        addKeyTime(  500,   0.0, 350.0, -0.75, 1.0, content);
        addKeyTime( 2000, 100.0, 200.0, -0.30, 1.0, null);        
        addKeyTime( 4000, 200.0,  50.0,  0.00, 1.0, null);        
        addKeyTime( 6000, 300.0,  50.0,  0.20, 1.0, null);        
        addKeyTime( 8000, 400.0, 200.0,  0.00, 1.0, null);        
        addKeyTime( 8500, 500.0, 200.0,  0.00, 1.0, null);        
        
        setEndState(REMOVE);
    }

    /**
     * Add a key frame
     */
    private void addKeyTime(int time, double x, double y,
                             double r, double s, Content c)
    {
       addKeyTime(time, new Point2D.Double(x, y), new Double(r),
                   new Double(s), c);
    }

}
        
Rotation Tweening (cont.)

Another Example of Pure Rotation Tweening

javaexamples/visual/dynamic/described/BuzzyOnMars.java
        package visual.dynamic.described;


import java.awt.geom.Point2D;

import visual.statik.described.*;

/**
 * A Buzzy moving on Mars
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 *
 */
public class BuzzyOnMars extends DescribedSprite
{

    /**
     * Default Constructor
     */
    public BuzzyOnMars()
    {
        super();
        BuzzyStanding       buzzy;
        

        buzzy = new BuzzyStanding();
        addKeyTime(  500,   0.0, 380.0,  0.00, 1.0, buzzy);
        addKeyTime( 2000, 180.0, 380.0,  0.00, 1.0, null);        
        addKeyTime( 4000, 180.0,  75.0,  0.20, 1.0, null);        
        addKeyTime( 6000, 640.0,  20.0,  6.48, 1.0, null);        
        
        setEndState(REMOVE);
    }




    /**
     * Add a key time
     *
     * @param time    The key time
     * @param x       The x position
     * @param y       The y position
     * @param r       The rotation angle
     * @param r       The scaling
     * @param c       The Content 
     */
    private void addKeyTime(int time, double x, double y,
                             double r, double s, AggregateContent c)
    {
       addKeyTime(time, new Point2D.Double(x, y), new Double(r),
                   new Double(s), c);
    }

}
        
Rotation Tweening (cont.)

An Example of Alignment

javaexamples/visual/dynamic/described/BusOnRoute.java
        package visual.dynamic.described;


import java.awt.geom.Point2D;

import visual.statik.sampled.*;

/**
 * The Bus in the Transit Information System
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 *
 */
public class BusOnRoute extends SampledSprite
{


    /**
     * Default Constructor
     *
     * @param fn   The file containing the bitmap
     */
    public BusOnRoute()
    {
       super();
    
        Content              content;
        ContentFactory       factory;
        
        factory = new ContentFactory();        
        content = factory.createContent(
                       "/visual/dynamic/described/bus.gif");
        addKeyTime( 0, 164, 210, content);
        addKeyTime( 1, 310, 255, null);
        addKeyTime( 2, 314, 234, null);
        addKeyTime( 3, 401, 231, null);
        addKeyTime( 4, 419, 269, null);
        addKeyTime( 5, 353, 340, null);
        addKeyTime( 6, 430, 367, null);
        addKeyTime( 7, 420, 418, null);
        addKeyTime( 8, 450, 421, null);
        addKeyTime( 9, 454, 386, null);
        addKeyTime(10, 512, 393, null);
        addKeyTime(11, 487, 338, null);
        addKeyTime(12, 554, 323, null);
        addKeyTime(13, 500, 238, null);
        addKeyTime(14, 577, 206, null);
        addKeyTime(15, 632, 155, null);
        addKeyTime(16, 480, 151, null);
        addKeyTime(19, 301,  88, null);
        addKeyTime(21, 233, 149, null);
        addKeyTime(22, 147, 181, null);
        addKeyTime(30, 164, 210, null);

        setEndState(REMOVE);
        
    }


    /**
     * Add a key time
     *
     * @param time     The key time
     * @param x        The x position
     * @param y        The y position
     * @param content  The Content to use
     */
    private void addKeyTime(int time, int x, int y, 
                            Content content)
    {
       addKeyTime(time*1000, new Point2D.Double((double)x, (double)y), 
                   null,
                   new Double(1.0), content);
    }

}
        
Tweening Samples and Descriptions
Tweening Samples and Descriptions (cont.)

Tweening Sampled Content

javaexamples/visual/statik/sampled/AggregateContent.java
        package visual.statik.sampled;

import java.awt.*;
import java.awt.image.*;
import java.util.Iterator;


/**
 * A simple collection of Content objects
 *
 * This class is less flexible than the CompositeContent class
 * but is simpler for some purposes.
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class      AggregateContent 
       extends    visual.statik.AbstractAggregateContent<Content>
       implements TransformableContent
{
    /**
     * Default Constructor
     */
    public AggregateContent()
    {
       super();       
    }


    /**
     * Set the BufferedImageOp to use when transforming
     * the Image
     *
     * @param op   The BufferedImageOp
     */
    public void setBufferedImageOp(BufferedImageOp op)
    {
       Iterator<Content>  i;
       
       i = iterator();
       while (i.hasNext())
       {
          i.next().setBufferedImageOp(op);          
       }
    }
    
    /**
     * Set the transparency/Composite to use 
     * for the LAST Content object
     *
     * @param c   The Composite
     */
    public void setComposite(Composite c)
    {
       Content     content;
       
       content = components.getLast();
       content.setComposite(c);          
    }
}
        
javaexamples/visual/dynamic/described/SampledSprite.java
        package visual.dynamic.described;

import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.util.Vector;

import visual.statik.sampled.AggregateContent;
import visual.statik.sampled.Content;
import visual.statik.sampled.TransformableContent;


/**
 * A TweeningSprite that uses sampled visual content
 *
 * Note: This class uses a Vector of Content
 * objects rather than a CompositeContent object for
 * efficiency reasons.
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class SampledSprite extends TweeningSprite
{
    private   AggregateContent   tweened;
    private   Vector<Content>    content;
    


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

       content = new Vector<Content>();
    }
    


    /**
     * Add a key time
     *
     * Note: This method does not ensure that the first key
     * frame actually contains an image.
     *
     * @param keyTime     The time
     * @param location    The location of the sprite at this time
     * @param rotation    The rotation of the sprite (null to align with path)
     * @param scaling     The scaling of the sprite at this time
     * @param c           The Content to use (or null to use previous)
     */
    public void addKeyTime(int keyTime, Point2D location,
                           Double rotation, Double scaling, 
                           Content c)
    {
       int         index;
       
       index = super.addKeyTime(keyTime, location, rotation, scaling);

       if (index >= 0)
       {
          // If c is null then re-use the last Content
          if (c==null) c = content.get(index-1); 

          content.insertElementAt(c, index);
       }
    }


    /**
     * Get the visual content
     * (based on this Sprite's current state)
     */
    protected visual.statik.TransformableContent getContent()
    {
       AggregateContent      aggregate;       
       Content               currentContent, nextContent;       
       float                 alpha;       
       int                   current, next;
       visual.statik.TransformableContent   result;
       


       result  = null;       
       current = getKeyTimeIndex();
       next    = getNextKeyTimeIndex();

       if (visible && (current >= 0))
       {
          currentContent = content.get(current);
          nextContent    = content.get(next);

          if ((nextContent != null) && 
              (currentContent != nextContent))
          {
             aggregate = new AggregateContent();             
             aggregate.add(currentContent);
             aggregate.add(nextContent);

             // Setup alpha blending
             alpha = (float)getInterpolationFraction();
             
             aggregate.setComposite(
                        AlphaComposite.getInstance(
                                 AlphaComposite.SRC_OVER,
                                 alpha));

             result = aggregate;
          }
          else
          {
             result = currentContent;             
          }
       }

       return result;       
    }
}
        
Tweening Samples and Descriptions (cont.)

Tweening Sampled Content - An Example

javaexamples/visual/dynamic/described/CrystalBall.java
        package visual.dynamic.described;


import java.awt.geom.Point2D;

import visual.statik.sampled.*;


/**
 * An animated crystal ball
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 *
 */
public class CrystalBall extends SampledSprite
{

    /**
     * Default Constructor
     *
     */
    public CrystalBall()
    {
        super();
        Content          content;
        ContentFactory   factory;
        
        factory = new ContentFactory();
        

        content = factory.createContent(
                   "/visual/dynamic/described/crystalball01.gif");
        addKeyTime(  500,   0.0, 350.0, -0.75, content);
        addKeyTime( 4000, 100.0, 200.0, -0.30, null);        

        content = factory.createContent(
                   "/visual/dynamic/described/crystalball02.gif");
        addKeyTime( 7500, 200.0,  50.0,  0.00, content);        
        
        setEndState(REMAIN);
    }




    /**
     * Add a key time
     *
     * @param time     The key time
     * @param x        The x position
     * @param y        The y position
     * @param r        The rotation angle
     * @param content  The static visual content
     */
    private void addKeyTime(int time, double x, double y,
                             double r, Content content)
    {
       addKeyTime(time, new Point2D.Double(x, y), new Double(r),
                   new Double(1.0), content);
    }

}
        
Tweening Samples and Descriptions (cont.)

Tweening Sampled Content - Another Example

javaexamples/visual/dynamic/described/Professor.java
        package visual.dynamic.described;


import java.awt.geom.Point2D;

import visual.statik.sampled.*;


/**
 * A morphing Professor
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 *
 */
public class Professor extends SampledSprite
{

    /**
     * Default Constructor
     *
     */
    public Professor()
    {
        super();
        Content         content;
        ContentFactory  factory;       

        factory = new ContentFactory();
        content = factory.createContent(
           "/visual/dynamic/described/bernstein.gif", 4);

        addKeyTime(    0, 0.0, 0.0, content);
        content = factory.createContent(
           "/visual/dynamic/described/abzug.gif", 4);
        addKeyTime(10000, 0.0, 0.0, content);        
        
        setEndState(REMAIN);
    }




    /**
     * Add a key time
     *
     * @param time    The key time
     * @param x       The x position
     * @param y       The y position
     * @param r       The rotation angle
     * @param image   The image
     */
    private void addKeyTime(int time, double x, double y,
                            Content content)
    {
       addKeyTime(time, new Point2D.Double(x, y), new Double(0.0),
                   new Double(1.0), content);
    }

}
        
Tweening Samples and Descriptions (cont.)

Shape Tweening

images/tweening_shape.gif
Tweening Samples and Descriptions (cont.)

Shape Tweening - Using java/awt/geom/PathIterator java/awt/geom/PathIterator

javaexamples/visual/statik/described/Content.java (Fragment: PathIterator)
            /**
     * Get a PathIterator
     *
     * @param transformed  true to get the transformed version
     * @return             The PathIterator
     */
    public PathIterator getPathIterator(boolean transformed)
    {
       if (transformed)
          return transformedShape.getPathIterator(IDENTITY);       
       else
          return originalShape.getPathIterator(IDENTITY);
    }
        
Tweening Samples and Descriptions (cont.)

Shape Tweening - described.AggregateContent

javaexamples/visual/statik/described/AggregateContent.java
        package visual.statik.described;

import java.awt.*;
import java.util.Iterator;


/**
 * A simple collection of Content objects
 *
 * This class is less flexible than the CompositeContent class
 * but is simpler for some purposes.
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class      AggregateContent 
       extends    visual.statik.AbstractAggregateContent<Content>
       implements TransformableContent
{
    /**
     * Default Constructor
     */
    public AggregateContent()
    {
       super();       
    }
    


    /**
     * Set the Color to use for stroking
     *
     * @param color The Color to use (or null to prevent stroking)
     */
    public void setColor(Color color)
    {
       Iterator<Content>  i;
       
       i = iterator();
       while (i.hasNext())
       {
          i.next().setColor(color);          
       }
    }
    


    /**
     * Set the Paint to use for filling
     *
     * @param paint  The Paint to use (or null to prevent filling)
     */
    public void setPaint(Paint paint)
    {
       Iterator<Content>  i;
       
       i = iterator();
       while (i.hasNext())
       {
          i.next().setPaint(paint);          
       }
    }
    


    /**
     * Set the Stroke to use
     *
     * @param stroke  The Stroke to use (or null to use the default)
     */
    public void setStroke(Stroke stroke)
    {
       Iterator<Content>  i;
       
       i = iterator();
       while (i.hasNext())
       {
          i.next().setStroke(stroke);          
       }
    }

}
        
Tweening Samples and Descriptions (cont.)

A DescribedSprite

javaexamples/visual/dynamic/described/DescribedSprite.java
        package visual.dynamic.described;

import java.awt.*;
import java.awt.geom.*;
import java.util.Iterator;
import java.util.Vector;

import visual.statik.described.*;



/**
 * A TweeningSprite that uses described visual content
 *
 * Note: This class uses a Vector of AggregateContent
 * objects rather than CompositeContent objects for
 * simplicity.
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class DescribedSprite extends TweeningSprite
{
    private   AggregateContent          tweened;
    private   Vector<AggregateContent>  content;
    
       
    




    /**
     * Default Constructor
     */
    public DescribedSprite()
    {
       content = new Vector<AggregateContent>();
       tweened = new AggregateContent();
    }
    


    /**
     * Add a key time
     *
     * Note: This method does not ensure that the first key
     * time actually contains static visual content
     *
     * @param keyTime     The time
     * @param location    The location of the sprite at this time
     * @param rotation    The rotation of the sprite (null to align with path)
     * @param scaling     The scaling of the sprite at this time
     * @param ctc         The Content to use (or null to use previous Content)
     */
    public void addKeyTime(int keyTime, Point2D location,
                           Double rotation, Double scaling, 
                           AggregateContent ctc)
    {
       int         index;
       
       index = super.addKeyTime(keyTime, location, rotation, scaling);

       if (index >= 0)
       {
          // If ctc is null then re-use the last CompositeContent
          if (ctc == null) ctc = content.get(index-1); 

          content.insertElementAt(ctc, index);
       }
       
    }




    /**
     * Get the visual content
     * associated with this Sprite
     *
     * Note that the visual content created depends on the
     * current state of this Sprite
     *
     * @param at   The AffineTransform to use
     * @return     The transformed content
     */
    public visual.statik.TransformableContent getContent()
    {
       int                   current, next;
       AggregateContent      currentCTC, nextCTC, result;


       current = getKeyTimeIndex();
       next    = getNextKeyTimeIndex();
       
       result = null;
       
       if (current >= 0)
       {
          currentCTC = content.get(current);
          nextCTC    = content.get(next);

          //result    = currentCTC.clone();
          result    = currentCTC;

          
          if (currentCTC != nextCTC)
          {
             tweenShape(currentCTC, nextCTC, getInterpolationFraction());
             result = tweened;
          }
       }

       return result;       
    }

    
    /**
     * Determine the current 'tweened shape
     *
     * @param a      The first shape
     * @param b      The second shape
     * @param frac   The interpolation fraction
     */
    protected void tweenShape(AggregateContent a, 
                              AggregateContent b,
                              double frac)
    {
       Color                      color;
       float[]                    coords, coordsA, coordsB;
       GeneralPath                gp;
       int                        seg;
       Iterator<Content>          iterA, iterB;
       PathIterator               piA, piB;
       Paint                      paint;
       Content                    shapeA, shapeB;
       Stroke                     stroke;


       //tweened.clear();
       tweened = new AggregateContent();
       
       coordsA = new float[6];
       coordsB = new float[6];
       coords  = new float[6];
       
       iterA = a.iterator();
       iterB = b.iterator();
       
       // Loop over all of the TransformableContent objects
       // in the CompositeTransformableContent
       while (iterA.hasNext())
       {
          shapeA = iterA.next();
          if (iterB.hasNext()) shapeB = iterB.next();
          else                 shapeB = shapeA;
          
          piA = shapeA.getPathIterator(false);
          piB = shapeB.getPathIterator(false);
          
          gp = new GeneralPath();
          gp.setWindingRule(piA.getWindingRule());

          
          // Loop over all of the segments in the 
          // TransformableContent object
          while (!piA.isDone())
          {
             seg = piA.currentSegment(coordsA);
             if (piB.isDone()) // Use the coordinates of the first shape
             {
                for (int i=0; i < coordsA.length; i++) 
                   coords[i] = coordsA[i];
             }
             else           // Interpolate the coordinates
             {
                piB.currentSegment(coordsB);
                
                for (int i=0; i < coordsA.length; i++)
                {
                   coords[i] = coordsA[i] + 
                               (float)frac*(coordsB[i] - coordsA[i]);
                }
             }
             
             // Add to the General Path object
             if      (seg == PathIterator.SEG_MOVETO)
             {
                gp.moveTo(coords[0], coords[1]);
             }
             else if (seg == PathIterator.SEG_LINETO)
             {
                gp.lineTo(coords[0], coords[1]);
             }
             else if (seg == PathIterator.SEG_QUADTO)
             {
                gp.quadTo(coords[0], coords[1], coords[2], coords[3]);
             }
             else if (seg == PathIterator.SEG_CUBICTO)
             {
                gp.curveTo(coords[0], coords[1], 
                           coords[2], coords[3], 
                           coords[4], coords[5]);
             }
             else if (seg == PathIterator.SEG_CLOSE)
             {
                gp.closePath();
             }
             
             piA.next();
             piB.next();
          }
        
          paint  = shapeA.getPaint();
          color  = shapeA.getColor(); // This could also be tweened
          stroke = shapeA.getStroke();

          tweened.add(new Content(gp, color, paint, stroke));
       }
    }
    
}
        
Tweening Samples and Descriptions (cont.)

Shape Tweening - An Example

javaexamples/visual/dynamic/described/BuzzyJumping.java
        package visual.dynamic.described;

import java.awt.geom.Point2D;

import visual.statik.described.*;


/**
 * A Buzzy jumping
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 *
 */
public class BuzzyJumping extends DescribedSprite
{

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

        addKeyTime( 1000,   0.0, 355.0,  0.00, new BuzzyStanding());
        addKeyTime( 1500,   0.0, 355.0,  0.00, new BuzzyCrouching());
        addKeyTime( 2000,   0.0, 355.0,  0.00, new BuzzyLeaningForward());
        addKeyTime( 4000, 180.0,  75.0,  0.00, new BuzzyLeaningForward());
        addKeyTime( 6000, 540.0, 355.0,  0.00, new BuzzyCrouching());        
        addKeyTime( 6500, 540.0, 355.0,  0.00, new BuzzyStanding());        
        
        setEndState(REMAIN);
    }




    /**
     * Add a key time
     *
     * @param time    The key time
     * @param x       The x position
     * @param y       The y position
     * @param r       The rotation angle
     * @param content The static visual content
     */
    private void addKeyTime(int time, double x, double y,
                             double r, AggregateContent content)
    {
       addKeyTime(time, new Point2D.Double(x, y), new Double(r), 
                  new Double(1.0), content);
    }

}
        
Tweening Samples and Descriptions (cont.)

An Example of a DescribedSprite

javaexamples/visual/dynamic/described/BuzzyJumping.java
        package visual.dynamic.described;

import java.awt.geom.Point2D;

import visual.statik.described.*;


/**
 * A Buzzy jumping
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 *
 */
public class BuzzyJumping extends DescribedSprite
{

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

        addKeyTime( 1000,   0.0, 355.0,  0.00, new BuzzyStanding());
        addKeyTime( 1500,   0.0, 355.0,  0.00, new BuzzyCrouching());
        addKeyTime( 2000,   0.0, 355.0,  0.00, new BuzzyLeaningForward());
        addKeyTime( 4000, 180.0,  75.0,  0.00, new BuzzyLeaningForward());
        addKeyTime( 6000, 540.0, 355.0,  0.00, new BuzzyCrouching());        
        addKeyTime( 6500, 540.0, 355.0,  0.00, new BuzzyStanding());        
        
        setEndState(REMAIN);
    }




    /**
     * Add a key time
     *
     * @param time    The key time
     * @param x       The x position
     * @param y       The y position
     * @param r       The rotation angle
     * @param content The static visual content
     */
    private void addKeyTime(int time, double x, double y,
                             double r, AggregateContent content)
    {
       addKeyTime(time, new Point2D.Double(x, y), new Double(r), 
                  new Double(1.0), content);
    }

}
        
Dynamics with Multiple Views

An Animated Diptych

javaexamples/visual/dynamic/described/DiptychApp.java
        package visual.dynamic.described;


import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;

import app.*;
import visual.*;
import visual.dynamic.described.BuzzyOnMars;
import visual.dynamic.described.Stage;
import visual.statik.sampled.*;


/**
 * An example that uses multiple views
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 *
 */
public class   DiptychApp
       extends AbstractMultimediaApp
{
    /**
     * The entry point
     */
    public void init()
    {
       BuzzyOnMars                    buzzy;
       Content                        mars;
       ContentFactory                 factory;
       JFrame                         window2;       
       JPanel                         contentPane;
       Stage                          stage;
       VisualizationRenderer          renderer1, renderer2;       
       VisualizationView              view1, view2;       
       

       // The Stage for Buzzy
       stage = new Stage(50);
       stage.setBackground(Color.white);
       stage.setRestartTime(7000);
       view1 = stage.getView();
       view1.setRenderer(new PartialVisualizationRenderer(
                                view1.getRenderer(),
                                0.0, 0.0));
       view1.setBounds(0,0,320,480);       

       renderer2 = new PartialVisualizationRenderer(
                       new PlainVisualizationRenderer(), 320.0, 0.0);
       view2 = new VisualizationView(stage, renderer2);
       view2.setBounds(0,0,320,480);       
       stage.addView(view2);

       factory = new ContentFactory();       
       
       mars = factory.createContent("/visual/dynamic/described/mars.gif");
       stage.add(mars);


       // Buzzy
       buzzy = new BuzzyOnMars();
       stage.add(buzzy);




       // The content pane for the main window
       contentPane = (JPanel)rootPaneContainer.getContentPane();
       contentPane.add(view1);


       // The content pane for the other window
       window2 = new JFrame();
       window2.setSize(320,480);       
       contentPane = (JPanel)window2.getContentPane();
       contentPane.add(view2);
       window2.setVisible(true);
       

       stage.start();
    }


}
        
Dynamics with Multiple Views (cont.)

Picture-in-a-Picture

javaexamples/visual/dynamic/described/PIPApp.java
        package visual.dynamic.described;

import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;

import app.*;
//import gui.*;
import visual.*;
import visual.dynamic.described.Airplane;
import visual.dynamic.described.BuzzyOnMars;
import visual.dynamic.described.Stage;
import visual.statik.sampled.*;


/**
 * An example that illustrates the use of multiple views
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 *
 */
public class   PIPApp
       extends AbstractMultimediaApp
{
    /**
     * The entry point
     */
    public void init()
    {
       Airplane                      plane;
       BuzzyOnMars                   buzzy;
       Content                       mars;
       ContentFactory                factory;
       JPanel                        contentPane;       
       Stage                         stage1, stage2;
       VisualizationView             view1, view2;
       

       // The Stage for Buzzy
       stage1 = new Stage(50);
       stage1.setBackground(Color.white);
       stage1.setRestartTime(7000);
       view1 = stage1.getView();
       view1.setRenderer(new ScaledVisualizationRenderer(
                                view1.getRenderer(),
                                640.0, 480.0));       
       view1.setBounds(0,0,640,480);       

       factory = new ContentFactory();       
       mars = factory.createContent(
          "/visual/dynamic/described/mars.gif");
       stage1.add(mars);


       // Buzzy
       buzzy = new BuzzyOnMars();
       stage1.add(buzzy);


       // The stage for the airplane
       stage2 = new Stage(50);
       view2 = stage2.getView();
       view2.setRenderer(new ScaledVisualizationRenderer(
                                view2.getRenderer(),
                                640.0, 480.0));
       view2.setBounds(50,50,160,120);     
       view2.setSize(160,120);       
       view2.setBackground(Color.white);
       stage2.setRestartTime(12000);

       // The Airplane
       plane = new Airplane();
       stage2.add(plane);
       
       
       // The content pane
       contentPane = (JPanel)rootPaneContainer.getContentPane();
       contentPane.add(view2);
       contentPane.add(view1);
       
       stage1.start();
       stage2.start();
    }


}
        
Dynamics with Multiple Views (cont.)

Jumbo Tron (Two Views of one Model)

javaexamples/visual/dynamic/described/JumboTronApp.java
        package visual.dynamic.described;

import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;

import app.*;
import visual.*;
import visual.dynamic.described.Airplane;
import visual.dynamic.described.BuzzyOnMars;
import visual.dynamic.described.Stage;
import visual.statik.sampled.*;


/**
 * An example that illustrates the use of multiple views
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 *
 */
public class   JumboTronApp
       extends AbstractMultimediaApp
{
    /**
     * The entry point
     */
    public void init()
    {
       BuzzyOnMars                    buzzy;
       Content                        mars;
       ContentFactory                 factory;
       JPanel                         contentPane;       
       ScaledVisualizationRenderer    renderer2;       
       Stage                          stage;
       VisualizationView              view1, view2;       


       // The Stage for Buzzy
       stage = new Stage(50);
       stage.setBackground(Color.white);
       stage.setRestartTime(7000);
       view1 = stage.getView();
       view1.setBounds(0,0,640,480);       

       
       renderer2 = new ScaledVisualizationRenderer(
                       new PlainVisualizationRenderer(), 640.0, 480.0);
       view2 = new VisualizationView(stage, renderer2);       
       view2.setBounds(50,50,160,120);       
       stage.addView(view2);
       

       factory = new ContentFactory();       
       mars = factory.createContent(
                "/visual/dynamic/described/mars.gif");
       stage.add(mars);


       // Buzzy
       buzzy = new BuzzyOnMars();
       stage.add(buzzy);

       // The content pane
       contentPane = (JPanel)rootPaneContainer.getContentPane();
       contentPane.add(view2);
       contentPane.add(view1);

       stage.start();
    }


}