diff --git a/src/java/com/threerings/media/FrameManager.java b/src/java/com/threerings/media/FrameManager.java index 614120091..bab8dee8a 100644 --- a/src/java/com/threerings/media/FrameManager.java +++ b/src/java/com/threerings/media/FrameManager.java @@ -1,5 +1,5 @@ // -// $Id: FrameManager.java,v 1.10 2002/06/11 00:52:37 mdb Exp $ +// $Id: FrameManager.java,v 1.11 2002/06/18 22:25:33 mdb Exp $ package com.threerings.media; @@ -511,8 +511,12 @@ public class FrameManager } try { - // render this participant - _g.setClip(_bounds); + // render this participant; we don't set the clip because + // frame participants are expected to handle clipping + // themselves; otherwise we might pointlessly set the clip + // here, creating a few Rectangle objects in the process, + // only to have the frame participant immediately set the + // clip to something more sensible _g.translate(_bounds.x, _bounds.y); pcomp.paint(_g); _g.translate(-_bounds.x, -_bounds.y); diff --git a/src/java/com/threerings/media/FrameParticipant.java b/src/java/com/threerings/media/FrameParticipant.java index 18d058683..c007b22a6 100644 --- a/src/java/com/threerings/media/FrameParticipant.java +++ b/src/java/com/threerings/media/FrameParticipant.java @@ -1,5 +1,5 @@ // -// $Id: FrameParticipant.java,v 1.1 2002/04/23 01:16:27 mdb Exp $ +// $Id: FrameParticipant.java,v 1.2 2002/06/18 22:25:33 mdb Exp $ package com.threerings.media; @@ -26,9 +26,14 @@ public interface FrameParticipant * is hijacked when using the frame manager such that we take care of * repainting dirty Swing components every frame into our off-screen * buffer), it can return a component here which will have {@link - * Component#paint} called on it once per frame with a properly - * configured graphics object. If a particpant does not wish to be - * actively rendered, it can safely return null. + * Component#paint} called on it once per frame with a translated but + * unclipped graphics object. + * + *
Because clipping is expensive in terms of rectangle object + * allocation, frame participants are given the opportunity to do + * their own clipping because they are likely to want to clip to a + * more fine grained region than their entire bounds. If a particpant + * does not wish to be actively rendered, it can safely return null. */ public Component getComponent (); } diff --git a/src/java/com/threerings/media/MediaPanel.java b/src/java/com/threerings/media/MediaPanel.java index 6eeebbfb1..44c4d8db0 100644 --- a/src/java/com/threerings/media/MediaPanel.java +++ b/src/java/com/threerings/media/MediaPanel.java @@ -1,33 +1,21 @@ // -// $Id: MediaPanel.java,v 1.13 2002/06/12 00:51:24 mdb Exp $ +// $Id: MediaPanel.java,v 1.14 2002/06/18 22:25:33 mdb Exp $ package com.threerings.media; import java.awt.Component; -import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Graphics; -import java.awt.Point; import java.awt.Rectangle; import java.awt.Shape; import javax.swing.JComponent; import javax.swing.RepaintManager; -import javax.swing.SwingUtilities; import javax.swing.event.AncestorEvent; -import java.util.ArrayList; -import java.util.List; - import com.samskivert.swing.event.AncestorAdapter; import com.samskivert.util.StringUtil; -import com.threerings.media.FrameManager; -import com.threerings.media.Log; - -import com.threerings.media.util.Path; -import com.threerings.media.util.Pathable; - import com.threerings.media.animation.Animation; import com.threerings.media.animation.AnimationManager; @@ -60,7 +48,7 @@ public class MediaPanel extends JComponent implements FrameParticipant, MediaConstants { /** - * Constructs an animated panel. + * Constructs a media panel. */ public MediaPanel (FrameManager framemgr) { @@ -88,94 +76,6 @@ public class MediaPanel extends JComponent }); } - /** - * 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; - -// Log.info("Scrolling [dx=" + dx + ", dy=" + dy + -// ", millis=" + millis + "ms."); - } - - /** - * Instructs the view to allow the supplied path to "scroll" it such - * that the center point of the view follows the supplied path. The - * view will initially report its position as being in the center of - * the view and will update those coordinates as it is scrolled by the - * path. - */ - public void setPath (Path path) - { - _pathable = createPathable(); - _path = path; - _path.init(_pathable, System.currentTimeMillis()); - } - - /** - * Creates a pathable that will be used to scroll the view along a - * path. - */ - protected Pathable createPathable () - { - return new PanelPathable(); - } - - /** - * If this media panel is following a path and that path attempts to - * set the orientation of the pathable it is controlling, this method - * will be called. Derived classes can override the method and effect - * whatever display of the panel's orientation they might desire. - */ - protected void setOrientation (int orient) - { - } - - /** - * When the media panel is made to follow a path, this method is - * called when the path begins. - */ - protected void pathBeginning () - { - } - - /** - * When the media panel is made to follow a path, this method is - * called when the path ends. {@link #viewFinishedScrolling} will - * still be called (and is in fact called by this method). - */ - protected void pathCompleted () - { - // we're all done; clear out our business - _path = null; - _pathable = null; - - // call our standard callback - viewFinishedScrolling(); - } - /** * Pauses the sprites and animations that are currently active on this * media panel. Also stops listening to the frame tick while paused. @@ -201,30 +101,11 @@ public class MediaPanel extends JComponent _animmgr.fastForward(delta); _spritemgr.fastForward(delta); - // fast forward our path as well (if we have one) - if (_path != null) { - _path.fastForward(delta); - } - // clear out our pause time _pauseTime = 0; } } - /** - * Configures a region of interest which will be displayed in the - * center of the viewport in situations where the media panel's actual - * size is smaller than its view size. - * - * @param region the region of interest or null if there is to be no - * region of interest (in which case the view will simply be - * centered). - */ - public void setRegionOfInterest (Rectangle region) - { - _interest = region; - } - /** * Returns a reference to the region manager that is used to * accumulate dirty regions each frame. @@ -266,53 +147,16 @@ public class MediaPanel extends JComponent _animmgr.unregisterAnimation(anim); } - // documentation inherited - public void doLayout () - { - super.doLayout(); - - // figure out our viewport offsets - Dimension size = getSize(), vsize = getViewSize(); - - // we need to compute one dimension at a time to make things work; - // if we have a region of interest (and our actual size is smaller - // than our view size), use that to compute our offset - if (_interest != null && size.width < vsize.width) { - _tx = centerWithInterest(size.width, vsize.width, - _interest.x, _interest.width); - } else { - // otherwise just center the display - _tx = (vsize.width - size.width)/2; - } - if (_interest != null && size.height < vsize.height) { - _ty = centerWithInterest(size.height, vsize.height, - _interest.y, _interest.height); - } else { - _ty = (vsize.height - size.height)/2; - } - -// Log.info("Size: " + size + ", vsize: " + vsize + -// ", tx: " + _tx + ", ty: " + _ty + "."); - } - - /** - * Used to compute our viewport offsets. - */ - protected int centerWithInterest (int len, int vlen, int ix, int ilen) - { - // start out by centering on the region of interest - int tx = (ix - (len - ilen)/2); - // make sure that didn't push us off of the screen - return Math.min(Math.max(tx, 0), vlen-len); - } - // documentation inherited public void invalidate () { super.invalidate(); // invalidate our bounds with the region manager - _remgr.invalidateRegion(_tx, _ty, getWidth(), getHeight()); + int width = getWidth(), height = getHeight(); + if (width > 0 && height > 0) { + dirtyScreenRect(new Rectangle(0, 0, width, height)); + } } // documentation inherited from interface @@ -323,124 +167,39 @@ public class MediaPanel extends JComponent return; } - int width = getWidth(), height = getHeight(); - _dx = 0; _dy = 0; - - // figure out if we should be scrolling and by how much - computeScrollDeltas(tickStamp); - - // and add invalid rectangles for the exposed areas - if (_dx > 0) { - _remgr.invalidateRegion(width - _dx + _tx, _ty, _dx, height); - } else if (_dx < 0) { - _remgr.invalidateRegion(_tx, _ty, -_dx, height); - } - if (_dy > 0) { - _remgr.invalidateRegion(_tx, height - _dy + _ty, width, _dy); - } else if (_dy < 0) { - _remgr.invalidateRegion(_tx, _ty, width, -_dy); - } - - // make sure we're really scrolling before telling people about it - if (_dx != 0 || _dy != 0) { - // if we are working with a sprite manager, let it know - // that we're about to scroll out from under its sprites - // and allow it to provide us with more dirty rects - if (_spritemgr != null) { - _spritemgr.viewWillScroll(_dx, _dy); - } - - // let our derived classes do whatever they need to do to - // prepare to be scrolled - viewWillScroll(_dx, _dy, tickStamp); - - // keep track of the last time we scrolled - _last = tickStamp; - - // if we've reached our desired position, finish the job; if - // we're being scrolled by a path, the path will let us know - // when we're done (via pathCompleted()) and that will trigger - // a call to viewFinishedScrolling() - if (_ttime != 0 && _scrollx == 0 && _scrolly == 0) { - _ttime = 0; - viewFinishedScrolling(); - } - } + // let derived classes do their business + willTick(tickStamp); // now tick our animations and sprites _animmgr.tick(tickStamp); _spritemgr.tick(tickStamp); + // let derived classes do their business + didTick(tickStamp); + // make a note that the next paint will correspond to a call to // tick() _tickPaintPending = true; } /** - * Called at the beginning of each tick, this method determines - * whether or not the view should be scrolled because of standard - * methods. These methods include a call to {@link #setScrolling} or a - * call to {@link #setPath} which causes the view to follow a path. A - * derived class could override this method to compute custom scroll - * deltas, which it would supply via a call to {@link - * #setScrollDeltas} at some point during the execution of the - * overridden method. + * Derived classes can override this method and perform computation + * prior to the ticking of the sprite and animation managers. */ - protected void computeScrollDeltas (long tickStamp) + protected void willTick (long tickStamp) { - // if scrolling is enabled, determine the scrolling delta to be - // used and do the business - if (_ttime != 0) { - // if we've blown past our allotted time, we want to scroll - // the rest of the way - if (tickStamp > _ttime) { - setScrollDeltas(_scrollx, _scrolly); - -// Log.info("Scrolling rest [dx=" + _dx + ", dy=" + _dy + "]."); - - } else { - // otherwise figure out how many milliseconds have gone by - // since we last scrolled and scroll the requisite amount - float dt = (float)(tickStamp - _last); - float rt = (float)(_ttime - _last); - - // our delta is the remaining distance multiplied by the - // time delta divided by the remaining time - setScrollDeltas(Math.round((float)(_scrollx * dt) / rt), - Math.round((float)(_scrolly * dt) / rt)); - -// Log.info("Scrolling delta [dt=" + dt + ", rt=" + rt + -// ", dx=" + _dx + ", dy=" + _dy + -// ", leftx=" + (_scrollx-_dx) + -// ", lefty=" + (_scrolly-_dy) + "]."); - } - - // subtract our scrolled deltas from the distance remaining - _scrollx -= _dx; - _scrolly -= _dy; - - // otherwise, if we're following a path, give that a chance to - // scroll us along - } else if (_path != null) { - // this will update our _dx and _dy if the path wishes us to - // scroll - _path.tick(_pathable, tickStamp); - } } /** - * This can be called by derived classes during the beginning of the - * tick to instruct the view to scroll by the specified amount on the - * tick in question. + * Derived classes can override this method and perform computation + * subsequent to the ticking of the sprite and animation managers. */ - protected void setScrollDeltas (int dx, int dy) + protected void didTick (long tickStamp) { - _dx = dx; - _dy = dy; } /** - * We want to be painted every tick. + * Returns this component, as we want to be painted with the tick. */ public Component getComponent () { @@ -450,14 +209,15 @@ public class MediaPanel extends JComponent // documentation inherited public void repaint (long tm, int x, int y, int width, int height) { - _remgr.invalidateRegion(_tx + x, _ty + y, width, height); + if (width > 0 && height > 0) { + dirtyScreenRect(new Rectangle(x, y, width, height)); + } } // documentation inherited public void paint (Graphics g) { Graphics2D gfx = (Graphics2D)g; - int width = getWidth(), height = getHeight(); // no use in painting if we're not showing or if we've not yet // been validated @@ -469,218 +229,134 @@ public class MediaPanel extends JComponent // rectangle and mark it as dirty if (!_tickPaintPending) { Shape clip = g.getClip(); - Rectangle dirty = (clip == null) ? getBounds() : clip.getBounds(); - dirty.translate(_tx, _ty); - _remgr.invalidateRegion(dirty); + if (clip == null) { + // mark the whole component as invalid + invalidate(); + } else { + dirtyScreenRect(clip.getBounds()); + } + return; } else { _tickPaintPending = false; } - // if we didn't scroll and have no invalid rects, there's no need - // to repaint anything - if (!_remgr.haveDirtyRegions() && _dx == 0 && _dy == 0) { + // if we have no invalid rects, there's no need to repaint + if (!_remgr.haveDirtyRegions()) { return; } - // we may need to do some scrolling - if (_dx != 0 || _dy != 0) { - gfx.copyArea(0, 0, width, height, -_dx, -_dy); - } - - // get our dirty rectangles - Rectangle[] dirty = _remgr.getDirtyRegions(); - int dcount = dirty.length; - - // clip the dirty regions to the viewport - for (int ii = 0; ii < dcount; ii++) { - SwingUtilities.computeIntersection( - _tx, _ty, width, height, dirty[ii]); - } - - // translate into happy space - gfx.translate(-_tx, -_ty); - - // paint the behind the scenes stuff - paintBehind(gfx, dirty); - - // paint back sprites and animations - for (int ii = 0; ii < dcount; ii++) { - paintBits(gfx, AnimationManager.BACK, dirty[ii]); - } - - // paint the between the scenes stuff - paintBetween(gfx, dirty); - - // paint front sprites and animations - for (int ii = 0; ii < dcount; ii++) { - paintBits(gfx, AnimationManager.FRONT, dirty[ii]); - } - - // paint anything in front - paintInFront(gfx, dirty); - -// if (_path != null) { -// Dimension vsize = getViewSize(); -// int ox = _pathable.getX() - vsize.width/2; -// int oy = _pathable.getY() - vsize.height/2; -// gfx.translate(-ox, -oy); -// _path.paint(gfx); -// gfx.translate(ox, oy); -// } - - // translate back out of happy space - gfx.translate(_tx, _ty); + // get our dirty rectangles and delegate the main painting to a + // method that can be more easily overridden + paint(gfx, _remgr.getDirtyRegions()); } /** - * Paints behind all sprites and animations. The supplied list of - * invalid rectangles should be redrawn in the supplied graphics - * context. The rectangles will be in the view coordinate system - * (which may differ from screen coordinates, see {@link - * #getViewSize}. Sub-classes should override this method to do the - * actual rendering for their display. + * Performs the actual painting of the media panel. Derived methods + * can override this method if they wish to perform pre- and/or + * post-paint activities or if they wish to provide their own painting + * mechanism entirely. */ - protected void paintBehind (Graphics2D gfx, Rectangle[] dirtyRects) + protected void paint (Graphics2D gfx, Rectangle[] dirty) + { + int dcount = dirty.length; + + for (int ii = 0; ii < dcount; ii++) { + Rectangle clip = dirty[ii]; + // ignore rectangles that were reduced to nothingness + if (clip.width == 0 || clip.height == 0) { + continue; + } + clipToDirtyRegion(gfx, clip); + + // paint the behind the scenes stuff + paintBehind(gfx, clip); + + // paint back sprites and animations + paintBits(gfx, AnimationManager.BACK, clip); + + // paint the between the scenes stuff + paintBetween(gfx, clip); + + // paint front sprites and animations + paintBits(gfx, AnimationManager.FRONT, clip); + + // paint anything in front + paintInFront(gfx, clip); + } + } + + /** + * This is called to clip the rendering output to the supplied dirty + * region. This should use {@link Graphics#setClip} because the + * clipping region will need to be replaced as we iterate through our + * dirty regions. By default, a region is assumed to represent screen + * coordinates, but if a derived class wishes to maintain dirty + * regions in non-screen coordinates, it can override this method to + * properly clip to the dirty region. + */ + protected void clipToDirtyRegion (Graphics2D gfx, Rectangle dirty) + { +// Log.info("MP: Clipping to [clip=" + StringUtil.toString(dirty) + "]."); + gfx.setClip(dirty); + } + + /** + * Called to mark the specified rectangle (in screen coordinates) as + * dirty. The rectangle will be bent, folded and mutilated, so be sure + * you're not passing a rectangle into this method that is being used + * elsewhere. + * + *
If derived classes wish to convert from screen coordinates to + * some virtual coordinate system to be used by their repaint manager, + * this is the place to do it. + */ + protected void dirtyScreenRect (Rectangle rect) + { + _remgr.addDirtyRegion(rect); + } + + /** + * Paints behind all sprites and animations. The supplied invalid + * rectangle should be redrawn in the supplied graphics context. + * Sub-classes should override this method to do the actual rendering + * for their display. + */ + protected void paintBehind (Graphics2D gfx, Rectangle dirtyRect) { } /** * Paints between the front and back layer of sprites and animations. - * The supplied list of invalid rectangles should be redrawn in the - * supplied graphics context. The rectangles will be in the view - * coordinate system (which may differ from screen coordinates, see - * {@link #getViewSize}. Sub-classes should override this method to do - * the actual rendering for their display. + * The supplied invalid rectangle should be redrawn in the supplied + * graphics context. Sub-classes should override this method to do the + * actual rendering for their display. */ - protected void paintBetween (Graphics2D gfx, Rectangle[] dirtyRects) + protected void paintBetween (Graphics2D gfx, Rectangle dirtyRect) { } /** - * Paints in front of all sprites and animations. The supplied list of - * invalid rectangles should be redrawn in the supplied graphics - * context. The rectangles will be in the view coordinate system - * (which may differ from screen coordinates, see {@link - * #getViewSize}. Sub-classes should override this method to do the - * actual rendering for their display. + * Paints in front of all sprites and animations. The supplied invalid + * rectangle should be redrawn in the supplied graphics context. + * Sub-classes should override this method to do the actual rendering + * for their display. */ - protected void paintInFront (Graphics2D gfx, Rectangle[] dirtyRects) + protected void paintInFront (Graphics2D gfx, Rectangle dirtyRect) { } /** * Renders the sprites and animations that intersect the supplied - * clipping region in the specified layer. Derived classes can - * override this method if they need to do custom sprite or animation - * rendering (if they need to do special sprite z-order handling, for - * example). This method also takes care of setting the clipping - * region in the graphics object which an overridden method should - * also do to preserve performance. + * dirty region in the specified layer. Derived classes can override + * this method if they need to do custom sprite or animation rendering + * (if they need to do special sprite z-order handling, for example). + * The clipping region will already be set appropriately. */ - protected void paintBits (Graphics2D gfx, int layer, Rectangle clip) + protected void paintBits (Graphics2D gfx, int layer, Rectangle dirty) { - Shape oclip = gfx.getClip(); - gfx.clipRect(clip.x, clip.y, clip.width, clip.height); - _animmgr.renderAnimations(gfx, layer, clip); - _spritemgr.renderSprites(gfx, layer, clip); - gfx.setClip(oclip); - } - - /** - * This is called, when the view is scrolling, during the tick - * processing phase. The animated panel will take care of scrolling - * the contents of the offscreen buffer when the time comes to render, - * but the derived class will need to do whatever is necessary to - * prepare to repaint the exposed regions as well as update its own - * internal state accordingly. - * - * @param dx the distance (in pixels) that the view will scroll in the - * x direction. - * @param dy the distance (in pixels) that the view will scroll in the - * y direction. - * @param now the current time, provided because we have it and - * scrolling views are likely to want to use it in calculating stuff. - */ - protected void viewWillScroll (int dx, int dy, long tickStamp) - { - // if we have a path, let it know that we're scrolling - if (_path != null) { - _path.viewWillScroll(dx, dy); - } - } - - /** - * 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 the paint methods. - */ - protected Dimension getViewSize () - { - return getSize(); - } - - /** - * Used when causing the view to follow a path. - */ - protected class PanelPathable implements Pathable - { - public PanelPathable () - { - Dimension vsize = getViewSize(); - _porigin = new Point(vsize.width/2, vsize.height/2); - } - - // documentation inherited from interface - public int getX () { - return _porigin.x; - } - - // documentation inherited from interface - public int getY () { - return _porigin.y; - } - - // documentation inherited from interface - public void setLocation (int x, int y) { - // make a note of the scrolling that we'll need to do to - // actually follow this path - setScrollDeltas(x - _porigin.x, y - _porigin.y); - - // update our origin - _porigin.x = x; - _porigin.y = y; - } - - // documentation inherited from interface - public void setOrientation (int orient) { - MediaPanel.this.setOrientation(orient); - } - - // documentation inherited from interface - public void pathBeginning () { - MediaPanel.this.pathBeginning(); - } - - // documentation inherited from interface - public void pathCompleted () { - MediaPanel.this.pathCompleted(); - } - - protected Point _porigin; + _animmgr.renderAnimations(gfx, layer, dirty); + _spritemgr.renderSprites(gfx, layer, dirty); } /** The frame manager with whom we register. */ @@ -695,30 +371,6 @@ public class MediaPanel extends JComponent /** Used to accumulate and merge dirty regions on each tick. */ protected RegionManager _remgr; - /** A region of interest which we'll try to keep visible. */ - protected Rectangle _interest; - - /** Our viewport offsets. */ - protected int _tx, _ty; - - /** How many pixels we have left to scroll. */ - protected int _scrollx, _scrolly; - - /** Our scroll offsets for this frame tick. */ - protected int _dx, _dy; - - /** The time at which we expect to stop scrolling. */ - protected long _ttime; - - /** The last time we were scrolled. */ - protected long _last; - - /** The path we're following. */ - protected Path _path; - - /** The pathable we use to follow the path. */ - protected Pathable _pathable; - /** Used to correlate tick()s with paint()s. */ protected boolean _tickPaintPending = false; diff --git a/src/java/com/threerings/media/RegionManager.java b/src/java/com/threerings/media/RegionManager.java index 7492df40d..1d346dca2 100644 --- a/src/java/com/threerings/media/RegionManager.java +++ b/src/java/com/threerings/media/RegionManager.java @@ -1,5 +1,5 @@ // -// $Id: RegionManager.java,v 1.2 2002/06/11 00:52:37 mdb Exp $ +// $Id: RegionManager.java,v 1.3 2002/06/18 22:25:33 mdb Exp $ package com.threerings.media; @@ -8,6 +8,8 @@ import java.awt.Rectangle; import java.util.ArrayList; import java.util.List; +import com.samskivert.util.StringUtil; + /** * Manages regions (rectangles) that are invalidated in the process of * ticking animations and sprites and generally doing other display @@ -20,7 +22,9 @@ public class RegionManager */ public void invalidateRegion (int x, int y, int width, int height) { - _dirty.add(new Rectangle(x, y, width, height)); + if (width != 0 && height != 0) { + addDirtyRegion(new Rectangle(x, y, width, height)); + } } /** @@ -30,7 +34,9 @@ public class RegionManager */ public void invalidateRegion (Rectangle rect) { - _dirty.add((Rectangle)rect.clone()); + if (rect.width != 0 && rect.height != 0) { + addDirtyRegion((Rectangle)rect.clone()); + } } /** @@ -41,7 +47,10 @@ public class RegionManager */ public void addDirtyRegion (Rectangle rect) { - _dirty.add(rect); + if (rect.width != 0 && rect.height != 0) { +// Log.info("Invalidating " + StringUtil.toString(rect)); + _dirty.add(rect); + } } /** diff --git a/src/java/com/threerings/media/VirtualMediaPanel.java b/src/java/com/threerings/media/VirtualMediaPanel.java new file mode 100644 index 000000000..d1c401f2f --- /dev/null +++ b/src/java/com/threerings/media/VirtualMediaPanel.java @@ -0,0 +1,219 @@ +// +// $Id: VirtualMediaPanel.java,v 1.1 2002/06/18 22:25:33 mdb Exp $ + +package com.threerings.media; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.event.MouseEvent; + +import javax.swing.SwingUtilities; + +import com.samskivert.util.StringUtil; + +import com.threerings.media.util.Path; +import com.threerings.media.util.Pathable; + +/** + * Extends the base media panel with the notion of a virtual coordinate + * system. All entities in the virtual media panel have virtual + * coordinates and the virtual media panel displays a window into that + * virtual view. The panel can be made to scroll by adjusting the view + * offset slightly at the start of each tick and it will efficiently copy + * the unmodified view data and generate repaint requests for the exposed + * regions. + */ +public class VirtualMediaPanel extends MediaPanel +{ + /** + * Constructs a virtual media panel. + */ + public VirtualMediaPanel (FrameManager framemgr) + { + super(framemgr); + } + + /** + * Sets the upper-left coordinate of the view port in virtual + * coordinates. The view will be as efficient as possible about + * repainting itself to achieve this new virtual location (meaning + * that if we need only to move one pixel to the left, it will use + * {@link Graphics#copyArea} to move our rendered view over one pixel + * and generate a dirty region for the exposed area). The new location + * will not take effect until the view is {@link #tick}ed, so only the + * last call to this method during a tick will have any effect. + */ + public void setViewLocation (int x, int y) + { + // make a note of our new x and y offsets + _nx = x; + _ny = y; + } + + /** + * Instructs the view to follow the supplied pathable; ensuring that + * the pathable always remains in the center of the view. The virtual + * coordinates will be adjusted after every tick to center the view on + * the sprite. + */ + public void setFollowsPathable (Pathable pable) + { + _fpath = pable; + } + + /** + * Configures a region of interest which will be displayed in the + * center of the viewport in situations where the media panel's actual + * size is smaller than its view size. + * + * @param region the region of interest or null if there is to be no + * region of interest (in which case the view will simply be + * centered). + */ + public void setRegionOfInterest (Rectangle region) + { + _interest = region; + } + + /** + * We overload this to translate mouse events into the proper + * coordinates before they are dispatched to any of the mouse + * listeners. + */ + protected void processMouseEvent (MouseEvent event) + { + event.translatePoint(_tx, _ty); + super.processMouseEvent(event); + } + + /** + * We overload this to translate mouse events into the proper + * coordinates before they are dispatched to any of the mouse + * listeners. + */ + protected void processMouseMotionEvent (MouseEvent event) + { + event.translatePoint(_tx, _ty); + super.processMouseMotionEvent(event); + } + + // documentation inherited + protected void dirtyScreenRect (Rectangle rect) + { + // translate the screen rect into happy coordinates + rect.translate(_tx, _ty); + _remgr.addDirtyRegion(rect); + } + + // documentation inherited + protected void didTick (long tickStamp) + { + super.didTick(tickStamp); + + int width = getWidth(), height = getHeight(); + + // if we're following a pathable, adjust our view coordinates such + // that the pathable is at the center of the view + if (_fpath != null) { + setViewLocation(_fpath.getX() - width/2, _fpath.getY() - height/2); + } + + // if we have a new target location, we'll need to generate dirty + // regions for the area exposed by the scrolling + if (_nx != _tx || _ny != _ty) { + // determine how far we'll be moving on this tick + _dx = _nx - _tx; _dy = _ny - _ty; + + // these are used to prevent the vertical strip from + // overlapping the horizontal strip + int sy = _ny, shei = height; + + // and add invalid rectangles for the exposed areas + if (_dy > 0) { + shei -= _dy; + _remgr.invalidateRegion(_nx, _ny + height - _dy, width, _dy); + } else if (_dy < 0) { + sy -= _dy; + _remgr.invalidateRegion(_nx, _ny, width, -_dy); + } + if (_dx > 0) { + _remgr.invalidateRegion(_nx + width - _dx, sy, _dx, shei); + } else if (_dx < 0) { + _remgr.invalidateRegion(_nx, sy, -_dx, shei); + } + + // now go ahead and update our location so that changes in + // between here and the call to paint() for this tick don't + // booch everything + _tx = _nx; _ty = _ny; + +// Log.info("Scrolling into place " + +// "[dx=" + _dx + ", dy=" + _dy + "]."); + } + } + + // documentation inherited + protected void paint (Graphics2D gfx, Rectangle[] dirty) + { + int width = getWidth(), height = getHeight(); + int dcount = dirty.length; + + // if we're scrolling, go ahead and do the business + if (_dx != 0 || _dy != 0) { + int cx = (_dx > 0) ? _dx : 0; + int cy = (_dy > 0) ? _dy : 0; + gfx.copyArea(cx, cy, width - Math.abs(_dx), + height - Math.abs(_dy), -_dx, -_dy); + // and clear out our scroll deltas + _dx = 0; _dy = 0; + } + + // clip the dirty regions to the viewport + for (int ii = 0; ii < dcount; ii++) { + SwingUtilities.computeIntersection( + _tx, _ty, width, height, dirty[ii]); +// Log.info("Intersected [rect=" + StringUtil.toString(dirty[ii]) + +// ", tx=" + _tx + ", ty=" + _ty + "]."); + } + + // translate into happy space + gfx.translate(-_tx, -_ty); + + // now do the actual painting + super.paint(gfx, dirty); + + gfx.setColor(java.awt.Color.white); + gfx.drawOval(_tx, _tx, 10, 10); + + // translate back out of happy space + gfx.translate(_tx, _ty); + } + + /** + * Used to compute our viewport offsets. + */ + protected int centerWithInterest (int len, int vlen, int ix, int ilen) + { + // start out by centering on the region of interest + int tx = (ix - (len - ilen)/2); + // make sure that didn't push us off of the screen + return Math.min(Math.max(tx, 0), vlen-len); + } + + /** Our viewport offsets. */ + protected int _tx, _ty; + + /** Our target offsets to be effected on the next tick. */ + protected int _nx, _ny; + + protected int _dx, _dy; + + /** A region of interest which we'll try to keep visible. */ + protected Rectangle _interest; + + /** The pathable being followed. */ + protected Pathable _fpath; +} diff --git a/src/java/com/threerings/media/sprite/Sprite.java b/src/java/com/threerings/media/sprite/Sprite.java index a769ed589..68c89f611 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.46 2002/06/11 05:56:44 mdb Exp $ +// $Id: Sprite.java,v 1.47 2002/06/18 22:25:33 mdb Exp $ package com.threerings.media.sprite; @@ -44,25 +44,6 @@ public abstract class Sprite _y = y; } - /** - * Returns true if this sprite should scroll along with the view or - * false if it should remain fixed relative to the view when it - * scrolls. - */ - public boolean scrollsWithView () - { - return _scrollsWithView; - } - - /** - * Specifies whether this sprite scrolls when the view scrolls or - * remains fixed relative to the view. - */ - public void setScrollsWithView (boolean scrollsWithView) - { - _scrollsWithView = scrollsWithView; - } - /** * Called by the sprite manager to initialize the sprite when a sprite * is added to said manager for management. @@ -182,8 +163,8 @@ public abstract class Sprite */ public void setLocation (int x, int y) { - // dirty our current bounds - invalidate(); + // start with our current bounds + Rectangle dirty = new Rectangle(_bounds); // move ourselves _x = x; @@ -193,8 +174,19 @@ public abstract class Sprite // size of our current bounds updateRenderOrigin(); - // dirty our new bounds - invalidate(); + // grow the dirty rectangle to incorporate our new bounds and pass + // the dirty region to our region manager + if (_spritemgr != 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); + } + _spritemgr.getRegionManager().addDirtyRegion(dirty); + } } /** @@ -405,47 +397,6 @@ public abstract class Sprite _observers.add(obs); } - /** - * Called by the sprite manager to let us know that the view we occupy - * is scrolling by the specified amount. A sprite anchored to the view - * will simply update its coordinates; a fixed sprite will remain in - * the same position but invalidate its bounds and its scrolled - * bounds. - */ - protected void viewWillScroll (int dx, int dy) - { - if (_scrollsWithView) { - // update our coordinates - _x -= dx; - _y -= dy; - _bounds.x -= dx; - _bounds.y -= dy; - - // and let any path that we're following know what's up - if (_path != null) { - _path.viewWillScroll(dx, dy); - } - - } else { - // if we're not scrolling, we need to dirty our bounds and the - // part that was just scrolled out from under us - Rectangle dirty = new Rectangle(_bounds); - - // expand the rectangle to contain the scrolled regions - int ex = dirty.x - dx, ey = dirty.y - dy; - if (dx < 0) { - ex += dirty.width; - } - if (dy < 0) { - ey += dirty.height; - } - dirty.add(ex, ey); - - // give the dirty rectangle to the region manager - _spritemgr.getRegionManager().addDirtyRegion(dirty); - } - } - /** * Inform all sprite observers of a sprite event. * @@ -497,11 +448,6 @@ public abstract class Sprite /** The orientation of this sprite. */ protected int _orient = NONE; - /** True if we should move along with the view when it scrolls, false - * if we should remain fixed relative to the viewport. We are fixed by - * default. */ - protected boolean _scrollsWithView = false; - /** When moving, the path the sprite is traversing. */ protected Path _path; diff --git a/src/java/com/threerings/media/sprite/SpriteManager.java b/src/java/com/threerings/media/sprite/SpriteManager.java index 512a78eea..1bd7c2964 100644 --- a/src/java/com/threerings/media/sprite/SpriteManager.java +++ b/src/java/com/threerings/media/sprite/SpriteManager.java @@ -1,5 +1,5 @@ // -// $Id: SpriteManager.java,v 1.30 2002/04/25 16:23:31 mdb Exp $ +// $Id: SpriteManager.java,v 1.31 2002/06/18 22:25:33 mdb Exp $ package com.threerings.media.sprite; @@ -38,23 +38,6 @@ public class SpriteManager _remgr = remgr; } - /** - * Lets the sprite manager know that the view is about to scroll by - * the specified offsets. It can update the positions of its sprites - * if they are tracking the scrolled view, or generate dirty regions - * for the sprites that remain in place (meaning they move relative to - * the scrolling view). - */ - public void viewWillScroll (int dx, int dy) - { - // let the sprites know that the view is scrolling - int size = _sprites.size(); - for (int i = 0; i < size; i++) { - Sprite sprite = (Sprite)_sprites.get(i); - sprite.viewWillScroll(dx, dy); - } - } - /** * When an animated view processes its dirty rectangles, it may * require an expansion of the dirty region which may in turn diff --git a/src/java/com/threerings/media/util/LinePath.java b/src/java/com/threerings/media/util/LinePath.java index 0d2ffb604..d518a6ae4 100644 --- a/src/java/com/threerings/media/util/LinePath.java +++ b/src/java/com/threerings/media/util/LinePath.java @@ -1,5 +1,5 @@ // -// $Id: LinePath.java,v 1.8 2002/06/12 01:11:55 mdb Exp $ +// $Id: LinePath.java,v 1.9 2002/06/18 22:25:33 mdb Exp $ package com.threerings.media.util; @@ -46,28 +46,6 @@ public class LinePath implements Path _duration = duration; } - /** - * Instructs this path to adjust itself when the view scrolls, or not. - * If the path scrolls with the view, it will adjust its starting and - * ending positions by the amount scrolled by the view so that they - * remain fixed relative to the scrolled view. If it doesn't they will - * remain the same regardless of whether the view scrolls. - */ - public void setScrollsWithView (boolean scrollsWithView) - { - _scrollsWithView = scrollsWithView; - } - - // documentation inherited from interface - public void viewWillScroll (int dx, int dy) - { - // adjust our source and dest points if we're tracking the view - if (_scrollsWithView) { - _source.translate(-dx, -dy); - _dest.translate(-dx, -dy); - } - } - // documentation inherited public void init (Pathable pable, long timestamp) { @@ -153,9 +131,4 @@ public class LinePath implements Path /** The duration that we're to spend following the path. */ protected long _duration; - - /** Whether or not we scroll along with the view (which we accomplish - * by scrolling our start and end positions when the view scrolls) or - * if we should leave our coordinates alone. */ - protected boolean _scrollsWithView = true; } diff --git a/src/java/com/threerings/media/util/LineSegmentPath.java b/src/java/com/threerings/media/util/LineSegmentPath.java index 53e0b60ef..049d03cff 100644 --- a/src/java/com/threerings/media/util/LineSegmentPath.java +++ b/src/java/com/threerings/media/util/LineSegmentPath.java @@ -1,5 +1,5 @@ // -// $Id: LineSegmentPath.java,v 1.24 2002/06/12 23:33:03 ray Exp $ +// $Id: LineSegmentPath.java,v 1.25 2002/06/18 22:25:33 mdb Exp $ package com.threerings.media.util; @@ -156,18 +156,6 @@ public class LineSegmentPath setVelocity(distance/millis); } - // documentation inherited from interface - public void viewWillScroll (int dx, int dy) - { - // adjust the coordinates of our path nodes - int ncount = _nodes.size(); - for (int ii = 0; ii < ncount; ii++) { - PathNode node = (PathNode)_nodes.get(ii); - node.loc.x -= dx; - node.loc.y -= dy; - } - } - // documentation inherited public void init (Pathable pable, long timestamp) { diff --git a/src/java/com/threerings/media/util/Path.java b/src/java/com/threerings/media/util/Path.java index ffb4253eb..c5447f550 100644 --- a/src/java/com/threerings/media/util/Path.java +++ b/src/java/com/threerings/media/util/Path.java @@ -1,5 +1,5 @@ // -// $Id: Path.java,v 1.7 2002/05/31 03:38:03 mdb Exp $ +// $Id: Path.java,v 1.8 2002/06/18 22:25:33 mdb Exp $ package com.threerings.media.util; @@ -48,13 +48,6 @@ public interface Path */ public void fastForward (long timeDelta); - /** - * Called when the view that contains the pathable following this path - * is scrolling by the specified amount. Gives the path an opportunity - * to adjust its internal coordinates by the scrolled amount. - */ - public void viewWillScroll (int dx, int dy); - /** * Paint this path on the screen (used for debugging purposes only). */