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:
Michael Bayne
2002-04-23 01:16:28 +00:00
parent 849eaec47b
commit cc6a9d00ef
18 changed files with 1596 additions and 1093 deletions
@@ -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