Pulled out gobs of Sprite/Animation functionality into AbstractMedia.

Pulled out gobs of SpriteManager/AnimationManager functionality into
  AbstractMediaManager.
The big change: sprites can have their render order fine-tuned like
animations.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1787 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Ray Greenwell
2002-10-08 21:03:37 +00:00
parent e804abad30
commit a0730067ec
10 changed files with 532 additions and 659 deletions
@@ -0,0 +1,184 @@
//
// $Id: AbstractMedia.java,v 1.1 2002/10/08 21:03:37 ray Exp $
package com.threerings.media;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.ArrayList;
import com.samskivert.util.StringUtil;
/**
* Something that can be rendered on the media panel.
*/
public abstract class AbstractMedia
{
/**
* Instantiate an abstract media object.
*/
public AbstractMedia (Rectangle bounds)
{
_bounds = bounds;
}
/**
* Called periodically by this media's manager to give it
* a chance to do its thing.
*
* @param tickStamp the system time for this tick.
*/
public abstract void tick (long tickStamp);
/**
* Called by the appropriate manager to request that the
* media render itself with the given graphics context. The
* media 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
* media's bounds intersect the clipping region.
*/
public abstract void paint (Graphics2D gfx);
/**
* Called when the appropriate media manager has been paused for some
* length of time and is then unpaused. Media should adjust any time stamps
* that are maintained internally forward by the delta so that time
* maintains the illusion of flowing smoothly forward.
*/
public void fastForward (long timeDelta)
{
}
/**
* Invalidate the media's bounding rectangle for later painting.
*/
public void invalidate ()
{
if (_mgr != null) {
_mgr.getRegionManager().invalidateRegion(_bounds);
}
}
/**
* Set the location.
*/
public void setLocation (int x, int y)
{
_bounds.x = x;
_bounds.y = y;
}
/**
* Returns a rectangle containing all the pixels rendered by this media.
*/
public Rectangle getBounds ()
{
return _bounds;
}
/**
* Sets the render order associated with this media. Media
* can be rendered in two layers; those with negative render order and
* those with positive render order. In the same layer, they
* will be rendered according to their render order's cardinal value
* (least to greatest). Those with the same render order value will be
* rendered in arbitrary order.
*
* <p> This must be set <em>before</em> the media is added to the
* appropriate manager and must not change while the manager is
* managing it. If you wish to change the render order, remove the
* media from the manager, change the order and add it back again.
*/
public void setRenderOrder (int renderOrder)
{
_renderOrder = renderOrder;
}
/**
* Returns the render order of this media element.
*/
public int getRenderOrder ()
{
return _renderOrder;
}
/**
* Initialize the media.
*/
protected final void init (AbstractMediaManager manager)
{
_mgr = manager;
init();
}
/**
* Called when the media has had its manager set.
* Derived classes may override this method, but should be sure to
* call <code>super.init()</code>.
*/
protected void init ()
{
}
/**
* Called by the media manager after the media is removed from service.
* Derived classes may override this method, but should be sure to
* call <code>super.shutdown()</code>.
*/
protected void shutdown ()
{
_mgr = null;
}
/**
* Add the specified observer to this media element.
*/
protected void addObserver (Object obs)
{
if (_observers == null) {
_observers = new ArrayList();
} else if (_observers.contains(obs)) {
Log.info("Attempt to observe media already observing " +
"[media=" + this + ", obs=" + obs + "].");
return;
}
_observers.add(obs);
}
/**
* Dumps this media to a String object.
*/
public String toString ()
{
StringBuffer buf = new StringBuffer("[");
toString(buf);
return buf.append("]").toString();
}
/**
* This should be overridden by derived classes (which should be sure
* to call <code>super.toString()</code>) to append the derived class
* specific information to the string buffer.
*/
protected void toString (StringBuffer buf)
{
buf.append("bounds=").append(StringUtil.toString(_bounds));
buf.append(", renderOrder=").append(_renderOrder);
}
/** The layer in which to render. */
protected int _renderOrder = 0;
/** The bounds of the media's rendering area. */
protected Rectangle _bounds;
/** Our manager. */
protected AbstractMediaManager _mgr;
/** Our observers. */
protected ArrayList _observers = null;
}
@@ -0,0 +1,200 @@
//
// $Id: AbstractMediaManager.java,v 1.1 2002/10/08 21:03:37 ray Exp $
package com.threerings.media;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.util.ArrayList;
import java.util.Comparator;
import com.samskivert.util.SortableArrayList;
import com.samskivert.util.StringUtil;
/**
* Manages, ticks, and paints AbstractMedia.
*/
public abstract class AbstractMediaManager
implements MediaConstants
{
/**
* Default constructor.
*/
public AbstractMediaManager (RegionManager remgr)
{
_remgr = remgr;
}
/**
* Provides access to the region manager that the mediamanager is using.
*/
public RegionManager getRegionManager ()
{
return _remgr;
}
/**
* Renders all registered media in the given layer that intersect
* the supplied clipping rectangle 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.
*/
public void renderMedia (Graphics2D gfx, int layer, Shape clip)
{
for (int ii=0, nn=_media.size(); ii < nn; ii++) {
AbstractMedia media = (AbstractMedia) _media.get(ii);
int order = media.getRenderOrder();
try {
if (((layer == ALL) ||
(layer == FRONT && order >= 0) ||
(layer == BACK && order < 0)) &&
clip.intersects(media.getBounds())) {
media.paint(gfx);
}
} catch (Exception e) {
Log.warning("Failed to render media " +
"[media=" + media + ", e=" + e + "].");
Log.logStackTrace(e);
}
}
}
/**
* Must be called every frame so that the media can be properly
* updated.
*/
public void tick (long tickStamp)
{
tickAllMedia(tickStamp);
dispatchNotifications();
}
/**
* If the manager is paused for some length of time, it should
* be fast forwarded by the appropriate number of milliseconds. This
* allows media to smoothly pick up where they left off rather than
* abruptly jumping into the future, thinking that some outrageous
* amount of time passed since their last tick.
*/
public void fastForward (long timeDelta)
{
for (int ii=0, nn=_media.size(); ii < nn; ii++) {
((AbstractMedia) _media.get(ii)).fastForward(timeDelta);
}
}
/**
* Call {@link AbstractMedia#tick} on all media to give
* them a chance to move about, change their look, generate dirty
* regions, and so forth.
*/
protected void tickAllMedia (long tickStamp)
{
// we tick media in reverse order, because some may remove
// themselves on the tick
for (int ii=_media.size() - 1; ii >= 0; ii--) {
((AbstractMedia) _media.get(ii)).tick(tickStamp);
}
}
/**
* Insert the specified media into this manager, return true on success.
*/
protected boolean insertMedia (AbstractMedia media)
{
if (_media.contains(media)) {
Log.warning("Attempt to insert media more than once " +
"[media=" + media + "].");
return false;
}
_media.insertSorted(media, RENDER_ORDER);
media.init(this);
return true;
}
/**
* Remove the specified media from this manager, return true on success.
*/
protected boolean removeMedia (AbstractMedia media)
{
if (_media.remove(media)) {
media.invalidate();
media.shutdown();
return true;
}
Log.warning("Attempt to remove media that wasn't inserted " +
"[media=" + media + "].");
return false;
}
/**
* Clears all media from the manager. This does not invalidate
* their vacated bounds, it is assumed that it will be ok.
*/
protected void clearMedia ()
{
for (int ii=_media.size() - 1; ii >= 0; ii--) {
AbstractMedia media = (AbstractMedia) _media.remove(ii);
media.shutdown();
}
}
/**
* Queue the event for dispatching after we've ticked all
* the media.
*/
public void queueNotification (ArrayList observers, Object event)
{
_notify.add(new Object[] {observers, event});
}
/**
* Dispatch all queued events.
*/
protected void dispatchNotifications ()
{
for (int ii=0, nn=_notify.size(); ii < nn; ii++) {
Object[] junk = (Object[]) _notify.remove(0);
ArrayList observers = (ArrayList) junk[0];
Object event = junk[1];
dispatchEvent(observers, event);
}
}
/**
* Dispatch the specified event to the specified observers.
*/
protected abstract void dispatchEvent (ArrayList observers, Object event);
/** The Region Manager. */
protected RegionManager _remgr;
/** List of observers to notify at the end of the tick. */
protected ArrayList _notify = new ArrayList();
/** Our render-order sorted list of media. */
protected SortableArrayList _media = new SortableArrayList();
/** Used to sort media by render order. */
protected static final Comparator RENDER_ORDER = new Comparator() {
public int compare (Object o1, Object o2) {
int result = (((AbstractMedia)o1)._renderOrder -
((AbstractMedia)o2)._renderOrder);
if (result != 0) {
return result;
} else {
// find some other way to keep them stable relative to each
// other.
return o1.hashCode() - o2.hashCode();
}
}
};
}
@@ -1,5 +1,5 @@
//
// $Id: MediaPanel.java,v 1.17 2002/06/25 17:31:16 mdb Exp $
// $Id: MediaPanel.java,v 1.18 2002/10/08 21:03:37 ray Exp $
package com.threerings.media;
@@ -371,8 +371,8 @@ public class MediaPanel extends JComponent
*/
protected void paintBits (Graphics2D gfx, int layer, Rectangle dirty)
{
_animmgr.renderAnimations(gfx, layer, dirty);
_spritemgr.renderSprites(gfx, layer, dirty);
_animmgr.renderMedia(gfx, layer, dirty);
_spritemgr.renderMedia(gfx, layer, dirty);
}
/** The frame manager with whom we register. */
@@ -1,15 +1,11 @@
//
// $Id: Animation.java,v 1.8 2002/09/20 21:28:20 mdb Exp $
// $Id: Animation.java,v 1.9 2002/10/08 21:03:37 ray Exp $
package com.threerings.media.animation;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.ArrayList;
import com.samskivert.util.StringUtil;
import com.threerings.media.AbstractMedia;
import com.threerings.media.Log;
/**
@@ -17,7 +13,7 @@ import com.threerings.media.Log;
* provide animation functionality. It is generally used in conjunction
* with an {@link AnimationManager}.
*/
public abstract class Animation
public abstract class Animation extends AbstractMedia
{
/**
* Constructs an animation.
@@ -26,80 +22,7 @@ public abstract class Animation
*/
public Animation (Rectangle bounds)
{
_bounds = bounds;
}
/**
* Returns the render order of this animation.
*/
public int getRenderOrder ()
{
return _renderOrder;
}
/**
* Sets the render order associated with this animation. Animations
* can be rendered in two layers; those with negative render order and
* those with positive render order. In the same layer, animations
* will be rendered according to their render order's cardinal value
* (least to greatest). Those with the same render order value will be
* rendered in arbitrary order.
*
* <p> This must be set <em>before</em> the animation is handed to the
* {@link AnimationManager} and must not change while the {@link
* AnimationManager} is managing the animation. If you wish to change
* the render order, remove the animation from the manager, change the
* order and add it back again.
*/
public void setRenderOrder (int value)
{
_renderOrder = value;
}
/**
* Sets the location at which this animation will be rendered.
*/
public void setLocation (int x, int y)
{
_bounds.x = x;
_bounds.y = y;
}
/**
* Returns a rectangle containing all pixels rendered by this
* animation.
*/
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 tickStamp);
/**
* 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 abstract void paint (Graphics2D gfx);
/**
* This is called if the animation manager is paused for some length
* of time and then unpaused. Animations should adjust any time stamps
* they are maintaining internally by the delta so that time maintains
* the illusion of flowing smoothly forward.
*/
public void fastForward (long timeDelta)
{
super(bounds);
}
/**
@@ -120,14 +43,6 @@ public abstract class Animation
_finished = false;
}
/**
* 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.
@@ -142,76 +57,20 @@ public abstract class Animation
*/
public void addAnimationObserver (AnimationObserver obs)
{
// create the observer list if it doesn't yet exist
if (_observers == null) {
_observers = new ArrayList();
}
// make sure each observer observes only once
if (_observers.contains(obs)) {
Log.info("Attempt to observe animation already observing " +
"[anim=" + this + ", obs=" + obs + "].");
return;
}
// add the observer
_observers.add(obs);
addObserver(obs);
}
/**
* Notifies any animation observers that the given animation event has
* occurred.
*/
public void notifyObservers (AnimationEvent e)
public void notifyObservers (AnimationEvent event)
{
int size = (_observers == null) ? 0 : _observers.size();
for (int ii = 0; ii < size; ii++) {
((AnimationObserver)_observers.get(ii)).handleEvent(e);
if (_observers != null) {
_mgr.queueNotification(_observers, event);
}
}
/**
* Return a string representation of the animation.
*/
public String toString ()
{
StringBuffer buf = new StringBuffer();
buf.append("[");
toString(buf);
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
* specific animation information to the string buffer.
*/
protected void toString (StringBuffer buf)
{
buf.append("bounds=").append(StringUtil.toString(_bounds));
}
/** Our animation manager. */
protected AnimationManager _animmgr;
/** Whether the animation is finished. */
protected boolean _finished = false;
/** The animation bounds. */
protected Rectangle _bounds;
/** The render order of this animation. */
protected int _renderOrder;
/** The list of animation observers. */
protected ArrayList _observers;
}
@@ -1,17 +1,14 @@
//
// $Id: AnimationManager.java,v 1.13 2002/09/20 21:28:20 mdb Exp $
// $Id: AnimationManager.java,v 1.14 2002/10/08 21:03:37 ray Exp $
package com.threerings.media.animation;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.util.Comparator;
import java.util.ArrayList;
import com.samskivert.util.SortableArrayList;
import com.threerings.media.AbstractMediaManager;
import com.threerings.media.Log;
import com.threerings.media.MediaConstants;
import com.threerings.media.RegionManager;
/**
@@ -19,8 +16,7 @@ import com.threerings.media.RegionManager;
* manager itself is ticked and generating events when animations finish
* and suchlike.
*/
public class AnimationManager
implements MediaConstants
public class AnimationManager extends AbstractMediaManager
{
/**
* Construct and initialize the animation manager which readies itself
@@ -28,7 +24,7 @@ public class AnimationManager
*/
public AnimationManager (RegionManager remgr)
{
_remgr = remgr;
super(remgr);
}
/**
@@ -37,14 +33,7 @@ public class AnimationManager
*/
public void registerAnimation (Animation anim)
{
if (_anims.contains(anim)) {
Log.warning("Attempt to register animation more than once " +
"[anim=" + anim + "].");
return;
}
anim.setAnimationManager(this);
_anims.insertSorted(anim, RENDER_ORDER);
insertMedia(anim);
}
/**
@@ -55,43 +44,17 @@ public class AnimationManager
*/
public void unregisterAnimation (Animation anim)
{
// un-register the animation
if (!_anims.remove(anim)) {
Log.warning("Attempt to un-register animation that isn't " +
"registered [anim=" + anim + "].");
return;
}
// invalidate its bounds
_remgr.invalidateRegion(anim.getBounds());
removeMedia(anim);
}
/**
* 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 RegionManager getRegionManager ()
// documentation inherited
protected void tickAllMedia (long tickStamp)
{
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)_anims.get(ii)).tick(tickStamp);
}
super.tickAllMedia(tickStamp);
// remove any finished animations
for (int ii = size - 1; ii >= 0; ii--) {
Animation anim = (Animation)_anims.get(ii);
for (int ii=_media.size() - 1; ii >= 0; ii--) {
Animation anim = (Animation)_media.get(ii);
if (anim.isFinished()) {
// let any animation observers know that we're done
anim.notifyObservers(new AnimationCompletedEvent(anim));
@@ -104,68 +67,12 @@ public class AnimationManager
}
}
/**
* If the animation manager is paused for some length of time, it
* should be fast forwarded by the appropriate number of milliseconds.
* This allows animations to smoothly pick up where they left off
* rather than abruptly jumping into the future, thinking that some
* outrageous amount of time passed since their last tick.
*/
public void fastForward (long timeDelta)
// documentation inherited
protected void dispatchEvent (ArrayList observers, Object event)
{
// fast forward all of our animations
int size = _anims.size();
for (int ii = 0; ii < size; ii++) {
((Animation)_anims.get(ii)).fastForward(timeDelta);
AnimationEvent aevt = (AnimationEvent) event;
for (int ii=0, nn=observers.size(); ii < nn; ii++) {
((AnimationObserver) observers.get(ii)).handleEvent(aevt);
}
}
/**
* Renders all registered animations in the given layer that intersect
* the supplied clipping rectangle 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.
*/
public void renderAnimations (Graphics2D gfx, int layer, Shape clip)
{
// now paint them
int size = _anims.size();
for (int ii = 0; ii < size; ii++) {
Animation anim = (Animation)_anims.get(ii);
int order = anim.getRenderOrder();
try {
if (((layer == ALL) ||
(layer == FRONT && order >= 0) ||
(layer == BACK && order < 0)) &&
clip.intersects(anim.getBounds())) {
anim.paint(gfx);
}
} catch (Exception e) {
Log.warning("Failed to render animation " +
"[anim=" + anim + ", e=" + e + "].");
Log.logStackTrace(e);
}
}
}
/** Used to accumulate dirty regions. */
protected RegionManager _remgr;
/** The list of animations. */
protected SortableArrayList _anims = new SortableArrayList();
/** Used to sort animations prior to painting. */
protected static final Comparator RENDER_ORDER = new Comparator() {
public int compare (Object o1, Object o2) {
return (((Animation)o1)._renderOrder -
((Animation)o2)._renderOrder);
}
public boolean equals (Object obj) {
return this == obj;
}
};
}
@@ -1,5 +1,5 @@
//
// $Id: ImageSprite.java,v 1.14 2002/09/17 20:21:19 mdb Exp $
// $Id: ImageSprite.java,v 1.15 2002/10/08 21:03:37 ray Exp $
package com.threerings.media.sprite;
@@ -81,9 +81,9 @@ public class ImageSprite extends Sprite
}
// documentation inherited
protected void init (SpriteManager spritemgr)
protected void init ()
{
super.init(spritemgr);
super.init();
// now that we have our spritemanager, we can initialize our frames
setFrameIndex(0, true);
@@ -187,8 +187,8 @@ public class ImageSprite extends Sprite
dirty.add(_bounds);
// give the dirty rectangle to the region manager
if (_spritemgr != null) {
_spritemgr.getRegionManager().addDirtyRegion(dirty);
if (_mgr != null) {
_mgr.getRegionManager().addDirtyRegion(dirty);
}
}
@@ -1,5 +1,5 @@
//
// $Id: LabelSprite.java,v 1.3 2002/07/08 21:15:35 mdb Exp $
// $Id: LabelSprite.java,v 1.4 2002/10/08 21:03:37 ray Exp $
package com.threerings.media.sprite;
@@ -35,9 +35,9 @@ public class LabelSprite extends Sprite
}
// documentation inherited
protected void init (SpriteManager spritemgr)
protected void init ()
{
super.init(spritemgr);
super.init();
// size the bounds to fit our label
Dimension size = _label.getSize();
+16 -136
View File
@@ -1,5 +1,5 @@
//
// $Id: Sprite.java,v 1.50 2002/07/08 21:15:35 mdb Exp $
// $Id: Sprite.java,v 1.51 2002/10/08 21:03:37 ray Exp $
package com.threerings.media.sprite;
@@ -7,10 +7,9 @@ import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.util.ArrayList;
import com.threerings.util.DirectionCodes;
import com.threerings.media.AbstractMedia;
import com.threerings.media.Log;
import com.threerings.media.util.Path;
import com.threerings.media.util.Pathable;
@@ -20,7 +19,7 @@ import com.threerings.media.util.Pathable;
* view. A sprite has a position and orientation within the view, and can
* be moved along a path.
*/
public abstract class Sprite
public abstract class Sprite extends AbstractMedia
implements DirectionCodes, Pathable
{
/**
@@ -33,27 +32,7 @@ public abstract class Sprite
*/
public Sprite ()
{
}
/**
* Called by the sprite manager to initialize the sprite when a sprite
* is added to said manager for management.
*
* @param spritemgr the sprite manager.
*/
protected void init (SpriteManager spritemgr)
{
_spritemgr = spritemgr;
}
/**
* Called by the sprite manager after the sprite is removed from
* service. Derived classes may override this method but should be
* sure to call <code>super.shutdown()</code>.
*/
protected void shutdown ()
{
_spritemgr = null;
super(new Rectangle());
}
/**
@@ -92,14 +71,6 @@ public abstract class Sprite
return _bounds.height;
}
/**
* Returns the sprite's rendered bounds in pixels.
*/
public Rectangle getBounds ()
{
return _bounds;
}
/**
* Sprites have an orientation in one of the eight cardinal
* directions: <code>NORTH</code>, <code>NORTHEAST</code>, etc.
@@ -125,31 +96,7 @@ public abstract class Sprite
return _orient;
}
/**
* Returns the render order of this sprite.
*/
public int getRenderOrder ()
{
return _renderOrder;
}
/**
* Sets the render order associated with this animation. Sprites can
* be rendered in two layers; those with negative render order and
* those with positive render order. Someday sprites will be rendered
* in each layer according to render order.
*/
public void setRenderOrder (int value)
{
_renderOrder = value;
}
/**
* Moves the sprite to the specified location.
*
* @param x the x-position in pixels.
* @param y the y-position in pixels.
*/
// documentation inherited
public void setLocation (int x, int y)
{
// start with our current bounds
@@ -165,24 +112,20 @@ public abstract class Sprite
// grow the dirty rectangle to incorporate our new bounds and pass
// the dirty region to our region manager
if (_spritemgr != null) {
if (_mgr != null) {
// if our new bounds intersect our old bounds, grow a single
// dirty rectangle to incorporate them both
if (_bounds.intersects(dirty)) {
dirty.add(_bounds);
} else {
// otherwise invalidate our new bounds separately
_spritemgr.getRegionManager().invalidateRegion(_bounds);
_mgr.getRegionManager().invalidateRegion(_bounds);
}
_spritemgr.getRegionManager().addDirtyRegion(dirty);
_mgr.getRegionManager().addDirtyRegion(dirty);
}
}
/**
* Paint the sprite to the specified graphics context.
*
* @param gfx the graphics context.
*/
// documentation inherited
public void paint (Graphics2D gfx)
{
gfx.draw(_bounds);
@@ -302,24 +245,7 @@ public abstract class Sprite
notifyObservers(new PathCompletedEvent(this, oldpath));
}
/**
* Invalidate the sprite's bounding rectangle for later repainting.
*/
public void invalidate ()
{
if (_spritemgr != null) {
_spritemgr.getRegionManager().invalidateRegion(_bounds);
}
}
/**
* This method is called periodically by the sprite manager to give
* the sprite a chance to update its state. The sprite manager will
* attempt to call this with the desired refresh rate, but will drop
* calls to tick if it can't keep up. Thus, a sprite should rely on
* the timestamp information to compute elapsed progress if it wishes
* to handle heavy loads gracefully.
*/
// documentation inherited
public void tick (long tickStamp)
{
// if we've a path, move the sprite along toward its destination
@@ -328,12 +254,7 @@ public abstract class Sprite
}
}
/**
* This is called if the sprite manager is paused for some length of
* time and then unpaused. Sprites should adjust any time stamps they
* are maintaining internally by the delta so that time maintains the
* illusion of flowing smoothly forward.
*/
// documentation inherited
public void fastForward (long timeDelta)
{
// fast forward any path we're following
@@ -360,20 +281,7 @@ public abstract class Sprite
*/
public void addSpriteObserver (SpriteObserver obs)
{
// create the observer list if it doesn't yet exist
if (_observers == null) {
_observers = new ArrayList();
}
// make sure each observer observes only once
if (_observers.contains(obs)) {
Log.info("Attempt to observe sprite already observing " +
"[sprite=" + this + ", obs=" + obs + "].");
return;
}
// add the observer
_observers.add(obs);
addObserver(obs);
}
/**
@@ -384,39 +292,20 @@ public abstract class Sprite
protected void notifyObservers (SpriteEvent event)
{
if (_observers != null) {
// we pass this notification off to the sprite manager so that
// it can dispatch all of the notifications at once after all
// ticking has been completed
_spritemgr.notifySpriteObservers(_observers, event);
_mgr.queueNotification(_observers, event);
}
}
/**
* Return a string representation of the sprite.
*/
public String toString ()
{
StringBuffer buf = new StringBuffer("[");
toString(buf);
return buf.append("]").toString();
}
/**
* This should be overridden by derived classes (which should be sure
* to call <code>super.toString()</code>) to append the derived class
* specific sprite information to the string buffer.
*/
// documentation inherited
protected void toString (StringBuffer buf)
{
buf.append("ox=").append(_ox);
super.toString(buf);
buf.append(", ox=").append(_ox);
buf.append(", oy=").append(_oy);
buf.append(", oxoff=").append(_oxoff);
buf.append(", oyoff=").append(_oyoff);
}
/** The sprite manager. */
protected SpriteManager _spritemgr;
/** The location of the sprite's origin in pixel coordinates. If the
* sprite positions itself via a hotspot that is not the upper left
* coordinate of the sprite's bounds, the offset to the hotspot should
@@ -429,18 +318,9 @@ public abstract class Sprite
* its bounds. */
protected int _oxoff, _oyoff;
/** Our rendered bounds in pixel coordinates. */
protected Rectangle _bounds = new Rectangle();
/** The orientation of this sprite. */
protected int _orient = NONE;
/** When moving, the path the sprite is traversing. */
protected Path _path;
/** The render order of this sprite. */
protected int _renderOrder;
/** The sprite observers observing this sprite. */
protected ArrayList _observers;
}
@@ -1,42 +1,32 @@
//
// $Id: SpriteManager.java,v 1.37 2002/09/20 02:19:53 mdb Exp $
// $Id: SpriteManager.java,v 1.38 2002/10/08 21:03:37 ray Exp $
package com.threerings.media.sprite;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import com.samskivert.util.CollectionUtil;
import com.samskivert.util.SortableArrayList;
import com.samskivert.util.Tuple;
import com.threerings.media.AbstractMediaManager;
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
public class SpriteManager extends AbstractMediaManager
{
/**
* Construct and initialize the sprite manager.
*/
public SpriteManager (RegionManager remgr)
{
_sprites = new SortableArrayList();
_notify = new ArrayList();
_remgr = remgr;
super(remgr);
}
/**
@@ -53,9 +43,9 @@ public class SpriteManager
*/
public void getIntersectingSprites (List list, Shape shape)
{
int size = _sprites.size();
int size = _media.size();
for (int ii = 0; ii < size; ii++) {
Sprite sprite = (Sprite)_sprites.get(ii);
Sprite sprite = (Sprite)_media.get(ii);
if (sprite.intersects(shape)) {
list.add(sprite);
}
@@ -75,9 +65,9 @@ public class SpriteManager
*/
public void getHitSprites (List list, int x, int y)
{
int size = _sprites.size();
int size = _media.size();
for (int ii = 0; ii < size; ii++) {
Sprite sprite = (Sprite)_sprites.get(ii);
Sprite sprite = (Sprite)_media.get(ii);
if (sprite.hitTest(x, y)) {
list.add(sprite);
}
@@ -91,12 +81,10 @@ public class SpriteManager
*/
public void addSprite (Sprite sprite)
{
// initialize the sprite
sprite.init(this);
// add the sprite to our list
_sprites.add(sprite);
// and invalidate the sprite's original position
sprite.invalidate();
if (insertMedia(sprite)) {
// and invalidate the sprite's original position
sprite.invalidate();
}
}
/**
@@ -106,7 +94,7 @@ public class SpriteManager
*/
public List getSprites ()
{
return Collections.unmodifiableList(_sprites);
return Collections.unmodifiableList(_media);
}
/**
@@ -117,12 +105,7 @@ public class SpriteManager
*/
public void removeSprite (Sprite sprite)
{
// invalidate the current sprite position
sprite.invalidate();
// remove the sprite from our list
_sprites.remove(sprite);
// clear out our manager reference
sprite.shutdown();
removeMedia(sprite);
}
/**
@@ -133,51 +116,7 @@ public class SpriteManager
*/
public void clearSprites ()
{
int size = _sprites.size();
for (int ii = 0; ii < size; ii++) {
Sprite sprite = (Sprite)_sprites.get(ii);
sprite.shutdown();
}
_sprites.clear();
}
/**
* 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 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, 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
// determine which sprites to draw.
int size = _sprites.size();
for (int ii = 0; ii < size; ii++) {
Sprite sprite = (Sprite)_sprites.get(ii);
int order = sprite.getRenderOrder();
if (((layer == ALL) ||
(layer == FRONT && order >= 0) ||
(layer == BACK && order < 0)) &&
sprite.intersects(bounds)) {
sprite.paint(gfx);
}
}
clearMedia();
}
/**
@@ -187,187 +126,91 @@ public class SpriteManager
*/
public void renderSpritePaths (Graphics2D gfx)
{
int size = _sprites.size();
for (int ii = 0; ii < size; ii++) {
Sprite sprite = (Sprite)_sprites.get(ii);
for (int ii=0, nn=_media.size(); ii < nn; ii++) {
Sprite sprite = (Sprite)_media.get(ii);
sprite.paintPath(gfx);
}
}
/**
* 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 tickStamp)
// documentation inherited
protected void dispatchEvent (ArrayList observers, Object event)
{
// tick all sprites
tickSprites(tickStamp);
// re-sort the sprite list to account for potential new positions
_sprites.sort(SPRITE_COMP);
// // notify sprite observers of any collisions
// handleCollisions();
// notify sprite observers of any sprite events. note that we
// explicitly queue up events while ticking and checking for
// collisions, and we only notify observers once we're
// finished with all of those antics so that any actions the
// observers may take, such as sprite removal, won't screw us
// up elsewhere.
handleSpriteEvents();
}
/**
* If the sprite manager is paused for some length of time, it should
* be fast forwarded by the appropriate number of milliseconds. This
* allows sprites to smoothly pick up where they left off rather than
* abruptly jumping into the future, thinking that some outrageous
* amount of time passed since their last tick.
*/
public void fastForward (long timeDelta)
{
int size = _sprites.size();
for (int ii = 0; ii < size; ii++) {
Sprite sprite = (Sprite)_sprites.get(ii);
sprite.fastForward(timeDelta);
SpriteEvent sevt = (SpriteEvent) event;
for (int ii=0, nn=observers.size(); ii < nn; ii++) {
((SpriteObserver) observers.get(ii)).handleEvent(sevt);
}
}
/**
* Call {@link Sprite#tick} on all sprite objects to give them a
* chance to move themselves about, change their display image,
* generate dirty regions and so forth.
*/
protected void tickSprites (long tickStamp)
{
// we tick sprites in reverse order so as not to freak out if a
// sprite is removed as a result of our calling tick() on it
int size = _sprites.size();
for (int ii = size-1; ii >= 0; ii--) {
Sprite sprite = (Sprite)_sprites.get(ii);
sprite.tick(tickStamp);
}
}
/**
* Check all sprites for collisions with others and inform any
* sprite observers.
*/
protected void handleCollisions ()
{
// gather a list of all sprite collisions
int size = _sprites.size();
for (int ii = 0; ii < size; ii++) {
Sprite sprite = (Sprite)_sprites.get(ii);
checkCollisions(ii, size, sprite);
}
}
/**
* Check a sprite for collision with any other sprites in the
* sprite list and notify the sprite observers associated with any
* sprites that do indeed collide.
*
* @param idx the starting sprite index.
* @param size the total number of sprites.
* @param sprite the sprite to check against other sprites for
* collisions.
*/
protected void checkCollisions (int idx, int size, Sprite sprite)
{
// TODO: make this handle quickly moving objects that may pass
// through each other.
// if we're the last sprite we know we've already handled any
// collisions
if (idx == (size - 1)) {
return;
}
// calculate the x-position of the right edge of the sprite we're
// checking for collisions
Rectangle bounds = sprite.getBounds();
int edgeX = bounds.x + bounds.width;
for (int ii = (idx + 1); ii < size; ii++) {
Sprite other = (Sprite)_sprites.get(ii);
Rectangle obounds = other.getBounds();
if (obounds.x > edgeX) {
// since sprites are stored in the list sorted by
// ascending x-position, we know this sprite and any
// other sprites farther on in the list can't possibly
// intersect with the sprite we're checking, so we're
// done.
return;
} else if (obounds.intersects(bounds)) {
sprite.notifyObservers(new CollisionEvent(sprite, other));
}
}
}
/**
* Notify all sprite observers of any sprite events that took place
* during our most recent <code>tick()</code>.
*/
protected void handleSpriteEvents ()
{
int size = _notify.size();
for (int ii = 0; ii < size; ii++) {
Tuple tup = (Tuple)_notify.remove(0);
ArrayList observers = (ArrayList)tup.left;
SpriteEvent evt = (SpriteEvent)tup.right;
int osize = observers.size();
for (int jj = 0; jj < osize; jj++) {
SpriteObserver obs = (SpriteObserver)observers.get(jj);
obs.handleEvent(evt);
}
}
}
/**
* Called by {@link Sprite#notifyObservers} to note that the
* sprite's observers should be informed of a sprite event once
* the current <code>tick()</code> is complete.
*
* @param observers the list of sprite observers.
* @param event the sprite event to notify the observers of.
*/
protected void notifySpriteObservers (ArrayList observers,
SpriteEvent event)
{
// throw it on the list of events for later
_notify.add(new Tuple(observers, event));
}
/** The sprite objects we're managing. */
protected SortableArrayList _sprites;
/** 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)
{
Sprite s1 = (Sprite)o1;
Sprite s2 = (Sprite)o2;
return (s2.getX() - s1.getX());
}
public boolean equals (Object obj)
{
return (obj == this);
}
}
// NOTE- collision handling code is turned off for now. To re-implement,
// a new array should be kept with sprites sorted in some sort of x/y order
//
// /**
// * Check all sprites for collisions with others and inform any
// * sprite observers.
// */
// protected void handleCollisions ()
// {
// // gather a list of all sprite collisions
// int size = _sprites.size();
// for (int ii = 0; ii < size; ii++) {
// Sprite sprite = (Sprite)_sprites.get(ii);
// checkCollisions(ii, size, sprite);
// }
// }
//
// /**
// * Check a sprite for collision with any other sprites in the
// * sprite list and notify the sprite observers associated with any
// * sprites that do indeed collide.
// *
// * @param idx the starting sprite index.
// * @param size the total number of sprites.
// * @param sprite the sprite to check against other sprites for
// * collisions.
// */
// protected void checkCollisions (int idx, int size, Sprite sprite)
// {
// // TODO: make this handle quickly moving objects that may pass
// // through each other.
//
// // if we're the last sprite we know we've already handled any
// // collisions
// if (idx == (size - 1)) {
// return;
// }
//
// // calculate the x-position of the right edge of the sprite we're
// // checking for collisions
// Rectangle bounds = sprite.getBounds();
// int edgeX = bounds.x + bounds.width;
//
// for (int ii = (idx + 1); ii < size; ii++) {
// Sprite other = (Sprite)_sprites.get(ii);
// Rectangle obounds = other.getBounds();
// if (obounds.x > edgeX) {
// // since sprites are stored in the list sorted by
// // ascending x-position, we know this sprite and any
// // other sprites farther on in the list can't possibly
// // intersect with the sprite we're checking, so we're
// // done.
// return;
//
// } else if (obounds.intersects(bounds)) {
// sprite.notifyObservers(new CollisionEvent(sprite, other));
// }
// }
// }
// /** 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)
// {
// Sprite s1 = (Sprite)o1;
// Sprite s2 = (Sprite)o2;
// return (s2.getX() - s1.getX());
// }
// }
}
@@ -1,5 +1,5 @@
//
// $Id: SceneViewPanel.java,v 1.44 2002/09/24 07:56:25 shaper Exp $
// $Id: SceneViewPanel.java,v 1.45 2002/10/08 21:03:37 ray Exp $
package com.threerings.miso.scene;
@@ -169,7 +169,7 @@ public class SceneViewPanel extends VirtualMediaPanel
*/
protected void paintBits (Graphics2D gfx, int layer, Rectangle dirty)
{
_animmgr.renderAnimations(gfx, layer, dirty);
_animmgr.renderMedia(gfx, layer, dirty);
}
/**