Death, taxes and refactoring: seriously revamped the low-level media code

to handle scrolling in a much cleaner and happier way. There's now a
VirtualMediaPanel which operates in virtual coordinates and can be made to
"view" any location in those virtual coordinates. By changing the view,
the view can be scrolled and the view can be made to center on a pathable
(generally a sprite) which will cause it to scroll around as the pathable
moves around in the virtual coordinate space.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1475 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2002-06-18 22:25:33 +00:00
parent 4b29f806df
commit 7253a68c7c
10 changed files with 387 additions and 615 deletions
@@ -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);
@@ -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.
*
* <p> 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 ();
}
+119 -467
View File
@@ -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.
*
* <p> 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;
@@ -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);
}
}
/**
@@ -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;
}
@@ -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;
@@ -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
@@ -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;
}
@@ -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)
{
+1 -8
View File
@@ -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).
*/