Major low-level rendering rethink. There will be much follow-on cleanup.
git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1286 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -0,0 +1,482 @@
|
||||
//
|
||||
// $Id: FrameManager.java,v 1.1 2002/04/23 01:16:27 mdb Exp $
|
||||
|
||||
package com.threerings.media;
|
||||
|
||||
import java.applet.Applet;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Frame;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.GraphicsConfiguration;
|
||||
import java.awt.GraphicsDevice;
|
||||
import java.awt.Image;
|
||||
import java.awt.Insets;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Window;
|
||||
|
||||
import java.awt.image.BufferStrategy;
|
||||
import java.awt.image.VolatileImage;
|
||||
|
||||
import java.awt.EventQueue;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.RepaintManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.samskivert.util.Interval;
|
||||
import com.samskivert.util.IntervalManager;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.media.util.PerformanceMonitor;
|
||||
import com.threerings.media.util.PerformanceObserver;
|
||||
|
||||
/**
|
||||
* Provides a central point from which the computation for each "frame" or
|
||||
* tick can be dispatched. This assumed that the application structures
|
||||
* its activity around the rendering of each frame, which is a common
|
||||
* architecture for games. The animation and sprite support provided by
|
||||
* other classes in this package are structured for use in an application
|
||||
* that uses a frame manager to tick everything once per frame.
|
||||
*
|
||||
* <p> The frame manager goes through a simple two part procedure every
|
||||
* frame:
|
||||
*
|
||||
* <ul>
|
||||
* <li> Ticking all of the frame participants: in {@link
|
||||
* FrameParticipant#tick}, any processing that need be performed during
|
||||
* this frame should be performed. Care should be taken not to execute
|
||||
* code that will take unduly long, instead such processing should be
|
||||
* broken up so that it can be performed in small pieces every frame (or
|
||||
* performed on a separate thread with the results safely communicated
|
||||
* back to the frame participants for incorporation into the rendering
|
||||
* loop).
|
||||
*
|
||||
* <li> Painting the user interface hierarchy: the top-level component
|
||||
* (the frame) is painted (via a call to {@link Frame#paint}) into a flip
|
||||
* buffer (if supported, an off-screen buffer if not). Updates that were
|
||||
* computed during the tick should be rendered in this call to paint. The
|
||||
* paint call will propagate down to all components in the UI hierarchy,
|
||||
* some of which may be {@link FrameParticipants} and will have prepared
|
||||
* themselves for their upcoming painting in the previous call to {@link
|
||||
* FrameParticipant#tick}. When the call to paint completes, the flip
|
||||
* buffer is flipped and the process starts all over again.
|
||||
* </ul>
|
||||
*
|
||||
* <p> The ticking and rendering takes place on the AWT thread so as to
|
||||
* avoid the need for complicated coordination between AWT event handler
|
||||
* code and frame code. However, this means that all AWT (and Swing) event
|
||||
* handlers <em>must not</em> perform any complicated processing. After
|
||||
* each frame, control of the AWT thread is given back to the AWT which
|
||||
* processes all pending AWT events before giving the frame manager an
|
||||
* opportunity to process the next frame. Thus the convenience of
|
||||
* everything running on the AWT thread comes with the price of requiring
|
||||
* that AWT event handlers not block or perform any intensive processing.
|
||||
* In general, this is a sensible structure for an application anyhow, so
|
||||
* this organization tends to be preferable to an organization where the
|
||||
* AWT and frame threads are separate and must tread lightly so as not to
|
||||
* collide.
|
||||
*/
|
||||
public class FrameManager
|
||||
implements PerformanceObserver
|
||||
{
|
||||
/**
|
||||
* Constructs a frame manager that will do its rendering to the
|
||||
* supplied frame. It is likely that the caller will want to have put
|
||||
* the frame into full-screen exclusive mode prior to providing it to
|
||||
* the frame manager so that the frame manager can take advantage of
|
||||
* optimizations available in that mode.
|
||||
*
|
||||
* @see GraphicsDevice#setFullScreenWindow
|
||||
*/
|
||||
public FrameManager (Frame frame)
|
||||
{
|
||||
_frame = frame;
|
||||
_frame.setIgnoreRepaint(true);
|
||||
|
||||
// set up our custom repaint manager
|
||||
_remgr = new FrameRepaintManager();
|
||||
RepaintManager.setCurrentManager(_remgr);
|
||||
|
||||
// turn off double buffering for the whole business because we
|
||||
// handle repaints
|
||||
_remgr.setDoubleBufferingEnabled(false);
|
||||
|
||||
// register with the performance monitor
|
||||
PerformanceMonitor.register(this, "frame-rate", 1000l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the frame manager to target the specified number of
|
||||
* frames per second. If the computation and rendering for a frame are
|
||||
* completed with time to spare, the frame manager will wait until the
|
||||
* proper time to begin processing for the next frame. If a frame
|
||||
* takes longer than its alotted time, the frame manager will
|
||||
* immediately begin processing on the next frame.
|
||||
*/
|
||||
public void setTargetFrameRate (int fps)
|
||||
{
|
||||
// compute the number of milliseconds per frame
|
||||
_millisPerFrame = 1000/fps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a frame participant. The participant will be given the
|
||||
* opportunity to do processing and rendering on each frame.
|
||||
*/
|
||||
public void registerFrameParticipant (FrameParticipant participant)
|
||||
{
|
||||
_participants.add(participant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a frame participant.
|
||||
*/
|
||||
public void removeFrameParticipant (FrameParticipant participant)
|
||||
{
|
||||
_participants.remove(participant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts up the per-frame tick
|
||||
*/
|
||||
public void start ()
|
||||
{
|
||||
if (_ticker == null) {
|
||||
// create ticker for queueing up tick requests on AWT thread
|
||||
_ticker = new Ticker();
|
||||
// and start it up
|
||||
_ticker.start();
|
||||
// and kick off our first frame
|
||||
_ticker.tickIn(_millisPerFrame, System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the per-frame tick.
|
||||
*/
|
||||
public synchronized void stop ()
|
||||
{
|
||||
if (_ticker != null) {
|
||||
_ticker = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the tick interval is be running (not necessarily at
|
||||
* that instant, but in general).
|
||||
*/
|
||||
public synchronized boolean isRunning ()
|
||||
{
|
||||
return (_ticker != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to perform the frame processing and rendering.
|
||||
*/
|
||||
protected void tick ()
|
||||
{
|
||||
// if our frame is not showing (or is impossibly sized), don't try
|
||||
// rendering anything
|
||||
if (_frame.isShowing() &&
|
||||
_frame.getWidth() > 0 && _frame.getHeight() > 0) {
|
||||
long tickStamp = System.currentTimeMillis();
|
||||
// tick our participants
|
||||
tickParticipants(tickStamp);
|
||||
// repaint our participants
|
||||
paintParticipants(tickStamp);
|
||||
}
|
||||
|
||||
// now determine how many milliseconds we have left before we need
|
||||
// to start the next frame (if any)
|
||||
long end = System.currentTimeMillis();
|
||||
long duration = end - _frameStart;
|
||||
long remaining = _millisPerFrame - duration;
|
||||
|
||||
// note that we've done a frame
|
||||
PerformanceMonitor.tick(this, "frame-rate");
|
||||
|
||||
// if we have no time remaining, queue up another tick immediately
|
||||
if (remaining <= 0) {
|
||||
// make a note that we're starting our next frame now
|
||||
_frameStart = end;
|
||||
EventQueue.invokeLater(_callTick);
|
||||
|
||||
} else {
|
||||
// otherwise queue one up in the requisite number of millis
|
||||
_ticker.tickIn(remaining, end);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once per frame to invoke {@link FrameParticipant#tick} on
|
||||
* all of our frame participants.
|
||||
*/
|
||||
protected void tickParticipants (long tickStamp)
|
||||
{
|
||||
// tick all of our frame participants
|
||||
int pcount = _participants.size();
|
||||
for (int ii = 0; ii < pcount; ii++) {
|
||||
FrameParticipant part = (FrameParticipant)
|
||||
_participants.get(ii);
|
||||
try {
|
||||
part.tick(tickStamp);
|
||||
} catch (Throwable t) {
|
||||
Log.warning("Frame participant choked during tick " +
|
||||
"[part=" +
|
||||
StringUtil.safeToString(part) + "].");
|
||||
Log.logStackTrace(t);
|
||||
}
|
||||
}
|
||||
|
||||
// validate any invalid components
|
||||
try {
|
||||
_remgr.validateComponents();
|
||||
} catch (Throwable t) {
|
||||
Log.warning("Failure validating components.");
|
||||
Log.logStackTrace(t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once per frame to invoke {@link FrameParticipant#paint} on
|
||||
* all of our frame participants.
|
||||
*/
|
||||
protected void paintParticipants (long tickStamp)
|
||||
{
|
||||
// create our buffer strategy if we don't already have one
|
||||
if (_bufstrat == null) {
|
||||
_frame.createBufferStrategy(2);
|
||||
_bufstrat = _frame.getBufferStrategy();
|
||||
}
|
||||
|
||||
// create our off-screen buffer if necessary
|
||||
GraphicsConfiguration gc = _frame.getGraphicsConfiguration();
|
||||
if (_backimg == null) {
|
||||
createBackBuffer(gc);
|
||||
}
|
||||
|
||||
// render into our back buffer
|
||||
do {
|
||||
// make sure our back buffer hasn't disappeared
|
||||
int valres = _backimg.validate(gc);
|
||||
|
||||
// if we've changed resolutions, recreate the buffer
|
||||
if (valres == VolatileImage.IMAGE_INCOMPATIBLE) {
|
||||
Log.info("Back buffer incompatible, recreating.");
|
||||
createBackBuffer(gc);
|
||||
}
|
||||
|
||||
Rectangle bounds = new Rectangle();
|
||||
Graphics g = null, fg = null;
|
||||
Insets fi = _frame.getInsets();
|
||||
try {
|
||||
g = _backimg.getGraphics();
|
||||
fg = _frame.getGraphics();
|
||||
|
||||
// if the image wasn't A-OK, we need to rerender the
|
||||
// whole business rather than just the dirty parts
|
||||
if (valres != VolatileImage.IMAGE_OK) {
|
||||
Log.info("Lost back buffer, redrawing.");
|
||||
}
|
||||
|
||||
// repaint any widgets that have declared there need to be
|
||||
// repainted since the last tick
|
||||
_remgr.paintComponents(g);
|
||||
|
||||
// paint our frame participants (which want to be handled
|
||||
// specially)
|
||||
int pcount = _participants.size();
|
||||
for (int ii = 0; ii < pcount; ii++) {
|
||||
FrameParticipant part = (FrameParticipant)
|
||||
_participants.get(ii);
|
||||
Component pcomp = part.getComponent();
|
||||
if (pcomp == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get the bounds of this component
|
||||
pcomp.getBounds(bounds);
|
||||
|
||||
// convert them into top-level coordinates; also note
|
||||
// that if this component does not have a valid or
|
||||
// visible root, we don't want to paint it either
|
||||
if (getRoot(pcomp, bounds) == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// render this participant
|
||||
// Log.info("Rendering [comp=" + pcomp.getClass().getName() +
|
||||
// ", bounds=" + StringUtil.toString(bounds) + "].");
|
||||
g.setClip(bounds);
|
||||
g.translate(bounds.x, bounds.y);
|
||||
pcomp.paint(g);
|
||||
g.translate(-bounds.x, -bounds.y);
|
||||
|
||||
// // copy the off-screen buffer on-screen
|
||||
// fg.setClip(bounds);
|
||||
// fg.drawImage(_backimg, fi.left, fi.top, null);
|
||||
|
||||
} catch (Throwable t) {
|
||||
String ptos = StringUtil.safeToString(part);
|
||||
Log.warning("Frame participant choked during paint " +
|
||||
"[part=" + ptos + "].");
|
||||
Log.logStackTrace(t);
|
||||
}
|
||||
}
|
||||
|
||||
// Log.info("insets: " + fi + ", fb: " + _frame.getBounds());
|
||||
// _frame.paint(g);
|
||||
fg.drawImage(_backimg, 0, 0, null);
|
||||
|
||||
} finally {
|
||||
if (g != null) {
|
||||
g.dispose();
|
||||
}
|
||||
if (fg != null) {
|
||||
fg.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
} while (_backimg.contentsLost());
|
||||
|
||||
// Graphics g = null;
|
||||
// try {
|
||||
// g = _bufstrat.getDrawGraphics();
|
||||
// _frame.paint(g);
|
||||
// _bufstrat.show();
|
||||
|
||||
// } catch (Throwable t) {
|
||||
// Log.warning("Frame rendering choked.");
|
||||
// Log.logStackTrace(t);
|
||||
|
||||
// } finally {
|
||||
// if (g != null) {
|
||||
// g.dispose();
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void checkpoint (String name, int ticks)
|
||||
{
|
||||
// Log.info("Frames in last second: " + ticks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to queue up frame ticks on the AWT thread at some point in the
|
||||
* future.
|
||||
*/
|
||||
protected class Ticker extends Thread
|
||||
{
|
||||
/**
|
||||
* Tells the ticker to queue up a frame in the requisite number of
|
||||
* milliseconds.
|
||||
*/
|
||||
public synchronized void tickIn (long millis, long now)
|
||||
{
|
||||
_sleepfor = millis;
|
||||
_now = now;
|
||||
this.notify();
|
||||
}
|
||||
|
||||
public void run ()
|
||||
{
|
||||
synchronized (this) {
|
||||
while (_sleepfor != -1) {
|
||||
try {
|
||||
if (_sleepfor == 0) {
|
||||
this.wait();
|
||||
}
|
||||
if (_sleepfor > 0) {
|
||||
Thread.sleep(_sleepfor);
|
||||
|
||||
// make a note of our frame start time
|
||||
_frameStart = System.currentTimeMillis();
|
||||
// long error =_frameStart - (_sleepfor + _now);
|
||||
// if (Math.abs(error) > 3) {
|
||||
// Log.warning("Funny business: " + error);
|
||||
// }
|
||||
|
||||
// queue up our ticker on the AWT thread
|
||||
EventQueue.invokeLater(_callTick);
|
||||
_sleepfor = 0;
|
||||
}
|
||||
|
||||
} catch (InterruptedException ie) {
|
||||
Log.warning("Girl interrupted!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected long _sleepfor = 0l;
|
||||
protected long _now = 0l;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the off-screen buffer used to perform double buffered
|
||||
* rendering of the animated panel.
|
||||
*/
|
||||
protected void createBackBuffer (GraphicsConfiguration gc)
|
||||
{
|
||||
_backimg = gc.createCompatibleVolatileImage(
|
||||
_frame.getWidth(), _frame.getHeight());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root component for the supplied component or null if it
|
||||
* is not part of a rooted hierarchy or if any parent along the way is
|
||||
* found to be hidden or without a peer. Along the way, it adjusts the
|
||||
* supplied component-relative rectangle to be relative to the
|
||||
* returned root component.
|
||||
*/
|
||||
public static Component getRoot (Component comp, Rectangle rect)
|
||||
{
|
||||
for (Component c = comp; c != null; c = c.getParent()) {
|
||||
if (!c.isVisible() || c.getPeer() == null) {
|
||||
return null;
|
||||
}
|
||||
if (c instanceof Window || c instanceof Applet) {
|
||||
return c;
|
||||
}
|
||||
rect.x += c.getX();
|
||||
rect.y += c.getY();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** The frame into which we do our rendering. */
|
||||
protected Frame _frame;
|
||||
|
||||
/** Our custom repaint manager. */
|
||||
protected FrameRepaintManager _remgr;
|
||||
|
||||
/** The buffer strategy used to do our rendering. */
|
||||
protected BufferStrategy _bufstrat;
|
||||
|
||||
/** The image used to render off-screen. */
|
||||
protected VolatileImage _backimg;
|
||||
|
||||
/** The number of milliseconds per frame (33 by default, which gives
|
||||
* an fps of 30). */
|
||||
protected long _millisPerFrame = 33;
|
||||
|
||||
/** The time at which we started the most recent "frame". */
|
||||
protected long _frameStart;
|
||||
|
||||
/** Used to queue up a tick. */
|
||||
protected Ticker _ticker;
|
||||
|
||||
/** Used to queue up a call to {@link #tick} on the AWT thread. */
|
||||
protected Runnable _callTick = new Runnable () {
|
||||
public void run () {
|
||||
tick();
|
||||
}
|
||||
};
|
||||
|
||||
/** The entites that are ticked each frame. */
|
||||
protected ArrayList _participants = new ArrayList();
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// $Id: FrameParticipant.java,v 1.1 2002/04/23 01:16:27 mdb Exp $
|
||||
|
||||
package com.threerings.media;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
/**
|
||||
* Provides a mechanism for participating in the frame tick managed by the
|
||||
* {@link FrameManager}.
|
||||
*/
|
||||
public interface FrameParticipant
|
||||
{
|
||||
/**
|
||||
* This is called on all registered frame participants, one for every
|
||||
* frame. Following the tick the interface will be rendered, so
|
||||
* participants can prepare themselves for their upcoming render in
|
||||
* this method (making use of the timestamp provided for the frame if
|
||||
* choreography is desired between different participants).
|
||||
*/
|
||||
public void tick (long tickStamp);
|
||||
|
||||
/**
|
||||
* If a frame participant wishes also to be actively rendered every
|
||||
* frame rather than use passive rendering (which for Swing, at least,
|
||||
* is hijacked when using the frame manager such that we take care of
|
||||
* repainting dirty Swing components every frame into our off-screen
|
||||
* buffer), it can return a component here which will have {@link
|
||||
* Component#paint} called on it once per frame with a properly
|
||||
* configured graphics object. If a particpant does not wish to be
|
||||
* actively rendered, it can safely return null.
|
||||
*/
|
||||
public Component getComponent ();
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
//
|
||||
// $Id: FrameRepaintManager.java,v 1.1 2002/04/23 01:16:27 mdb Exp $
|
||||
|
||||
package com.threerings.media;
|
||||
|
||||
import java.applet.Applet;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Window;
|
||||
|
||||
import javax.swing.CellRendererPane;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.RepaintManager;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import com.samskivert.util.ListUtil;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* Used to get Swing's repainting to jive with our active rendering
|
||||
* strategy.
|
||||
*
|
||||
* @see FrameManager
|
||||
*/
|
||||
public class FrameRepaintManager extends RepaintManager
|
||||
{
|
||||
// documentation inherited
|
||||
public synchronized void addInvalidComponent (JComponent comp)
|
||||
{
|
||||
Component vroot = null;
|
||||
|
||||
// locate the validation root for this component
|
||||
for (Component c = comp; c != null; c = c.getParent()) {
|
||||
// if the component is not part of an active widget hierarcy,
|
||||
// we can stop now; if the component is a cell render pane,
|
||||
// we're apparently supposed to ignore it as wel
|
||||
if (c.getPeer() == null || c instanceof CellRendererPane) {
|
||||
return;
|
||||
}
|
||||
|
||||
// skip non-Swing components
|
||||
if (!(c instanceof JComponent)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if we find our validate root, we can stop looking
|
||||
if (((JComponent)c).isValidateRoot()) {
|
||||
vroot = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if we found no validation root we can abort as this component
|
||||
// is not part of any valid widget hierarchy
|
||||
if (vroot == null) {
|
||||
// Log.info("Skipping vrootless component: " + comp);
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure that the component is actually in a window or applet
|
||||
// that is showing
|
||||
if (getRoot(vroot) == null) {
|
||||
// Log.info("Skipping rootless component: " + comp + "/" + vroot);
|
||||
return;
|
||||
}
|
||||
|
||||
// add the invalid component to our list and we'll validate it on
|
||||
// the next frame
|
||||
if (!ListUtil.contains(_invalid, vroot)) {
|
||||
_invalid = ListUtil.add(_invalid, vroot);
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public synchronized void addDirtyRegion (
|
||||
JComponent comp, int x, int y, int width, int height)
|
||||
{
|
||||
// ignore invalid requests
|
||||
if ((width <= 0) || (height <= 0) || (comp == null) ||
|
||||
(comp.getWidth() <= 0) || (comp.getHeight() <= 0)) {
|
||||
// Log.info("Skipping bogus region " + comp.getClass().getName() +
|
||||
// ", x=" + x + ", y=" + y + ", width=" + width + ", height=" + height + ".");
|
||||
return;
|
||||
}
|
||||
|
||||
// if this component is already dirty, simply expand their
|
||||
// existing dirty rectangle
|
||||
Rectangle drect = (Rectangle)_dirty.get(comp);
|
||||
if (drect != null) {
|
||||
drect.add(x, y);
|
||||
drect.add(x+width, y+height);
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure this component has a valid root
|
||||
if (getRoot(comp) == null) {
|
||||
// Log.info("Skipping rootless repaint " + comp + ".");
|
||||
return;
|
||||
}
|
||||
|
||||
// if we made it this far, we can queue up a dirty region for this
|
||||
// component to be repainted on the next tick
|
||||
_dirty.put(comp, new Rectangle(x, y, width, height));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root component for the supplied component or null if it
|
||||
* is not part of a rooted hierarchy or if any parent along the way is
|
||||
* found to be hidden or without a peer.
|
||||
*/
|
||||
protected Component getRoot (Component comp)
|
||||
{
|
||||
for (Component c = comp; c != null; c = c.getParent()) {
|
||||
if (!c.isVisible() || c.getPeer() == null) {
|
||||
return null;
|
||||
}
|
||||
if (c instanceof Window || c instanceof Applet) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public synchronized Rectangle getDirtyRegion (JComponent comp)
|
||||
{
|
||||
Rectangle drect = (Rectangle)_dirty.get(comp);
|
||||
// copy the rectangle if we found one, otherwise create an empty
|
||||
// rectangle because we don't want them leaving empty handed
|
||||
return (drect == null) ?
|
||||
new Rectangle(0, 0, 0, 0) : new Rectangle(drect);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public synchronized void markCompletelyClean (JComponent comp)
|
||||
{
|
||||
_dirty.remove(comp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the invalid components that have been queued up since the
|
||||
* last frame tick.
|
||||
*/
|
||||
public void validateComponents ()
|
||||
{
|
||||
// swap out our invalid array
|
||||
Object[] invalid = null;
|
||||
synchronized (this) {
|
||||
invalid = _invalid;
|
||||
_invalid = null;
|
||||
}
|
||||
|
||||
// if there's nothing to validate, we're home free
|
||||
if (invalid == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// validate everything therein
|
||||
int icount = invalid.length;
|
||||
for (int ii = 0; ii < icount; ii++) {
|
||||
if (invalid[ii] != null) {
|
||||
// Log.info("Validating " + invalid[ii]);
|
||||
((Component)invalid[ii]).validate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Paints the components that have become dirty since the last tick.
|
||||
*/
|
||||
public void paintComponents (Graphics g)
|
||||
{
|
||||
// swap out our hashmap
|
||||
synchronized (this) {
|
||||
HashMap tmap = _spare;
|
||||
_spare = _dirty;
|
||||
_dirty = tmap;
|
||||
}
|
||||
|
||||
// now paint each of the dirty components, by setting the clipping
|
||||
// rectangle appropriately and calling paint() on the associated
|
||||
// root component
|
||||
Iterator iter = _spare.entrySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
Entry entry = (Entry)iter.next();
|
||||
JComponent comp = (JComponent)entry.getKey();
|
||||
Rectangle drect = (Rectangle)entry.getValue();
|
||||
Rectangle orect = (Rectangle)drect.clone();
|
||||
Component root = FrameManager.getRoot(comp, drect);
|
||||
|
||||
Rectangle cbounds =
|
||||
new Rectangle(0, 0, comp.getWidth(), comp.getHeight());
|
||||
Rectangle obounds = comp.getBounds();
|
||||
FrameManager.getRoot(comp, cbounds);
|
||||
|
||||
Rectangle clip = drect.intersection(cbounds);
|
||||
|
||||
if (root != null) {
|
||||
// if (!(comp instanceof JButton)) {
|
||||
// Log.info("Repainting [comp=" + comp.getClass().getName() +
|
||||
// StringUtil.toString(obounds) +
|
||||
// StringUtil.toString(cbounds) +
|
||||
// ", root=" + root.getClass().getName() +
|
||||
// ", clip=" + StringUtil.toString(clip) +
|
||||
// ", drect=" + StringUtil.toString(drect) +
|
||||
// ", orect=" + StringUtil.toString(orect) + "].");
|
||||
// }
|
||||
g.setClip(clip);
|
||||
root.paint(g);
|
||||
}
|
||||
}
|
||||
|
||||
// clear out the mapping of dirty components
|
||||
_spare.clear();
|
||||
}
|
||||
|
||||
/** A list of invalid components. */
|
||||
protected Object[] _invalid;
|
||||
|
||||
/** A mapping of invalid rectangles for each widget that is dirty. */
|
||||
protected HashMap _dirty = new HashMap();
|
||||
|
||||
/** A spare hashmap that we swap in while repainting dirty components
|
||||
* in the old hashmap. */
|
||||
protected HashMap _spare = new HashMap();
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// $Id: ManagedJFrame.java,v 1.1 2002/04/23 01:16:27 mdb Exp $
|
||||
|
||||
package com.threerings.media;
|
||||
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Rectangle;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.RepaintManager;
|
||||
|
||||
import com.threerings.media.Log;
|
||||
|
||||
/**
|
||||
* When using the {@link FrameManager}, one must use this top-level frame
|
||||
* class (or the {@link ManagedFrame} class if one is not using Swing.
|
||||
*/
|
||||
public class ManagedJFrame extends JFrame
|
||||
{
|
||||
/**
|
||||
* Constructs a managed frame with no title.
|
||||
*/
|
||||
public ManagedJFrame ()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a managed frame with the specified title.
|
||||
*/
|
||||
public ManagedJFrame (String title)
|
||||
{
|
||||
super(title);
|
||||
}
|
||||
|
||||
/**
|
||||
* We catch update requests and forward them on to the repaint
|
||||
* infrastructure.
|
||||
*/
|
||||
public void update (Graphics g)
|
||||
{
|
||||
Rectangle clip = g.getClip().getBounds();
|
||||
if (clip != null) {
|
||||
RepaintManager.currentManager(this).addDirtyRegion(
|
||||
getRootPane(), clip.x, clip.y, clip.width, clip.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// $Id: MediaConstants.java,v 1.1 2002/04/23 01:16:27 mdb Exp $
|
||||
|
||||
package com.threerings.media;
|
||||
|
||||
/**
|
||||
* Constants specific to the media package.
|
||||
*/
|
||||
public interface MediaConstants
|
||||
{
|
||||
/** Identifies the back "layer" of sprites and animations. */
|
||||
public static final int BACK = 1;
|
||||
|
||||
/** Identifies the front "layer" of sprites and animations. */
|
||||
public static final int FRONT = 2;
|
||||
|
||||
/** Identifies the all "layers" of sprites and animations. */
|
||||
public static final int ALL = 3;
|
||||
}
|
||||
@@ -0,0 +1,486 @@
|
||||
//
|
||||
// $Id: MediaPanel.java,v 1.1 2002/04/23 01:16:27 mdb Exp $
|
||||
|
||||
package com.threerings.media;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Shape;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.RepaintManager;
|
||||
import javax.swing.event.AncestorEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.samskivert.swing.event.AncestorAdapter;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.media.FrameManager;
|
||||
import com.threerings.media.Log;
|
||||
|
||||
import com.threerings.media.animation.Animation;
|
||||
import com.threerings.media.animation.AnimationManager;
|
||||
|
||||
import com.threerings.media.sprite.Sprite;
|
||||
import com.threerings.media.sprite.SpriteManager;
|
||||
|
||||
/**
|
||||
* Provides a useful extensible framework for rendering animated displays
|
||||
* that use sprites and animations. Sprites and animations can be added to
|
||||
* this panel and they will automatically be ticked and rendered (see
|
||||
* {@link #addSprite} and {@link #addAnimation}).
|
||||
*
|
||||
* <p> To facilitate optimized sprite and animation rendering, the panel
|
||||
* provides a dirty region manager which is used to only repaint dirtied
|
||||
* regions on each frame. Derived classes can add dirty regions to the
|
||||
* scene and/or augment the dirty regions created by moving sprites and
|
||||
* changing animations.
|
||||
*
|
||||
* <p> Sprite and animation rendering is done in two layers: front and
|
||||
* back. Callbacks are provided to render behind the back layer ({@link
|
||||
* #paintBehind}), in between front and back ({@link #paintBetween}) and
|
||||
* in front of the front layer ({@link #paintInFront}).
|
||||
*
|
||||
* <p> The animated panel automatically registers with the {@link
|
||||
* FrameManager} to participate in the frame tick. It only does so while
|
||||
* it is a visible part of the UI hierarchy, so animations and sprites are
|
||||
* paused while the animated panel is hidden.
|
||||
*/
|
||||
public class MediaPanel extends JComponent
|
||||
implements FrameParticipant, MediaConstants
|
||||
{
|
||||
/**
|
||||
* Constructs an animated panel.
|
||||
*/
|
||||
public MediaPanel (FrameManager framemgr)
|
||||
{
|
||||
// keep this for later
|
||||
_framemgr = framemgr;
|
||||
|
||||
// create our region manager
|
||||
_remgr = new RegionManager();
|
||||
|
||||
// create our animation and sprite managers
|
||||
_animmgr = new AnimationManager(_remgr);
|
||||
_spritemgr = new SpriteManager(_remgr);
|
||||
|
||||
// we don't want to hear about repaints
|
||||
setIgnoreRepaint(true);
|
||||
|
||||
// participate in the frame when we're visible
|
||||
addAncestorListener(new AncestorAdapter() {
|
||||
public void ancestorAdded (AncestorEvent event) {
|
||||
_framemgr.registerFrameParticipant(MediaPanel.this);
|
||||
}
|
||||
public void ancestorRemoved (AncestorEvent event) {
|
||||
_framemgr.removeFrameParticipant(MediaPanel.this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the view to scroll by the specified number of pixels
|
||||
* (which can be negative if it should scroll in the negative x or y
|
||||
* direction) in the specified number of milliseconds. While the view
|
||||
* is scrolling, derived classes can hear about the scrolled
|
||||
* increments by overriding {@link #viewWillScroll} and they can find
|
||||
* out when scrolling is complete by overriding {@link
|
||||
* #viewFinishedScrolling}.
|
||||
*
|
||||
* @param dx the number of pixels in the x direction to scroll.
|
||||
* @param dy the number of pixels in the y direction to scroll.
|
||||
* @param millis the number of milliseconds in which to do it. The
|
||||
* scrolling is calculated such that we will "drop frames" in order to
|
||||
* scroll the necessary distance by the requested time.
|
||||
*/
|
||||
public void setScrolling (int dx, int dy, long millis)
|
||||
{
|
||||
// if dx and dy are zero, we've got nothing to do
|
||||
if (dx == 0 && dy == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// set our scrolling parameters
|
||||
_scrollx = dx;
|
||||
_scrolly = dy;
|
||||
_last = System.currentTimeMillis();
|
||||
_ttime = _last + millis;
|
||||
|
||||
// // figure out the lesser (but non-zero) of the two scroll deltas
|
||||
// int absx = Math.abs(dx), absy = Math.abs(dy);
|
||||
// int mindist = absx;
|
||||
// if (absx == 0) {
|
||||
// mindist = absy;
|
||||
// } else if (absy == 0) {
|
||||
// mindist = absx;
|
||||
// } else {
|
||||
// mindist = Math.min(absx, absy);
|
||||
// }
|
||||
|
||||
// // let the animation manager know how "fast" we'll be scrolling so
|
||||
// // that it can determine its frame rate
|
||||
// int mspp = (int)(millis/mindist);
|
||||
// _animmgr.setScrolling(mspp);
|
||||
|
||||
// Log.info("Scrolling [dx=" + dx + ", dy=" + dy +
|
||||
// ", millis=" + millis + "ms, mspp=" + mspp + "].");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the region manager that is used to
|
||||
* accumulate dirty regions each frame.
|
||||
*/
|
||||
public RegionManager getRegionManager ()
|
||||
{
|
||||
return _remgr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a sprite to this panel.
|
||||
*/
|
||||
public void addSprite (Sprite sprite)
|
||||
{
|
||||
_spritemgr.addSprite(sprite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a sprite from this panel.
|
||||
*/
|
||||
public void removeSprite (Sprite sprite)
|
||||
{
|
||||
_spritemgr.removeSprite(sprite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an animation to this panel.
|
||||
*/
|
||||
public void addAnimation (Animation anim)
|
||||
{
|
||||
_animmgr.registerAnimation(anim);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an animation from this panel.
|
||||
*/
|
||||
public void removeAnimation (Animation anim)
|
||||
{
|
||||
_animmgr.unregisterAnimation(anim);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void doLayout ()
|
||||
{
|
||||
super.doLayout();
|
||||
|
||||
// figure out our viewport offsets
|
||||
Dimension size = getSize(), vsize = getViewSize();
|
||||
_tx = (vsize.width - size.width)/2;
|
||||
_ty = (vsize.height - size.height)/2;
|
||||
|
||||
// Log.info("Size: " + size + ", vsize: " + vsize +
|
||||
// ", tx: " + _ty + ", ty: " + _ty + ".");
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void invalidate ()
|
||||
{
|
||||
super.invalidate();
|
||||
// invalidate our bounds with the region manager
|
||||
_remgr.invalidateRegion(getBounds());
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void tick (long tickStamp)
|
||||
{
|
||||
int width = getWidth(), height = getHeight();
|
||||
|
||||
// if scrolling is enabled, determine the scrolling delta to be
|
||||
// used and do the business
|
||||
_dx = 0;
|
||||
_dy = 0;
|
||||
if (_ttime != 0) {
|
||||
// if we've blown past our allotted time, we want to scroll
|
||||
// the rest of the way
|
||||
if (tickStamp > _ttime) {
|
||||
_dx = _scrollx;
|
||||
_dy = _scrolly;
|
||||
|
||||
// Log.info("Scrolling rest [dx=" + dx + ", dy=" + dy + "].");
|
||||
|
||||
} else {
|
||||
// otherwise figure out how many milliseconds have gone by
|
||||
// since we last scrolled and scroll the requisite amount
|
||||
float dt = (float)(tickStamp - _last);
|
||||
float rt = (float)(_ttime - _last);
|
||||
|
||||
// our delta is the remaining distance multiplied by the
|
||||
// time delta divided by the remaining time
|
||||
_dx = Math.round((float)(_scrollx * dt) / rt);
|
||||
_dy = Math.round((float)(_scrolly * dt) / rt);
|
||||
|
||||
// Log.info("Scrolling delta [dt=" + dt + ", rt=" + rt +
|
||||
// ", dx=" + dx + ", dy=" + dy + "].");
|
||||
}
|
||||
|
||||
// and add invalid rectangles for the exposed areas
|
||||
if (_dx > 0) {
|
||||
_remgr.invalidateRegion(width - _dx + _tx, _ty, _dx, height);
|
||||
} else if (_dx < 0) {
|
||||
_remgr.invalidateRegion(_tx, _ty, -_dx, height);
|
||||
}
|
||||
if (_dy > 0) {
|
||||
_remgr.invalidateRegion(_tx, height - _dy + _ty, width, _dy);
|
||||
} else if (_dy < 0) {
|
||||
_remgr.invalidateRegion(_tx, _ty, width, -_dy);
|
||||
}
|
||||
|
||||
// make sure we're actually scrolling before telling people
|
||||
// about it
|
||||
if (_dx != 0 || _dy != 0) {
|
||||
// if we are working with a sprite manager, let it know
|
||||
// that we're about to scroll out from under its sprites
|
||||
// and allow it to provide us with more dirty rects
|
||||
if (_spritemgr != null) {
|
||||
_spritemgr.viewWillScroll(_dx, _dy);
|
||||
}
|
||||
|
||||
// let our derived classes do whatever they need to do to
|
||||
// prepare to be scrolled
|
||||
viewWillScroll(_dx, _dy, tickStamp);
|
||||
|
||||
// keep track of the last time we scrolled
|
||||
_last = tickStamp;
|
||||
|
||||
// subtract our scrolled deltas from the distance remaining
|
||||
_scrollx -= _dx;
|
||||
_scrolly -= _dy;
|
||||
|
||||
// if we've reached our desired position, finish the job
|
||||
if (_scrollx == 0 && _scrolly == 0) {
|
||||
_ttime = 0;
|
||||
viewFinishedScrolling();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now tick our animations and sprites
|
||||
_animmgr.tick(tickStamp);
|
||||
_spritemgr.tick(tickStamp);
|
||||
|
||||
// make a note that the next paint will correspond to a call to
|
||||
// tick()
|
||||
_tickPaintPending = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* We want to be painted every tick.
|
||||
*/
|
||||
public Component getComponent ()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void repaint (long tm, int x, int y, int width, int height)
|
||||
{
|
||||
_remgr.invalidateRegion(x, y, width, height);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void paint (Graphics g)
|
||||
{
|
||||
Graphics2D gfx = (Graphics2D)g;
|
||||
|
||||
// no use in painting if we're not showing or if we've not yet
|
||||
// been validated
|
||||
if (!isValid() || !isShowing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if this isn't a tick paint, then we need to grab the clipping
|
||||
// rectangle and mark it as dirty
|
||||
if (!_tickPaintPending) {
|
||||
Shape clip = g.getClip();
|
||||
_remgr.invalidateRegion((clip == null) ?
|
||||
getBounds() : clip.getBounds());
|
||||
|
||||
} else {
|
||||
_tickPaintPending = false;
|
||||
}
|
||||
|
||||
// if we didn't scroll and have no invalid rects, there's no need
|
||||
// to repaint anything
|
||||
if (!_remgr.haveDirtyRegions() && _dx == 0 && _dy == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// we may need to do some scrolling
|
||||
if (_dx != 0 || _dy != 0) {
|
||||
gfx.copyArea(0, 0, getWidth(), getHeight(), -_dx, -_dy);
|
||||
}
|
||||
|
||||
// get our dirty rectangles
|
||||
Rectangle[] dirty = _remgr.getDirtyRegions();
|
||||
int dcount = dirty.length;
|
||||
|
||||
// translate into happy space
|
||||
gfx.translate(-_tx, -_ty);
|
||||
for (int ii = 0; ii < dcount; ii++) {
|
||||
dirty[ii].translate(_tx, _ty);
|
||||
}
|
||||
|
||||
// paint the behind the scenes stuff
|
||||
paintBehind(gfx, dirty);
|
||||
|
||||
// paint back sprites and animations
|
||||
for (int ii = 0; ii < dcount; ii++) {
|
||||
paintBits(gfx, AnimationManager.BACK, dirty[ii]);
|
||||
}
|
||||
|
||||
// paint the between the scenes stuff
|
||||
paintBetween(gfx, dirty);
|
||||
|
||||
// paint front sprites and animations
|
||||
for (int ii = 0; ii < dcount; ii++) {
|
||||
paintBits(gfx, AnimationManager.FRONT, dirty[ii]);
|
||||
}
|
||||
|
||||
// paint anything in front
|
||||
paintInFront(gfx, dirty);
|
||||
|
||||
// translate back out of happy space
|
||||
gfx.translate(_tx, _ty);
|
||||
}
|
||||
|
||||
/**
|
||||
* Paints behind all sprites and animations. The supplied list of
|
||||
* invalid rectangles should be redrawn in the supplied graphics
|
||||
* context. The rectangles will be in the view coordinate system
|
||||
* (which may differ from screen coordinates, see {@link
|
||||
* #getViewSize}. Sub-classes should override this method to do the
|
||||
* actual rendering for their display.
|
||||
*/
|
||||
protected void paintBehind (Graphics2D gfx, Rectangle[] dirtyRects)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Paints between the front and back layer of sprites and animations.
|
||||
* The supplied list of invalid rectangles should be redrawn in the
|
||||
* supplied graphics context. The rectangles will be in the view
|
||||
* coordinate system (which may differ from screen coordinates, see
|
||||
* {@link #getViewSize}. Sub-classes should override this method to do
|
||||
* the actual rendering for their display.
|
||||
*/
|
||||
protected void paintBetween (Graphics2D gfx, Rectangle[] dirtyRects)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Paints in front of all sprites and animations. The supplied list of
|
||||
* invalid rectangles should be redrawn in the supplied graphics
|
||||
* context. The rectangles will be in the view coordinate system
|
||||
* (which may differ from screen coordinates, see {@link
|
||||
* #getViewSize}. Sub-classes should override this method to do the
|
||||
* actual rendering for their display.
|
||||
*/
|
||||
protected void paintInFront (Graphics2D gfx, Rectangle[] dirtyRects)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the sprites and animations that intersect the supplied
|
||||
* clipping region in the specified layer. Derived classes can
|
||||
* override this method if they need to do custom sprite or animation
|
||||
* rendering (if they need to do special sprite z-order handling, for
|
||||
* example). This method also takes care of setting the clipping
|
||||
* region in the graphics object which an overridden method should
|
||||
* also do to preserve performance.
|
||||
*/
|
||||
protected void paintBits (Graphics2D gfx, int layer, Rectangle clip)
|
||||
{
|
||||
Shape oclip = gfx.getClip();
|
||||
gfx.setClip(clip);
|
||||
_animmgr.renderAnimations(gfx, layer, clip);
|
||||
_spritemgr.renderSprites(gfx, layer, clip);
|
||||
gfx.setClip(oclip);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called, when the view is scrolling, during the tick
|
||||
* processing phase. The animated panel will take care of scrolling
|
||||
* the contents of the offscreen buffer when the time comes to render,
|
||||
* but the derived class will need to do whatever is necessary to
|
||||
* prepare to repaint the exposed regions as well as update its own
|
||||
* internal state accordingly.
|
||||
*
|
||||
* @param dx the distance (in pixels) that the view will scroll in the
|
||||
* x direction.
|
||||
* @param dy the distance (in pixels) that the view will scroll in the
|
||||
* y direction.
|
||||
* @param now the current time, provided because we have it and
|
||||
* scrolling views are likely to want to use it in calculating stuff.
|
||||
*/
|
||||
protected void viewWillScroll (int dx, int dy, long tickStamp)
|
||||
{
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
/**
|
||||
* Called during the same frame that we scrolled into the final
|
||||
* desired position. This method is called after {@link
|
||||
* #viewWillScroll} is called with the final scrolling deltas.
|
||||
*/
|
||||
protected void viewFinishedScrolling ()
|
||||
{
|
||||
// Log.info("viewFinishedScrolling");
|
||||
}
|
||||
|
||||
/**
|
||||
* Derived classes that wish to operate in a coordinate system based
|
||||
* on a view size that is larger or smaller than the viewport size
|
||||
* (the actual dimensions of the animated panel) can override this
|
||||
* method and return the desired size of the view. The animated panel
|
||||
* will take this size into account and translate into the view
|
||||
* coordinate system before calling {@link #render}.
|
||||
*/
|
||||
protected Dimension getViewSize ()
|
||||
{
|
||||
return getSize();
|
||||
}
|
||||
|
||||
/** The frame manager with whom we register. */
|
||||
protected FrameManager _framemgr;
|
||||
|
||||
/** The animation manager in use by this panel. */
|
||||
protected AnimationManager _animmgr;
|
||||
|
||||
/** The sprite manager in use by this panel. */
|
||||
protected SpriteManager _spritemgr;
|
||||
|
||||
/** Used to accumulate and merge dirty regions on each tick. */
|
||||
protected RegionManager _remgr;
|
||||
|
||||
/** Our viewport offsets. */
|
||||
protected int _tx, _ty;
|
||||
|
||||
/** How many pixels we have left to scroll. */
|
||||
protected int _scrollx, _scrolly;
|
||||
|
||||
/** Our scroll offsets for this frame tick. */
|
||||
protected int _dx, _dy;
|
||||
|
||||
/** The time at which we expect to stop scrolling. */
|
||||
protected long _ttime;
|
||||
|
||||
/** The last time we were scrolled. */
|
||||
protected long _last;
|
||||
|
||||
/** Used to correlate tick()s with paint()s. */
|
||||
protected boolean _tickPaintPending = false;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
//
|
||||
// $Id: RegionManager.java,v 1.1 2002/04/23 01:16:27 mdb Exp $
|
||||
|
||||
package com.threerings.media;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Manages regions (rectangles) that are invalidated in the process of
|
||||
* ticking animations and sprites and generally doing other display
|
||||
* related business.
|
||||
*/
|
||||
public class RegionManager
|
||||
{
|
||||
/**
|
||||
* Invalidates the specified region.
|
||||
*/
|
||||
public void invalidateRegion (int x, int y, int width, int height)
|
||||
{
|
||||
_dirty.add(new Rectangle(x, y, width, height));
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the specified region (the supplied rectangle will be
|
||||
* cloned as the region manager fiddles with the rectangles it uses
|
||||
* internally).
|
||||
*/
|
||||
public void invalidateRegion (Rectangle rect)
|
||||
{
|
||||
_dirty.add((Rectangle)rect.clone());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the supplied rectangle to the dirty regions. Control of the
|
||||
* rectangle is given to the region manager as it may choose to bend,
|
||||
* fold or mutilate it later. If you don't want the region manager
|
||||
* messing with your rectangle, use {@link #invalidateRegion}.
|
||||
*/
|
||||
public void addDirtyRegion (Rectangle rect)
|
||||
{
|
||||
_dirty.add(rect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if dirty regions have been accumulated since the last
|
||||
* call to {@link #getDirtyRects}.
|
||||
*/
|
||||
public boolean haveDirtyRegions ()
|
||||
{
|
||||
return (_dirty.size() > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges all outstanding dirty regions into a single list of
|
||||
* rectangles and returns that to the caller. Interally, the list of
|
||||
* accumulated dirty regions is cleared out and prepared for the next
|
||||
* frame.
|
||||
*/
|
||||
public Rectangle[] getDirtyRegions ()
|
||||
{
|
||||
ArrayList merged = new ArrayList();
|
||||
|
||||
while (_dirty.size() > 0) {
|
||||
// pop the next rectangle from the dirty list
|
||||
Rectangle mr = (Rectangle)_dirty.remove(0);
|
||||
|
||||
// merge in any overlapping rectangles
|
||||
for (int ii = 0; ii < _dirty.size(); ii++) {
|
||||
Rectangle r = (Rectangle)_dirty.get(ii);
|
||||
if (mr.intersects(r)) {
|
||||
// remove the overlapping rectangle from the list
|
||||
_dirty.remove(ii--);
|
||||
// grow the merged dirty rectangle
|
||||
mr.add(r);
|
||||
}
|
||||
}
|
||||
|
||||
// add the merged rectangle to the list
|
||||
merged.add(mr);
|
||||
}
|
||||
|
||||
Rectangle[] rects = new Rectangle[merged.size()];
|
||||
merged.toArray(rects);
|
||||
return rects;
|
||||
}
|
||||
|
||||
/** A list of dirty rectangles. */
|
||||
protected ArrayList _dirty = new ArrayList();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// $Id: SafeScrollPane.java,v 1.1 2002/04/23 01:16:27 mdb Exp $
|
||||
|
||||
package com.threerings.media;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JViewport;
|
||||
|
||||
/**
|
||||
* A scroll pane that is safe to use in frame managed views.
|
||||
*/
|
||||
public class SafeScrollPane extends JScrollPane
|
||||
{
|
||||
public SafeScrollPane (Component view)
|
||||
{
|
||||
super(view);
|
||||
}
|
||||
|
||||
protected JViewport createViewport ()
|
||||
{
|
||||
JViewport vp = new JViewport();
|
||||
vp.setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
|
||||
return vp;
|
||||
}
|
||||
}
|
||||
@@ -1,506 +0,0 @@
|
||||
//
|
||||
// $Id: AnimatedPanel.java,v 1.21 2002/04/18 22:38:24 mdb Exp $
|
||||
|
||||
package com.threerings.media.animation;
|
||||
|
||||
import java.awt.AWTException;
|
||||
import java.awt.BufferCapabilities;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.GraphicsConfiguration;
|
||||
import java.awt.ImageCapabilities;
|
||||
import java.awt.Rectangle;
|
||||
|
||||
import java.awt.image.BufferStrategy;
|
||||
import java.awt.image.VolatileImage;
|
||||
|
||||
import javax.swing.event.AncestorEvent;
|
||||
import javax.swing.JComponent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.samskivert.swing.event.AncestorAdapter;
|
||||
import com.samskivert.util.Histogram;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.media.Log;
|
||||
import com.threerings.media.sprite.SpriteManager;
|
||||
|
||||
/**
|
||||
* The animated panel provides a useful extensible implementation of a
|
||||
* {@link JComponent} that implements the {@link AnimatedView} interface.
|
||||
* It takes care of automatically creating an animation manager and a
|
||||
* sprite manager to manage the animations that take place within this
|
||||
* panel. If a sprite manager is not needed, {@link #needsSpriteManager}
|
||||
* can be overridden to prevent it from being created.
|
||||
*
|
||||
* <p> Users of the animated panel must be sure to call {@link #stop} when
|
||||
* their panel is no longer being displayed and {@link #start} to start it
|
||||
* back up again when it is once again being displayed. These methods
|
||||
* deactivate and reactivate the underlying animation manager.
|
||||
*
|
||||
* <p> Sub-classes should override {@link #render} to draw their
|
||||
* panel-specific contents.
|
||||
*/
|
||||
public class AnimatedPanel extends JComponent
|
||||
implements AnimatedView
|
||||
{
|
||||
/**
|
||||
* Constructs an animated panel.
|
||||
*/
|
||||
public AnimatedPanel ()
|
||||
{
|
||||
// create our animation manager
|
||||
_animmgr = new AnimationManager(this);
|
||||
|
||||
// create a sprite manager if we haven't been requested not to
|
||||
if (needsSpriteManager()) {
|
||||
_spritemgr = new SpriteManager();
|
||||
_animmgr.setSpriteManager(_spritemgr);
|
||||
}
|
||||
|
||||
// turn off double buffering because we handle our own rendering;
|
||||
// it's not enough to turn it off for this component, we need to
|
||||
// climb all the way up the chain, otherwise one of our parents
|
||||
// might be painting and decide to do its own double buffering
|
||||
addAncestorListener(new AncestorAdapter() {
|
||||
public void ancestorAdded (AncestorEvent event) {
|
||||
Component c = AnimatedPanel.this;
|
||||
while (c != null) {
|
||||
if (c instanceof JComponent) {
|
||||
((JComponent)c).setDoubleBuffered(false);
|
||||
}
|
||||
c = c.getParent();
|
||||
}
|
||||
setOpaque(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Derived classes can override this method to prevent the automatic
|
||||
* creation of a sprite manager to work with the animation manager
|
||||
* that is managing this panel.
|
||||
*/
|
||||
protected boolean needsSpriteManager ()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts up the animation manager associated with this panel.
|
||||
*/
|
||||
public void start ()
|
||||
{
|
||||
_animmgr.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down the animation manager associated with this panel.
|
||||
*/
|
||||
public void stop ()
|
||||
{
|
||||
_animmgr.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the view to scroll by the specified number of pixels
|
||||
* (which can be negative if it should scroll in the negative x or y
|
||||
* direction) in the specified number of milliseconds. While the view
|
||||
* is scrolling, derived classes can hear about the scrolled
|
||||
* increments by overriding {@link #viewWillScroll} and they can find
|
||||
* out when scrolling is complete by overriding {@link
|
||||
* #viewFinishedScrolling}.
|
||||
*
|
||||
* @param dx the number of pixels in the x direction to scroll.
|
||||
* @param dy the number of pixels in the y direction to scroll.
|
||||
* @param millis the number of milliseconds in which to do it. The
|
||||
* scrolling is calculated such that we will "drop frames" in order to
|
||||
* scroll the necessary distance by the requested time.
|
||||
*/
|
||||
public void setScrolling (int dx, int dy, long millis)
|
||||
{
|
||||
// if dx and dy are zero, we've got nothing to do
|
||||
if (dx == 0 && dy == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// set our scrolling parameters
|
||||
_scrollx = dx;
|
||||
_scrolly = dy;
|
||||
_last = System.currentTimeMillis();
|
||||
_ttime = _last + millis;
|
||||
|
||||
// figure out the lesser (but non-zero) of the two scroll deltas
|
||||
int absx = Math.abs(dx), absy = Math.abs(dy);
|
||||
int mindist = absx;
|
||||
if (absx == 0) {
|
||||
mindist = absy;
|
||||
} else if (absy == 0) {
|
||||
mindist = absx;
|
||||
} else {
|
||||
mindist = Math.min(absx, absy);
|
||||
}
|
||||
|
||||
// let the animation manager know how "fast" we'll be scrolling so
|
||||
// that it can determine its frame rate
|
||||
int mspp = (int)(millis/mindist);
|
||||
_animmgr.setScrolling(mspp);
|
||||
|
||||
// Log.info("Scrolling [dx=" + dx + ", dy=" + dy +
|
||||
// ", millis=" + millis + "ms, mspp=" + mspp + "].");
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void paint (Graphics g)
|
||||
{
|
||||
// convert the clipping rectangle into the proper coordinates and
|
||||
// call into our rendering system
|
||||
Rectangle r = g.getClipBounds();
|
||||
r.translate(_tx, _ty);
|
||||
_paintList.add(r);
|
||||
// we have to tell paintImmediately() to use the graphics object
|
||||
// that we were passed, otherwise Swing will fuck everything to
|
||||
// the high heavens
|
||||
paintImmediately(g, _paintList);
|
||||
_paintList.clear();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void paintImmediately (int x, int y, int w, int h)
|
||||
{
|
||||
paintImmediately(new Rectangle(x, y, w, h));
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void paintImmediately (Rectangle r)
|
||||
{
|
||||
// convert the clipping rectangle into the proper coordinates and
|
||||
// call into our rendering system
|
||||
r.translate(_tx, _ty);
|
||||
_paintList.add(r);
|
||||
paintImmediately(_paintList);
|
||||
_paintList.clear();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void doLayout ()
|
||||
{
|
||||
super.doLayout();
|
||||
|
||||
// if we change size, clear out our old back buffer
|
||||
_backimg = null;
|
||||
|
||||
// figure out our viewport offsets
|
||||
Dimension size = getSize(), vsize = getViewSize();
|
||||
_tx = (vsize.width - size.width)/2;
|
||||
_ty = (vsize.height - size.height)/2;
|
||||
|
||||
// Log.info("Size: " + size + ", vsize: " + vsize +
|
||||
// ", tx: " + _ty + ", ty: " + _ty + ".");
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void invalidateRect (Rectangle invalidRect)
|
||||
{
|
||||
// pass it on to the animation manager
|
||||
_animmgr.addDirtyRect(invalidRect);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void paintImmediately (List invalidRects)
|
||||
{
|
||||
paintImmediately(null, invalidRects);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders this animated panel. If the supplied graphics reference is
|
||||
* non-null, we will render to that instance, otherwise a graphics
|
||||
* object will be obtained via a call to {@link #getGraphics}. This
|
||||
* method, of course, must be called from the AWT thread or mayhem
|
||||
* will ensue.
|
||||
*/
|
||||
protected void paintImmediately (Graphics gfx, List invalidRects)
|
||||
{
|
||||
// no use in painting if we're not showing or if we've not yet
|
||||
// been validated
|
||||
if (!isValid() || !isShowing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// track how long it was since we were last painted
|
||||
long now = System.currentTimeMillis();
|
||||
// if (_paint != 0) {
|
||||
// int delta = (int)(now-_paint);
|
||||
// _histo.addValue(delta);
|
||||
|
||||
// // dump the histogram every ten seconds
|
||||
// if (_paint % (10*1000) < 50) {
|
||||
// Log.info("Render histogram.");
|
||||
// Log.info(StringUtil.toMatrixString(_histo.getBuckets(), 10, 3));
|
||||
// }
|
||||
// }
|
||||
|
||||
int width = getWidth(), height = getHeight();
|
||||
|
||||
// if scrolling is enabled, determine the scrolling delta to be
|
||||
// used and do the business
|
||||
int dx = 0, dy = 0;
|
||||
if (_ttime != 0) {
|
||||
// if we've blown past our allotted time, we want to scroll
|
||||
// the rest of the way
|
||||
if (now > _ttime) {
|
||||
dx = _scrollx;
|
||||
dy = _scrolly;
|
||||
|
||||
// Log.info("Scrolling rest [dx=" + dx + ", dy=" + dy + "].");
|
||||
|
||||
} else {
|
||||
// otherwise figure out how many milliseconds have gone by
|
||||
// since we last scrolled and scroll the requisite amount
|
||||
float dt = (float)(now - _last);
|
||||
float rt = (float)(_ttime - _last);
|
||||
|
||||
// our delta is the remaining distance multiplied by the
|
||||
// time delta divided by the remaining time
|
||||
dx = Math.round((float)(_scrollx * dt) / rt);
|
||||
dy = Math.round((float)(_scrolly * dt) / rt);
|
||||
|
||||
// Log.info("Scrolling delta [dt=" + dt + ", rt=" + rt +
|
||||
// ", dx=" + dx + ", dy=" + dy + "].");
|
||||
}
|
||||
|
||||
// and add invalid rectangles for the exposed areas
|
||||
if (dx > 0) {
|
||||
Rectangle dirty = new Rectangle(width - dx, 0, dx, height);
|
||||
dirty.translate(_tx, _ty);
|
||||
invalidRects.add(dirty);
|
||||
} else if (dx < 0) {
|
||||
Rectangle dirty = new Rectangle(0, 0, -dx, height);
|
||||
dirty.translate(_tx, _ty);
|
||||
invalidRects.add(dirty);
|
||||
}
|
||||
if (dy > 0) {
|
||||
Rectangle dirty = new Rectangle(0, height - dy, width, dy);
|
||||
dirty.translate(_tx, _ty);
|
||||
invalidRects.add(dirty);
|
||||
} else if (dy < 0) {
|
||||
Rectangle dirty = new Rectangle(0, 0, width, -dy);
|
||||
dirty.translate(_tx, _ty);
|
||||
invalidRects.add(dirty);
|
||||
}
|
||||
|
||||
// make sure we're actually scrolling before telling people
|
||||
// about it
|
||||
if (dx != 0 || dy != 0) {
|
||||
// if we are working with a sprite manager, let it know
|
||||
// that we're about to scroll out from under its sprites
|
||||
// and allow it to provide us with more dirty rects
|
||||
if (_spritemgr != null) {
|
||||
_spritemgr.viewWillScroll(dx, dy, invalidRects);
|
||||
}
|
||||
|
||||
// let our derived classes do whatever they need to do to
|
||||
// prepare to be scrolled
|
||||
viewWillScroll(dx, dy, now, invalidRects);
|
||||
|
||||
// keep track of the last time we scrolled
|
||||
_last = now;
|
||||
|
||||
// subtract our scrolled deltas from the distance remaining
|
||||
_scrollx -= dx;
|
||||
_scrolly -= dy;
|
||||
|
||||
// if we've reached our desired position, finish the job
|
||||
if (_scrollx == 0 && _scrolly == 0) {
|
||||
_ttime = 0;
|
||||
viewFinishedScrolling();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we didn't scroll and have no invalid rects, there's no need
|
||||
// to repaint anything
|
||||
if (invalidRects.size() == 0 && dx == 0 && dy == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// create our off-screen buffer if necessary
|
||||
GraphicsConfiguration gc = getGraphicsConfiguration();
|
||||
if (_backimg == null) {
|
||||
createBackBuffer(gc);
|
||||
}
|
||||
|
||||
// render into our back buffer
|
||||
do {
|
||||
// make sure our back buffer hasn't disappeared
|
||||
int valres = _backimg.validate(gc);
|
||||
|
||||
// if we've changed resolutions, recreate the buffer
|
||||
if (valres == VolatileImage.IMAGE_INCOMPATIBLE) {
|
||||
Log.info("Back buffer incompatible, recreating.");
|
||||
createBackBuffer(gc);
|
||||
}
|
||||
|
||||
Graphics g = null;
|
||||
try {
|
||||
g = _backimg.getGraphics();
|
||||
|
||||
// if the image wasn't A-OK, we need to rerender the whole
|
||||
// business rather than just the dirty parts
|
||||
if (valres != VolatileImage.IMAGE_OK) {
|
||||
invalidRects.clear();
|
||||
invalidRects.add(new Rectangle(_tx, _ty, width, height));
|
||||
Log.info("Lost back buffer, redrawing.");
|
||||
|
||||
} else if (dx != 0 || dy != 0) {
|
||||
// if it was OK, we may need to do some scrolling
|
||||
g.copyArea(0, 0, width, height, -dx, -dy);
|
||||
}
|
||||
|
||||
// translate into happy space
|
||||
g.translate(-_tx, -_ty);
|
||||
|
||||
// now do our actual rendering
|
||||
render((Graphics2D)g, invalidRects);
|
||||
|
||||
// translate back out of happy space
|
||||
g.translate(_tx, _ty);
|
||||
|
||||
} finally {
|
||||
g.dispose();
|
||||
}
|
||||
|
||||
// draw the back buffer to the screen
|
||||
try {
|
||||
if (gfx == null) {
|
||||
g = getGraphics();
|
||||
} else {
|
||||
g = gfx;
|
||||
}
|
||||
|
||||
// if we're scrolling, we've got to copy the whole image
|
||||
// to the screen, otherwise we can just copy the dirty
|
||||
// regions
|
||||
if (dx != 0 || dy != 0) {
|
||||
g.drawImage(_backimg, 0, 0, null);
|
||||
|
||||
} else {
|
||||
// iterate through the invalid rectangles, copying
|
||||
// those areas from the back buffer to the display
|
||||
int isize = invalidRects.size();
|
||||
for (int i = 0; i < isize; i++) {
|
||||
Rectangle rect = (Rectangle)invalidRects.get(i);
|
||||
// we have to translate out of view coordinates
|
||||
// before doing the actual copy
|
||||
rect.translate(-_tx, -_ty);
|
||||
g.setClip(rect);
|
||||
g.drawImage(_backimg, 0, 0, null);
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
if (g != null) {
|
||||
g.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
} while (_backimg.contentsLost());
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called, when the view is scrolling, just before a call to
|
||||
* {@link #render}. The animated panel will take care of scrolling the
|
||||
* contents of the offscreen buffer, but the derived class will need
|
||||
* to do whatever is necessary to prepare to repaint the exposed
|
||||
* regions as well as update it's own internal state accordingly.
|
||||
*
|
||||
* @param dx the distance (in pixels) that the view will scroll in the
|
||||
* x direction.
|
||||
* @param dy the distance (in pixels) that the view will scroll in the
|
||||
* y direction.
|
||||
* @param now the current time, provided because we have it and
|
||||
* scrolling views are likely to want to use it in calculating stuff.
|
||||
* @param invalidRects the list of invalid rectangles which will be
|
||||
* redrawn; rectangles can be added to this list if necessary.
|
||||
*/
|
||||
protected void viewWillScroll (int dx, int dy, long now, List invalidRects)
|
||||
{
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
/**
|
||||
* Called during the same frame that we scrolled into the final
|
||||
* desired position. This method is called after {@link
|
||||
* #viewWillScroll} is called with the final scrolling deltas.
|
||||
*/
|
||||
protected void viewFinishedScrolling ()
|
||||
{
|
||||
// Log.info("viewFinishedScrolling");
|
||||
}
|
||||
|
||||
/**
|
||||
* Derived classes that wish to operate in a coordinate system based
|
||||
* on a view size that is larger or smaller than the viewport size
|
||||
* (the actual dimensions of the animated panel) can override this
|
||||
* method and return the desired size of the view. The animated panel
|
||||
* will take this size into account and translate into the view
|
||||
* coordinate system before calling {@link #render}.
|
||||
*/
|
||||
protected Dimension getViewSize ()
|
||||
{
|
||||
return getSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that the supplied list of invalid rectangles be redrawn in
|
||||
* the supplied graphics context. The rectangles will be in the view
|
||||
* coordinate system (which may differ from screen coordinates, see
|
||||
* {@link #getViewSize}. Sub-classes should override this method to do
|
||||
* the actual rendering for their display.
|
||||
*/
|
||||
protected void render (Graphics2D gfx, List invalidRects)
|
||||
{
|
||||
// nothing for now
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the off-screen buffer used to perform double buffered
|
||||
* rendering of the animated panel.
|
||||
*/
|
||||
protected void createBackBuffer (GraphicsConfiguration gc)
|
||||
{
|
||||
_backimg = gc.createCompatibleVolatileImage(getWidth(), getHeight());
|
||||
}
|
||||
|
||||
/** The animation manager we use in this panel. */
|
||||
protected AnimationManager _animmgr;
|
||||
|
||||
/** The sprite manager in use by this panel. */
|
||||
protected SpriteManager _spritemgr;
|
||||
|
||||
/** The image used to render off-screen. */
|
||||
protected VolatileImage _backimg;
|
||||
|
||||
/** Our viewport offsets. */
|
||||
protected int _tx, _ty;
|
||||
|
||||
/** The time at which we expect to stop scrolling. */
|
||||
protected long _ttime;
|
||||
|
||||
/** Used to determine how many pixels we have left to scroll. */
|
||||
protected int _scrollx, _scrolly;
|
||||
|
||||
/** The last time we were rendered. */
|
||||
protected long _last;
|
||||
|
||||
/** A histogram for tracking how frequently we're rendered. */
|
||||
protected Histogram _histo = new Histogram(0, 1, 100);
|
||||
|
||||
/** Used to pass dirty regions supplied by the AWT to the rendering
|
||||
* system. */
|
||||
protected ArrayList _paintList = new ArrayList();
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
//
|
||||
// $Id: AnimatedView.java,v 1.4 2002/02/21 06:01:29 mdb Exp $
|
||||
|
||||
package com.threerings.media.animation;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A view that wishes to interact with the animation manager needs to
|
||||
* implement this interface to give the animation manager a means by which
|
||||
* to communicate the regions of the view that need to be repainted
|
||||
* because of the process of animating on top of the view.
|
||||
*/
|
||||
public interface AnimatedView
|
||||
{
|
||||
/**
|
||||
* Requests that the specified rectangle (in view coordinates, which
|
||||
* need not account for scrolling offsets or viewport offsets) be
|
||||
* rendered invalid. The animated view should massage the location of
|
||||
* the invalid rectangle and pass it on to the animation manager.
|
||||
*/
|
||||
public void invalidateRect (Rectangle invalidRect);
|
||||
|
||||
/**
|
||||
* Requests that the animated view paint itself immediately (that it
|
||||
* complete the painting process before returning from this function).
|
||||
* This will only be called on the AWT thread and when it is safe to
|
||||
* paint.
|
||||
*
|
||||
* @param invalidRects the list of rectangles that have been
|
||||
* invalidated since the last call to this method.
|
||||
*/
|
||||
public void paintImmediately (List invalidRects);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// $Id: Animation.java,v 1.4 2002/03/16 03:11:23 shaper Exp $
|
||||
// $Id: Animation.java,v 1.5 2002/04/23 01:16:28 mdb Exp $
|
||||
|
||||
package com.threerings.media.animation;
|
||||
|
||||
@@ -8,12 +8,14 @@ import java.awt.Rectangle;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.media.Log;
|
||||
|
||||
/**
|
||||
* The animation class is an abstract class that should be extended to
|
||||
* provide animation functionality for use with the {@link
|
||||
* AnimationManager} and an {@link AnimatedView}.
|
||||
* provide animation functionality. It is generally used in conjunction
|
||||
* with an {@link AnimationManager}.
|
||||
*/
|
||||
public abstract class Animation
|
||||
{
|
||||
@@ -47,43 +49,49 @@ public abstract class Animation
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the {@link AnimationManager} to allow the animation to
|
||||
* render itself to the given graphics context.
|
||||
* Returns a rectangle containing all pixels rendered by this
|
||||
* animation.
|
||||
*/
|
||||
public abstract void paint (Graphics2D gfx);
|
||||
public Rectangle getBounds ()
|
||||
{
|
||||
return _bounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called periodically by the {@link AnimationManager} to give the
|
||||
* animation a chance to do its thing.
|
||||
*
|
||||
* @param tickStamp the system time for this tick.
|
||||
*/
|
||||
public abstract void tick (long timestamp);
|
||||
public abstract void tick (long tickStamp);
|
||||
|
||||
/**
|
||||
* Invalidates the animation's bounds for later re-rendering by the
|
||||
* {@link AnimationManager}.
|
||||
* Called by the {@link AnimationManager} to request that the
|
||||
* animation render itself with the given graphics context. The
|
||||
* animation may wish to inspect the clipping region that has been set
|
||||
* on the graphics context to render itself more efficiently. This
|
||||
* method will only be called after it has been established that this
|
||||
* animation's bounds intersect the clipping region.
|
||||
*/
|
||||
public void invalidate ()
|
||||
{
|
||||
_animmgr.addDirtyRect(new Rectangle(_bounds));
|
||||
}
|
||||
public abstract void paint (Graphics2D gfx);
|
||||
|
||||
/**
|
||||
* Invalidates the specified rectangle for later re-rendering by the
|
||||
* {@link AnimationManager}.
|
||||
*/
|
||||
public void invalidate (int x, int y, int width, int height)
|
||||
{
|
||||
_animmgr.addDirtyRect(new Rectangle(x, y, width, height));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the animation has finished all of its business.
|
||||
* Returns true if the animation has finished all of its business,
|
||||
* false if not.
|
||||
*/
|
||||
public boolean isFinished ()
|
||||
{
|
||||
return _finished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the bounds of this animation.
|
||||
*/
|
||||
public void invalidate ()
|
||||
{
|
||||
_animmgr.getRegionManager().invalidateRegion(_bounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the animation is finished and the animation manager has
|
||||
* removed it from service.
|
||||
@@ -115,8 +123,8 @@ public abstract class Animation
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies any animation observers that the given animation is
|
||||
* finished.
|
||||
* Notifies any animation observers that the given animation event has
|
||||
* occurred.
|
||||
*/
|
||||
public void notifyObservers (AnimationEvent e)
|
||||
{
|
||||
@@ -137,6 +145,15 @@ public abstract class Animation
|
||||
return buf.append("]").toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called automatically when an animation is added to an animation
|
||||
* manager for management.
|
||||
*/
|
||||
protected void setAnimationManager (AnimationManager animmgr)
|
||||
{
|
||||
_animmgr = animmgr;
|
||||
}
|
||||
|
||||
/**
|
||||
* This should be overridden by derived classes (which should be sure
|
||||
* to call <code>super.toString()</code>) to append the derived class
|
||||
@@ -144,20 +161,11 @@ public abstract class Animation
|
||||
*/
|
||||
protected void toString (StringBuffer buf)
|
||||
{
|
||||
buf.append("bounds=").append(_bounds);
|
||||
buf.append("bounds=").append(StringUtil.toString(_bounds));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the animation manager when an animation is added to said
|
||||
* manager for management.
|
||||
*
|
||||
* @param animmgr the animation manager.
|
||||
* @param view the animated view.
|
||||
*/
|
||||
protected void setAnimationManager (AnimationManager animmgr)
|
||||
{
|
||||
_animmgr = animmgr;
|
||||
}
|
||||
/** Our animation manager. */
|
||||
protected AnimationManager _animmgr;
|
||||
|
||||
/** Whether the animation is finished. */
|
||||
protected boolean _finished = false;
|
||||
@@ -170,7 +178,4 @@ public abstract class Animation
|
||||
|
||||
/** The list of animation observers. */
|
||||
protected ArrayList _observers;
|
||||
|
||||
/** The animation manager. */
|
||||
protected AnimationManager _animmgr;
|
||||
}
|
||||
|
||||
@@ -1,127 +1,32 @@
|
||||
//
|
||||
// $Id: AnimationManager.java,v 1.8 2002/03/16 03:11:23 shaper Exp $
|
||||
// $Id: AnimationManager.java,v 1.9 2002/04/23 01:16:28 mdb Exp $
|
||||
|
||||
package com.threerings.media.animation;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Shape;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.event.AncestorEvent;
|
||||
|
||||
import com.samskivert.swing.event.AncestorAdapter;
|
||||
import com.samskivert.util.Interval;
|
||||
import com.samskivert.util.IntervalManager;
|
||||
|
||||
import com.threerings.media.Log;
|
||||
import com.threerings.media.sprite.SpriteManager;
|
||||
import com.threerings.media.util.PerformanceMonitor;
|
||||
import com.threerings.media.util.PerformanceObserver;
|
||||
import com.threerings.media.MediaConstants;
|
||||
import com.threerings.media.RegionManager;
|
||||
|
||||
/**
|
||||
* The animation manager handles the regular refreshing of the scene view
|
||||
* to allow for animation. It also may someday manage special scene-wide
|
||||
* animations, such as rain, fog, or earthquakes.
|
||||
* Manages a collection of animations, ticking them when the animation
|
||||
* manager itself is ticked and generating events when animations finish
|
||||
* and suchlike.
|
||||
*/
|
||||
public class AnimationManager
|
||||
implements Interval, PerformanceObserver
|
||||
implements MediaConstants
|
||||
{
|
||||
/** Constant for the front layer of animations. */
|
||||
public static final int FRONT = 0;
|
||||
|
||||
/** Constant for the back layer of animations. */
|
||||
public static final int BACK = 1;
|
||||
|
||||
/** Constant for all layers of animations. */
|
||||
public static final int ALL = 2;
|
||||
|
||||
/**
|
||||
* Construct and initialize the animation manager with a sprite
|
||||
* manager and the view in which the animations will take place. The
|
||||
* animation manager will automatically start itself up, but must be
|
||||
* explicitly shutdown when the animated view is no longer in
|
||||
* operation via a call to {@link #stop}.
|
||||
* Construct and initialize the animation manager which readies itself
|
||||
* to manage animations.
|
||||
*/
|
||||
public AnimationManager (SpriteManager spritemgr, AnimatedView view)
|
||||
public AnimationManager (RegionManager remgr)
|
||||
{
|
||||
// save off references to the objects we care about
|
||||
_spritemgr = spritemgr;
|
||||
_view = view;
|
||||
|
||||
// register to monitor the refresh action
|
||||
PerformanceMonitor.register(this, "refresh", 1000);
|
||||
|
||||
// start ourselves up
|
||||
start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs and initializes an animation manager. If sprites are to
|
||||
* be used with this animation manager, the other constructor should
|
||||
* be used or the sprite manager should be set shortly after
|
||||
* construction via {@link #setSpriteManager}. The animation manager
|
||||
* will automatically start itself up, but must be explicitly shutdown
|
||||
* when the animated view is no longer in operation via a call to
|
||||
* {@link #stop}.
|
||||
*/
|
||||
public AnimationManager (AnimatedView view)
|
||||
{
|
||||
this(null, view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sprite manager with which this animation manager should
|
||||
* coordinate.
|
||||
*/
|
||||
public void setSpriteManager (SpriteManager spritemgr)
|
||||
{
|
||||
_spritemgr = spritemgr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the animation manager to doing its business.
|
||||
*/
|
||||
public synchronized void start ()
|
||||
{
|
||||
if (_ticker == null) {
|
||||
// create ticker for queueing up tick requests on AWT thread
|
||||
_ticker = new Runnable() {
|
||||
public void run () {
|
||||
tick();
|
||||
}
|
||||
};
|
||||
// register the refresh interval
|
||||
_iid = IntervalManager.register(this, _refreshInterval, null, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the animation manager to stop doing its business.
|
||||
*/
|
||||
public synchronized void stop ()
|
||||
{
|
||||
if (_ticker != null) {
|
||||
_ticker = null;
|
||||
// un-register the refresh interval since we're now hidden
|
||||
IntervalManager.remove(_iid);
|
||||
_iid = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a rectangle to the dirty rectangle list. Note that the
|
||||
* rectangle may be destructively modified by the animation manager at
|
||||
* some later date.
|
||||
*
|
||||
* @param rect the rectangle to add.
|
||||
*/
|
||||
public void addDirtyRect (Rectangle rect)
|
||||
{
|
||||
_dirty.add(rect);
|
||||
_remgr = remgr;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,12 +43,13 @@ public class AnimationManager
|
||||
|
||||
anim.setAnimationManager(this);
|
||||
_anims.add(anim);
|
||||
// Log.info("Registered animation [anim=" + anim + "].");
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-registers the given {@link Animation} from the animation
|
||||
* manager.
|
||||
* manager. The bounds of the animation will automatically be
|
||||
* invalidated so that they are properly rerendered in the absence of
|
||||
* the animation.
|
||||
*/
|
||||
public void unregisterAnimation (Animation anim)
|
||||
{
|
||||
@@ -151,211 +57,37 @@ public class AnimationManager
|
||||
if (!_anims.remove(anim)) {
|
||||
Log.warning("Attempt to un-register animation that isn't " +
|
||||
"registered [anim=" + anim + "].");
|
||||
return;
|
||||
}
|
||||
|
||||
// dirty the animation bounds
|
||||
anim.invalidate();
|
||||
// Log.info("Un-registered animation [anim=" + anim + "].");
|
||||
// invalidate its bounds
|
||||
_remgr.invalidateRegion(anim.getBounds());
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders all registered animations in the given layer to the given
|
||||
* graphics context.
|
||||
*
|
||||
* @param layer the layer to render; one of {@link #FRONT}, {@link
|
||||
* #BACK}, or {@link #ALL}. The front layer contains all animations
|
||||
* with a positive render order; the back layer contains all
|
||||
* animations with a negative render order; all, both.
|
||||
* Provides access to the region manager that the animation manager is
|
||||
* using to collect invalid regions every frame. This should generally
|
||||
* only be used by animations that want to invalidate themselves.
|
||||
*/
|
||||
public void renderAnimations (Graphics2D gfx, int layer)
|
||||
public RegionManager getRegionManager ()
|
||||
{
|
||||
return _remgr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles updating animations and generating associated events.
|
||||
*
|
||||
* @param tickStamp the system clock at the time of the tick.
|
||||
*/
|
||||
public void tick (long tickStamp)
|
||||
{
|
||||
// tick all of our animations
|
||||
int size = _anims.size();
|
||||
for (int ii = 0; ii < size; ii++) {
|
||||
Animation anim = (Animation)_anims.get(ii);
|
||||
int order = anim.getRenderOrder();
|
||||
if ((layer == ALL) ||
|
||||
(layer == FRONT && order >= 0) ||
|
||||
(layer == BACK && order < 0)) {
|
||||
anim.paint(gfx);
|
||||
}
|
||||
((Animation)_anims.get(ii)).tick(tickStamp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets the animation manager know that the animated view is scrolling
|
||||
* at the specified rate (in milliseconds per pixel) so that it can
|
||||
* adjust its rendering loop interval to coincide with the scrolling
|
||||
* speed, and cause the animated view to be rendered every time
|
||||
* through the loop regardless of whether it has dirty regions (so
|
||||
* that it can scroll).
|
||||
*/
|
||||
public void setScrolling (int mspp)
|
||||
{
|
||||
// sanity check
|
||||
if (mspp < 0) {
|
||||
String errmsg ="Negative scroll velocity illegal " +
|
||||
"[mspp=" + mspp + "]";
|
||||
throw new IllegalArgumentException(errmsg);
|
||||
}
|
||||
|
||||
// make a note of our scrolling velocity
|
||||
_scrollvel = mspp;
|
||||
|
||||
// we want to adjust our refresh interval at which we tick to
|
||||
// coincide with an even number of scrolled pixels
|
||||
long upperTarget = 1000 / MIN_FRAME_RATE;
|
||||
long lowerTarget = 1000 / MAX_FRAME_RATE;
|
||||
|
||||
// start out assuming that we can refresh for every pixel
|
||||
_refreshInterval = _scrollvel;
|
||||
|
||||
// if they've disabled scrolling, go back to the default refresh
|
||||
// interval
|
||||
if (_scrollvel == 0) {
|
||||
_refreshInterval = DEFAULT_REFRESH_INTERVAL;
|
||||
|
||||
// if the interval is too quick, bump it up a bit
|
||||
} else if (_refreshInterval < lowerTarget) {
|
||||
// keep adding a pixel at a time until we're above our minimum
|
||||
// refresh interval
|
||||
while (_refreshInterval < lowerTarget) {
|
||||
_refreshInterval += mspp;
|
||||
}
|
||||
|
||||
// if it's too slow, we'll want to refresh at some even
|
||||
// division of the desired rate
|
||||
} else if (_refreshInterval > upperTarget) {
|
||||
// try dividing the desired rate by larger and larger values
|
||||
// until we're under the upper target
|
||||
for (int i = 2; i < 100 && _refreshInterval > upperTarget; i++) {
|
||||
_refreshInterval = (mspp / i);
|
||||
}
|
||||
|
||||
// if the desired velocity is more than one hundred times
|
||||
// slower than our desired framerate, then they're not going
|
||||
// to notice if things aren't perfectly in sync, so fuck 'em
|
||||
if (_refreshInterval > upperTarget) {
|
||||
_refreshInterval = DEFAULT_REFRESH_INTERVAL;
|
||||
}
|
||||
}
|
||||
|
||||
// Log.info("Set scrolling velocity [velocity=" + _scrollvel +
|
||||
// ", refresh=" + _refreshInterval + "].");
|
||||
|
||||
// now stop and start ourselves to reregister our interval
|
||||
stop();
|
||||
start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by our interval when we'd like to begin a tick. Returns
|
||||
* whether we're already ticking, and notes that we've requested
|
||||
* another tick.
|
||||
*/
|
||||
protected synchronized boolean requestTick ()
|
||||
{
|
||||
return !(_ticking++ > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the tick task when it's finished with a tick.
|
||||
* Returns whether a new tick task should be created immediately.
|
||||
*/
|
||||
protected synchronized boolean finishedTick ()
|
||||
{
|
||||
if (--_ticking > 0) {
|
||||
_ticking = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The <code>IntervalManager</code> calls this method as often as
|
||||
* we've requested to obtain our desired frame rate. Since we'd
|
||||
* like to avoid messy thread-synchronization between the AWT
|
||||
* thread and other threads, here we just add the tick task to the
|
||||
* AWT thread for later execution.
|
||||
*/
|
||||
public void intervalExpired (int id, Object arg)
|
||||
{
|
||||
if (requestTick()) {
|
||||
// throw the tick task on the AWT thread task queue
|
||||
queueTick();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The <code>tick</code> method handles updating sprites and
|
||||
* animations, and repainting the target display.
|
||||
*/
|
||||
protected void tick ()
|
||||
{
|
||||
synchronized (this) {
|
||||
// see if we were shutdown since we were last queued up
|
||||
if (_ticker == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// every tick should have a timestamp associated with it
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
// call tick on all sprites
|
||||
if (_spritemgr != null) {
|
||||
_spritemgr.tick(now, _dirty);
|
||||
}
|
||||
|
||||
// call tick on all animations
|
||||
tickAnimations(now);
|
||||
|
||||
// perform a single pass merging overlapping rectangles. note
|
||||
// that this will also clear out the contents of our internal
|
||||
// dirty rectangle list.
|
||||
List rects = mergeDirtyRects(_dirty);
|
||||
|
||||
// invalidate screen-rects dirtied by sprites and/or animations
|
||||
if (rects.size() > 0 || _scrollvel > 0) {
|
||||
// pass the dirty-rects on to the animated view and repaint
|
||||
_view.paintImmediately(rects);
|
||||
}
|
||||
|
||||
// remove any finished animations
|
||||
removeFinishedAnimations();
|
||||
|
||||
// update refresh-rate information
|
||||
// PerformanceMonitor.tick(AnimationManager.this, "refresh");
|
||||
|
||||
if (finishedTick()) {
|
||||
// finishedTick returning true means there's been a
|
||||
// request for at least one more tick since we started
|
||||
// this tick, so we want to queue up another tick
|
||||
// immediately
|
||||
// Log.info("Queueing immediate tick.");
|
||||
queueTick();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls tick on all animations currently registered with the
|
||||
* animation manager.
|
||||
*/
|
||||
protected void tickAnimations (long timestamp)
|
||||
{
|
||||
int size = _anims.size();
|
||||
for (int ii = 0; ii < size; ii++) {
|
||||
((Animation)_anims.get(ii)).tick(timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any finished animations from the list of animations and
|
||||
* notifies their respective animation observers, if any.
|
||||
*/
|
||||
protected void removeFinishedAnimations ()
|
||||
{
|
||||
int size = _anims.size();
|
||||
for (int ii = size - 1; ii >= 0; ii--) {
|
||||
Animation anim = (Animation)_anims.get(ii);
|
||||
if (anim.isFinished()) {
|
||||
@@ -371,93 +103,32 @@ public class AnimationManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues up a tick on the AWT event handler thread, iff we are still
|
||||
* operating.
|
||||
*/
|
||||
protected synchronized void queueTick ()
|
||||
{
|
||||
if (_ticker != null) {
|
||||
SwingUtilities.invokeLater(_ticker);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new list of dirty rectangles representing the given list
|
||||
* with any intersecting rectangles merged. The given list is
|
||||
* destructively modified and cleared of all contents.
|
||||
* Renders all registered animations in the given layer that intersect
|
||||
* the supplied clipping rectangle to the given graphics context.
|
||||
*
|
||||
* @return the list of merged dirty rects.
|
||||
* @param layer the layer to render; one of {@link #FRONT}, {@link
|
||||
* #BACK}, or {@link #ALL}. The front layer contains all animations
|
||||
* with a positive render order; the back layer contains all
|
||||
* animations with a negative render order; all, both.
|
||||
*/
|
||||
protected List mergeDirtyRects (List rects)
|
||||
public void renderAnimations (Graphics2D gfx, int layer, Shape clip)
|
||||
{
|
||||
ArrayList merged = new ArrayList();
|
||||
|
||||
while (rects.size() > 0) {
|
||||
// pop the next rectangle from the dirty list
|
||||
Rectangle mr = (Rectangle)rects.remove(0);
|
||||
|
||||
// merge in any overlapping rectangles
|
||||
for (int ii = 0; ii < rects.size(); ii++) {
|
||||
Rectangle r = (Rectangle)rects.get(ii);
|
||||
if (mr.intersects(r)) {
|
||||
// remove the overlapping rectangle from the list
|
||||
rects.remove(ii--);
|
||||
// grow the merged dirty rectangle
|
||||
mr.add(r);
|
||||
}
|
||||
int size = _anims.size();
|
||||
for (int ii = 0; ii < size; ii++) {
|
||||
Animation anim = (Animation)_anims.get(ii);
|
||||
int order = anim.getRenderOrder();
|
||||
if (((layer == ALL) ||
|
||||
(layer == FRONT && order >= 0) ||
|
||||
(layer == BACK && order < 0)) &&
|
||||
clip.intersects(anim.getBounds())) {
|
||||
anim.paint(gfx);
|
||||
}
|
||||
|
||||
// add the merged rectangle to the list
|
||||
merged.add(mr);
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void checkpoint (String name, int ticks)
|
||||
{
|
||||
Log.info(name + " [ticks=" + ticks + "].");
|
||||
}
|
||||
/** Used to accumulate dirty regions. */
|
||||
protected RegionManager _remgr;
|
||||
|
||||
/** The list of animations. */
|
||||
protected ArrayList _anims = new ArrayList();
|
||||
|
||||
/** The dirty rectangles. */
|
||||
protected ArrayList _dirty = new ArrayList();
|
||||
|
||||
/** The ticker runnable that we put on the AWT thread periodically. */
|
||||
protected Runnable _ticker;
|
||||
|
||||
/** The number of milliseconds in between our refreshes. */
|
||||
protected long _refreshInterval = DEFAULT_REFRESH_INTERVAL;
|
||||
|
||||
/** The number of outstanding tick requests. */
|
||||
protected int _ticking = 0;
|
||||
|
||||
/** The refresh interval id. */
|
||||
protected int _iid = -1;
|
||||
|
||||
/** The sprite manager. */
|
||||
protected SpriteManager _spritemgr;
|
||||
|
||||
/** The view on which we are animating. */
|
||||
protected AnimatedView _view;
|
||||
|
||||
/** The velocity at which we are scrolling (in milliseconds per pixel)
|
||||
* or zero if we're not scrolling. */
|
||||
protected int _scrollvel;
|
||||
|
||||
/** The default number of refresh operations per second. */
|
||||
protected static final int DEFAULT_FRAME_RATE = 30;
|
||||
|
||||
/** The minimum frame rate we'll adjust to when scrolling. */
|
||||
protected static final int MIN_FRAME_RATE = 20;
|
||||
|
||||
/** The maximum frame rate we'll adjust to when scrolling. */
|
||||
protected static final int MAX_FRAME_RATE = 40;
|
||||
|
||||
/** The milliseconds to sleep to obtain desired frame rate. */
|
||||
protected static final long DEFAULT_REFRESH_INTERVAL =
|
||||
1000 / DEFAULT_FRAME_RATE;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// $Id: RainAnimation.java,v 1.2 2002/04/15 18:18:20 mdb Exp $
|
||||
// $Id: RainAnimation.java,v 1.3 2002/04/23 01:16:28 mdb Exp $
|
||||
|
||||
package com.threerings.media.animation;
|
||||
|
||||
@@ -62,7 +62,6 @@ public class RainAnimation extends Animation
|
||||
public void tick (long timestamp)
|
||||
{
|
||||
_finished = (timestamp >= _end);
|
||||
_animmgr.addDirtyRect(new Rectangle(_bounds));
|
||||
|
||||
// calculate the latest raindrop locations
|
||||
for (int ii = 0; ii < _count; ii++) {
|
||||
@@ -70,6 +69,8 @@ public class RainAnimation extends Animation
|
||||
int y = RandomUtil.getInt(_bounds.height);
|
||||
_drops[ii] = (x << 16 | y);
|
||||
}
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// $Id: ImageSprite.java,v 1.3 2002/04/17 15:52:49 mdb Exp $
|
||||
// $Id: ImageSprite.java,v 1.4 2002/04/23 01:16:28 mdb Exp $
|
||||
|
||||
package com.threerings.media.sprite;
|
||||
|
||||
@@ -175,8 +175,10 @@ public class ImageSprite extends Sprite
|
||||
updateRenderOffset();
|
||||
updateRenderOrigin();
|
||||
|
||||
// now invalidate the dirtied region
|
||||
invalidate(dirty);
|
||||
// give the dirty rectangle to the region manager
|
||||
if (_spritemgr != null) {
|
||||
_spritemgr.getRegionManager().addDirtyRegion(dirty);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -252,14 +254,6 @@ public class ImageSprite extends Sprite
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
protected void invalidate (Rectangle r)
|
||||
{
|
||||
if (_frame != null) {
|
||||
super.invalidate(r);
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
protected void toString (StringBuffer buf)
|
||||
{
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
//
|
||||
// $Id: Sprite.java,v 1.41 2002/04/16 02:28:50 mdb Exp $
|
||||
// $Id: Sprite.java,v 1.42 2002/04/23 01:16:28 mdb Exp $
|
||||
|
||||
package com.threerings.media.sprite;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Shape;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
import com.threerings.util.DirectionCodes;
|
||||
|
||||
@@ -17,7 +15,7 @@ import com.threerings.media.Log;
|
||||
|
||||
/**
|
||||
* The sprite class represents a single moveable object in an animated
|
||||
* view. A sprite has a position and orientation within the view, and can
|
||||
* view. A sprite has a position and orientation within the view, and can
|
||||
* be moved along a path.
|
||||
*/
|
||||
public abstract class Sprite
|
||||
@@ -182,8 +180,8 @@ public abstract class Sprite
|
||||
*/
|
||||
public void setLocation (int x, int y)
|
||||
{
|
||||
// create a starting dirty rectangle with our current position
|
||||
Rectangle dirty = new Rectangle(_bounds);
|
||||
// dirty our current bounds
|
||||
invalidate();
|
||||
|
||||
// move ourselves
|
||||
_x = x;
|
||||
@@ -193,19 +191,8 @@ public abstract class Sprite
|
||||
// size of our current bounds
|
||||
updateRenderOrigin();
|
||||
|
||||
if (dirty.intersects(_bounds)) {
|
||||
// grow the dirty rectangle to reflect our new location
|
||||
dirty.add(_bounds);
|
||||
|
||||
} else {
|
||||
// dirty the our new bounds rectangle separately from the old
|
||||
// to avoid potentially creating a large dirty rectangle if
|
||||
// the sprite warps from place to place
|
||||
invalidate(null);
|
||||
}
|
||||
|
||||
// invalidate the potentially-grown starting dirty rectangle
|
||||
invalidate(dirty);
|
||||
// dirty our new bounds
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -327,25 +314,9 @@ public abstract class Sprite
|
||||
* Invalidate the sprite's bounding rectangle for later repainting.
|
||||
*/
|
||||
public void invalidate ()
|
||||
{
|
||||
invalidate(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate the given display rectangle for later repainting.
|
||||
* Passing <code>null</code> will simply invalidate the sprite's
|
||||
* entire rendered bounds. Note that the given rectangle may be
|
||||
* destructively modified at some later time, e.g., by {@link
|
||||
* com.threerings.media.animation.AnimationManager#mergeDirtyRects}.
|
||||
*/
|
||||
protected void invalidate (Rectangle r)
|
||||
{
|
||||
if (_spritemgr != null) {
|
||||
_spritemgr.addDirtyRect((r != null) ? r : new Rectangle(_bounds));
|
||||
|
||||
} else {
|
||||
// Log.warning("Was invalidated but have no sprite manager " +
|
||||
// this + ".");
|
||||
_spritemgr.getRegionManager().invalidateRegion(_bounds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,11 +328,11 @@ public abstract class Sprite
|
||||
* the timestamp information to compute elapsed progress if it wishes
|
||||
* to handle heavy loads gracefully.
|
||||
*/
|
||||
public void tick (long timestamp)
|
||||
public void tick (long tickStamp)
|
||||
{
|
||||
// if we've a path, move the sprite along toward its destination
|
||||
if (_path != null) {
|
||||
_path.updatePosition(this, timestamp);
|
||||
_path.updatePosition(this, tickStamp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,13 +382,12 @@ public abstract class Sprite
|
||||
|
||||
/**
|
||||
* Called by the sprite manager to let us know that the view we occupy
|
||||
* is scrolling by the specified amount.
|
||||
*
|
||||
* @return a dirty rectangle that will be added to the list of
|
||||
* rectangles dirtied by the scrolling or null if the sprite scrolled
|
||||
* along with the view and didn't create any dirty region.
|
||||
* is scrolling by the specified amount. A sprite anchored to the view
|
||||
* will simply update its coordinates; a fixed sprite will remain in
|
||||
* the same position but invalidate its bounds and its scrolled
|
||||
* bounds.
|
||||
*/
|
||||
protected Rectangle viewWillScroll (int dx, int dy)
|
||||
protected void viewWillScroll (int dx, int dy)
|
||||
{
|
||||
if (_scrollsWithView) {
|
||||
// update our coordinates
|
||||
@@ -431,12 +401,9 @@ public abstract class Sprite
|
||||
_path.viewWillScroll(dx, dy);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
} else {
|
||||
// if we're not scrolling, then we need to dirty our bounds
|
||||
// and the part of our bounds that were just scrolled out from
|
||||
// under us
|
||||
// if we're not scrolling, we need to dirty our bounds and the
|
||||
// part that was just scrolled out from under us
|
||||
Rectangle dirty = new Rectangle(_bounds);
|
||||
|
||||
// expand the rectangle to contain the scrolled regions
|
||||
@@ -449,7 +416,8 @@ public abstract class Sprite
|
||||
}
|
||||
dirty.add(ex, ey);
|
||||
|
||||
return dirty;
|
||||
// give the dirty rectangle to the region manager
|
||||
_spritemgr.getRegionManager().addDirtyRegion(dirty);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// $Id: SpriteManager.java,v 1.28 2002/04/15 23:10:24 mdb Exp $
|
||||
// $Id: SpriteManager.java,v 1.29 2002/04/23 01:16:28 mdb Exp $
|
||||
|
||||
package com.threerings.media.sprite;
|
||||
|
||||
@@ -19,29 +19,23 @@ import com.samskivert.util.SortableArrayList;
|
||||
import com.samskivert.util.Tuple;
|
||||
|
||||
import com.threerings.media.Log;
|
||||
import com.threerings.media.MediaConstants;
|
||||
import com.threerings.media.RegionManager;
|
||||
|
||||
/**
|
||||
* The sprite manager manages the sprites running about in the game.
|
||||
*/
|
||||
public class SpriteManager
|
||||
implements MediaConstants
|
||||
{
|
||||
/** Constant for the front layer of sprites. */
|
||||
public static final int FRONT = 0;
|
||||
|
||||
/** Constant for the back layer of sprites. */
|
||||
public static final int BACK = 1;
|
||||
|
||||
/** Constant for all layers of sprites. */
|
||||
public static final int ALL = 2;
|
||||
|
||||
/**
|
||||
* Construct and initialize the SpriteManager object.
|
||||
* Construct and initialize the sprite manager.
|
||||
*/
|
||||
public SpriteManager ()
|
||||
public SpriteManager (RegionManager remgr)
|
||||
{
|
||||
_sprites = new SortableArrayList();
|
||||
_notify = new ArrayList();
|
||||
_dirty = new ArrayList();
|
||||
_remgr = remgr;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,33 +43,18 @@ public class SpriteManager
|
||||
* the specified offsets. It can update the positions of its sprites
|
||||
* if they are tracking the scrolled view, or generate dirty regions
|
||||
* for the sprites that remain in place (meaning they move relative to
|
||||
* the scrolling view). Regions invalidated by the scrolled sprites
|
||||
* should be appended to the supplied invalid rectangles list.
|
||||
* the scrolling view).
|
||||
*/
|
||||
public void viewWillScroll (int dx, int dy, List invalidRects)
|
||||
public void viewWillScroll (int dx, int dy)
|
||||
{
|
||||
// let the sprites know that the view is scrolling
|
||||
int size = _sprites.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
Sprite sprite = (Sprite)_sprites.get(i);
|
||||
Rectangle dirty = sprite.viewWillScroll(dx, dy);
|
||||
if (dirty != null) {
|
||||
invalidRects.add(dirty);
|
||||
}
|
||||
sprite.viewWillScroll(dx, dy);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a rectangle to the dirty rectangle list.
|
||||
*
|
||||
* @param rect the rectangle to add.
|
||||
*/
|
||||
public void addDirtyRect (Rectangle rect)
|
||||
{
|
||||
// translate the rectangle according to our viewport offset
|
||||
_dirty.add(rect);
|
||||
}
|
||||
|
||||
/**
|
||||
* When an animated view processes its dirty rectangles, it may
|
||||
* require an expansion of the dirty region which may in turn
|
||||
@@ -153,20 +132,29 @@ public class SpriteManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the sprites residing within the given shape and layer to the
|
||||
* given graphics context.
|
||||
* Provides access to the region manager that the sprite manager is
|
||||
* using to collect invalid regions every frame. This should generally
|
||||
* only be used by sprites that want to invalidate themselves.
|
||||
*/
|
||||
public RegionManager getRegionManager ()
|
||||
{
|
||||
return _remgr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render to the given graphics context the sprites intersecting the
|
||||
* given shape and residing in the specified layer.
|
||||
*
|
||||
* @param gfx the graphics context.
|
||||
* @param bounds the bounding shape.
|
||||
* @param layer the layer to render; one of {@link #FRONT}, {@link
|
||||
* #BACK}, or {@link #ALL}. The front layer contains all sprites with
|
||||
* a positive render order; the back layer contains all sprites with a
|
||||
* negative render order; all, both.
|
||||
* @param bounds the bounding shape.
|
||||
*/
|
||||
public void renderSprites (Graphics2D gfx, Shape bounds, int layer)
|
||||
public void renderSprites (Graphics2D gfx, int layer, Shape bounds)
|
||||
{
|
||||
// TODO: optimize to store sprites based on quadrants they're
|
||||
// in (or somesuch), and sorted, so that we can more quickly
|
||||
// TODO: optimize to store sprites based on quadrants they're in
|
||||
// (or somesuch), and sorted, so that we can more quickly
|
||||
// determine which sprites to draw.
|
||||
|
||||
int size = _sprites.size();
|
||||
@@ -197,14 +185,14 @@ public class SpriteManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Called periodically by the tick tasks put on the AWT event queue by
|
||||
* the {@link com.threerings.media.animation.AnimationManager}.
|
||||
* Handles moving about of sprites and reporting of sprite collisions.
|
||||
* Must be called every frame so that the sprites can be properly
|
||||
* updated. Normally a sprite manager is used in conjunction with an
|
||||
* animated panel which case this is called automatically.
|
||||
*/
|
||||
public void tick (long timestamp, List rects)
|
||||
public void tick (long tickStamp)
|
||||
{
|
||||
// tick all sprites
|
||||
tickSprites(timestamp);
|
||||
tickSprites(tickStamp);
|
||||
|
||||
// re-sort the sprite list to account for potential new positions
|
||||
_sprites.sort(SPRITE_COMP);
|
||||
@@ -219,26 +207,19 @@ public class SpriteManager
|
||||
// observers may take, such as sprite removal, won't screw us
|
||||
// up elsewhere.
|
||||
handleSpriteEvents();
|
||||
|
||||
// add all generated dirty rectangles to the passed-in dirty
|
||||
// rectangle list
|
||||
CollectionUtil.addAll(rects, _dirty.iterator());
|
||||
|
||||
// clear out our internal dirty rectangle list
|
||||
_dirty.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>tick()</code> on all sprite objects to give them a
|
||||
* Call {@link Sprite#tick} on all sprite objects to give them a
|
||||
* chance to move themselves about, change their display image,
|
||||
* and so forth.
|
||||
* generate dirty regions and so forth.
|
||||
*/
|
||||
protected void tickSprites (long timestamp)
|
||||
protected void tickSprites (long tickStamp)
|
||||
{
|
||||
int size = _sprites.size();
|
||||
for (int ii = 0; ii < size; ii++) {
|
||||
Sprite sprite = (Sprite)_sprites.get(ii);
|
||||
sprite.tick(timestamp);
|
||||
sprite.tick(tickStamp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,8 +283,8 @@ public class SpriteManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all sprite observers of any sprite events that took
|
||||
* place during our most recent <code>tick()</code>.
|
||||
* Notify all sprite observers of any sprite events that took place
|
||||
* during our most recent <code>tick()</code>.
|
||||
*/
|
||||
protected void handleSpriteEvents ()
|
||||
{
|
||||
@@ -336,18 +317,19 @@ public class SpriteManager
|
||||
_notify.add(new Tuple(observers, event));
|
||||
}
|
||||
|
||||
/** The comparator used to sort sprites by horizontal position. */
|
||||
protected static final Comparator SPRITE_COMP = new SpriteComparator();
|
||||
|
||||
/** The sprite objects we're managing. */
|
||||
protected SortableArrayList _sprites;
|
||||
|
||||
/** The dirty rectangles created by sprites. */
|
||||
protected ArrayList _dirty;
|
||||
|
||||
/** The list of pending sprite notifications. */
|
||||
protected ArrayList _notify;
|
||||
|
||||
/** Used to accumulate dirty regions. */
|
||||
protected RegionManager _remgr;
|
||||
|
||||
/** The comparator used to sort sprites by horizontal position. */
|
||||
protected static final Comparator SPRITE_COMP = new SpriteComparator();
|
||||
|
||||
/** Used to sort sprites. */
|
||||
protected static class SpriteComparator implements Comparator
|
||||
{
|
||||
public int compare (Object o1, Object o2)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// $Id: Path.java,v 1.3 2002/04/15 23:09:10 mdb Exp $
|
||||
// $Id: Path.java,v 1.4 2002/04/23 01:16:28 mdb Exp $
|
||||
|
||||
package com.threerings.media.sprite;
|
||||
|
||||
@@ -24,7 +24,7 @@ public interface Path
|
||||
* Called once to let the path prepare itself for the process of
|
||||
* animating the supplied sprite.
|
||||
*/
|
||||
public void init (Sprite sprite, long timestamp);
|
||||
public void init (Sprite sprite, long tickStamp);
|
||||
|
||||
/**
|
||||
* Called to request that this path update the position of the
|
||||
@@ -33,10 +33,13 @@ public interface Path
|
||||
* of the sprite along the path based on the time elapsed since the
|
||||
* sprite began down the path.
|
||||
*
|
||||
* @param sprite the sprite whose position should be updated.
|
||||
* @param tickStamp the timestamp associated with this frame.
|
||||
*
|
||||
* @return true if the sprite's position was updated, false if the
|
||||
* path determined that the sprite should not move at this time.
|
||||
*/
|
||||
public boolean updatePosition (Sprite sprite, long timestamp);
|
||||
public boolean updatePosition (Sprite sprite, long tickStamp);
|
||||
|
||||
/**
|
||||
* Sets the velocity of this sprite in pixels per millisecond. The
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// $Id: PerformanceMonitor.java,v 1.3 2001/10/25 22:08:29 mdb Exp $
|
||||
// $Id: PerformanceMonitor.java,v 1.4 2002/04/23 01:16:28 mdb Exp $
|
||||
|
||||
package com.threerings.media.util;
|
||||
|
||||
@@ -135,9 +135,10 @@ class PerformanceAction
|
||||
_numTicks++;
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
if ((now - _lastDelta) >= _delta) {
|
||||
long passed = now - _lastDelta;
|
||||
if (passed >= _delta) {
|
||||
// update the last checkpoint time
|
||||
_lastDelta = now;
|
||||
_lastDelta = now + (passed - _delta);
|
||||
|
||||
// notify our observer of the checkpoint
|
||||
_obs.checkpoint(_name, _numTicks);
|
||||
|
||||
Reference in New Issue
Block a user