diff --git a/src/java/com/threerings/media/AbstractMedia.java b/src/java/com/threerings/media/AbstractMedia.java new file mode 100644 index 000000000..92236153e --- /dev/null +++ b/src/java/com/threerings/media/AbstractMedia.java @@ -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. + * + *

This must be set before 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 super.init(). + */ + 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 super.shutdown(). + */ + 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 super.toString()) 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; +} diff --git a/src/java/com/threerings/media/AbstractMediaManager.java b/src/java/com/threerings/media/AbstractMediaManager.java new file mode 100644 index 000000000..5b1255077 --- /dev/null +++ b/src/java/com/threerings/media/AbstractMediaManager.java @@ -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(); + } + } + }; +} diff --git a/src/java/com/threerings/media/MediaPanel.java b/src/java/com/threerings/media/MediaPanel.java index 3ac01b761..1a124cc9e 100644 --- a/src/java/com/threerings/media/MediaPanel.java +++ b/src/java/com/threerings/media/MediaPanel.java @@ -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. */ diff --git a/src/java/com/threerings/media/animation/Animation.java b/src/java/com/threerings/media/animation/Animation.java index 1275e57c6..313186ef7 100644 --- a/src/java/com/threerings/media/animation/Animation.java +++ b/src/java/com/threerings/media/animation/Animation.java @@ -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. - * - *

This must be set before 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 super.toString()) 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; } diff --git a/src/java/com/threerings/media/animation/AnimationManager.java b/src/java/com/threerings/media/animation/AnimationManager.java index 9bc5b84d0..ab6b227af 100644 --- a/src/java/com/threerings/media/animation/AnimationManager.java +++ b/src/java/com/threerings/media/animation/AnimationManager.java @@ -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; - } - }; } diff --git a/src/java/com/threerings/media/sprite/ImageSprite.java b/src/java/com/threerings/media/sprite/ImageSprite.java index 779a46b47..0dfb60291 100644 --- a/src/java/com/threerings/media/sprite/ImageSprite.java +++ b/src/java/com/threerings/media/sprite/ImageSprite.java @@ -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); } } diff --git a/src/java/com/threerings/media/sprite/LabelSprite.java b/src/java/com/threerings/media/sprite/LabelSprite.java index 722406f57..274bed5ef 100644 --- a/src/java/com/threerings/media/sprite/LabelSprite.java +++ b/src/java/com/threerings/media/sprite/LabelSprite.java @@ -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(); diff --git a/src/java/com/threerings/media/sprite/Sprite.java b/src/java/com/threerings/media/sprite/Sprite.java index e811ace5c..e87ac0896 100644 --- a/src/java/com/threerings/media/sprite/Sprite.java +++ b/src/java/com/threerings/media/sprite/Sprite.java @@ -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 super.shutdown(). - */ - 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: NORTH, NORTHEAST, 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 super.toString()) 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; } diff --git a/src/java/com/threerings/media/sprite/SpriteManager.java b/src/java/com/threerings/media/sprite/SpriteManager.java index d588c4c52..6137ad86d 100644 --- a/src/java/com/threerings/media/sprite/SpriteManager.java +++ b/src/java/com/threerings/media/sprite/SpriteManager.java @@ -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 tick(). - */ - 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 tick() 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()); +// } +// } } diff --git a/src/java/com/threerings/miso/client/SceneViewPanel.java b/src/java/com/threerings/miso/client/SceneViewPanel.java index 91ef63827..594f04854 100644 --- a/src/java/com/threerings/miso/client/SceneViewPanel.java +++ b/src/java/com/threerings/miso/client/SceneViewPanel.java @@ -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); } /**