|
Described Dynamic Visual Content
An Introduction with Examples in Java |
|
Prof. David Bernstein |
| Computer Science Department |
| bernstdh@jmu.edu |
Visualization class, decorating the
Visualization class or specializing
Visualization class
Sprite
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();
}
Stage Classpackage 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();
}
}
/**
* 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();
}
SimpleContent with a particular
AbstractSprite at different points in time
AbstractSprite Classpackage 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;
}
}
/**
* 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);
}
}
}
FloatingSprite
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);
}
}
/**
* 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 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);
}
FloatingSprite
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();
handleTick() method
Rectangular Sprites
Non-Rectangular Sprites
Non-Convex Sprites
Using Bounding Boxes
Sprite Class
/**
* 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;
}
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);
}
}
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);
}
}
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);
}
}
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();
}
}
Sprite a
KeyListener, MouseListener or
MouseMotionListener
Visualization Class
/**
* 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);
}
}
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);
}
}
/**
* 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();
}
/**
* 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);
}
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);
}
}
TransformableContent objects themselves
TweeningSprite Classpackage 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();
}
}
/**
* 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;
}
TweeningSprite Class
/**
* 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);
}
TweeningSprite Class
/**
* 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);
}
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);
}
}
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);
}
}
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);
}
}
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);
}
}
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;
}
}
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);
}
}
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);
}
}
Shape Tweening
java/awt/geom/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);
}
described.AggregateContent
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);
}
}
}
DescribedSprite
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));
}
}
}
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);
}
}
DescribedSprite
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);
}
}
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();
}
}
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();
}
}
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();
}
}