Break SceneBlock and RethinkOp out of MisoScenePanel so the blocks in a visible region can be resolved without an underlying panel

git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@512 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
Charlie Groves
2008-05-28 01:55:15 +00:00
parent 4ec18f5250
commit 2a2d36d528
6 changed files with 283 additions and 190 deletions
@@ -99,15 +99,14 @@ public class MisoScenePanel extends VirtualMediaPanel
super(ctx.getFrameManager());
_ctx = ctx;
_metrics = metrics;
_rethinkOp = new RethinkOp(_metrics);
_applicator = new TileOpApplicator(_metrics);
// set ourselves up
setOpaque(true);
addMouseListener(this);
addMouseMotionListener(this);
// handy rectangle
_tbounds = new Rectangle(0, 0, _metrics.tilewid, _metrics.tilehei);
// create the resolver if it's not already around
if (_resolver == null) {
_resolver = new SceneBlockResolver();
@@ -803,7 +802,7 @@ public class MisoScenePanel extends VirtualMediaPanel
}
// compute the intersecting set of blocks
applyToTiles(_ibounds, _rethinkOp);
_applicator.applyToTiles(_ibounds, _rethinkOp);
// Log.info("Influential blocks " +
// StringUtil.toString(_rethinkOp.blocks) + ".");
@@ -852,34 +851,39 @@ public class MisoScenePanel extends VirtualMediaPanel
log.debug("Rethunk [pending=" + _pendingBlocks + ", visible=" + _visiBlocks.size() + "].");
return _visiBlocks.size();
}
/**
* Called during the {@link #rethink} process, configures {@link
* #_ibounds} to contain the bounds of the potentially "influential"
* world and {@link #_vibounds} to contain bounds that are used to
* determine which blocks should be resolved before making the view
* visible.
*
* <p> Everything that intersects the influential area will be
* resolved on the expectation that it could be scrolled into view at
* any time. The influential bounds should be large enough that the
* time between a block becoming influential and the time at which it
* is resolved is longer than the expected time by which it will be
* scrolled into view, otherwise the users will see the man behind the
* curtain.
* Calls through to {@link #computeInfluentialBounds(Rectangle, Rectangle, Rectangle)} with
* _vbounds, _ibounds and _vibounds.
*/
protected void computeInfluentialBounds ()
{
int infborx = 3*_vbounds.width/4;
int infbory = _vbounds.height/2;
_ibounds.setBounds(_vbounds.x-infborx, _vbounds.y-infbory,
_vbounds.width+2*infborx,
// we go extra on the height because objects
// below can influence fairly high up
_vbounds.height+3*infbory);
_vibounds.setBounds(_vbounds.x-_vbounds.width/4, _vbounds.y,
_vbounds.width+_vbounds.width/2,
_vbounds.height+infbory);
computeInfluentialBounds(_vbounds, _ibounds, _vibounds);
}
/**
* Configures <code>influentialBounds</code> to contain the bounds of the potentially
* "influential" world and <code>visibleBlockBounds</code> to contain bounds that are used to
* determine which blocks should be resolved before making the view visible.
*
* <p> Everything that intersects the influential area will be resolved on the expectation
* that it could be scrolled into view at any time. The influential bounds should be large
* enough that the time between a block becoming influential and the time at which it is
* resolved is longer than the expected time by which it will be scrolled into view, otherwise
* the users will see the man behind the curtain.
*/
public static void computeInfluentialBounds (Rectangle visibleBounds,
Rectangle influentualBounds, Rectangle visibleBlockBounds)
{
int infborx = 3 * visibleBounds.width / 4;
int infbory = visibleBounds.height / 2;
// we go extra on the height because objects below can influence fairly high up
influentualBounds.setBounds(visibleBounds.x - infborx, visibleBounds.y - infbory,
visibleBounds.width + 2 * infborx, visibleBounds.height + 3 * infbory);
visibleBlockBounds.setBounds(visibleBounds.x - visibleBounds.width / 4,
visibleBounds.y, visibleBounds.width + visibleBounds.width / 2,
visibleBounds.height + infbory);
}
/**
@@ -1357,87 +1361,6 @@ public class MisoScenePanel extends VirtualMediaPanel
}
}
/**
* Applies the supplied tile operation to all tiles that intersect the
* supplied screen rectangle.
*/
protected void applyToTiles (Rectangle bounds, TileOp op)
{
// determine which tiles intersect this region: this is going to
// be nearly incomprehensible without some sort of diagram; i'll
// do what i can to comment it, but you'll want to print out a
// scene diagram (docs/miso/scene.ps) and start making notes if
// you want to follow along
// obtain our upper left tile
Point tpos = MisoUtil.screenToTile(_metrics, bounds.x, bounds.y, new Point());
// determine which quadrant of the upper left tile we occupy
Point spos = MisoUtil.tileToScreen(_metrics, tpos.x, tpos.y, new Point());
boolean left = (bounds.x - spos.x < _metrics.tilehwid);
boolean top = (bounds.y - spos.y < _metrics.tilehhei);
// set up our tile position counters
int dx, dy;
if (left) {
dx = 0; dy = 1;
} else {
dx = 1; dy = 0;
}
// if we're in the top-half of the tile we need to move up a row,
// either forward or back depending on whether we're in the left
// or right half of the tile
if (top) {
if (left) {
tpos.x -= 1;
} else {
tpos.y -= 1;
}
// we'll need to start zig-zagging the other way as well
dx = 1 - dx;
dy = 1 - dy;
}
// these will bound our loops
int rightx = bounds.x + bounds.width,
bottomy = bounds.y + bounds.height;
// Log.info("Preparing to apply [tpos=" + StringUtil.toString(tpos) +
// ", left=" + left + ", top=" + top +
// ", bounds=" + StringUtil.toString(bounds) +
// ", spos=" + StringUtil.toString(spos) +
// "].");
// obtain the coordinates of the tile that starts the first row
// and loop through, applying to the intersecting tiles
MisoUtil.tileToScreen(_metrics, tpos.x, tpos.y, spos);
while (spos.y < bottomy) {
// set up our row counters
int tx = tpos.x, ty = tpos.y;
_tbounds.x = spos.x;
_tbounds.y = spos.y;
// Log.info("Applying to row [tx=" + tx + ", ty=" + ty + "].");
// apply to the tiles in this row
while (_tbounds.x < rightx) {
op.apply(tx, ty, _tbounds);
// move one tile to the right
tx += 1; ty -= 1;
_tbounds.x += _metrics.tilewid;
}
// update our tile coordinates
tpos.x += dx; dx = 1-dx;
tpos.y += dy; dy = 1-dy;
// obtain the screen coordinates of the next starting tile
MisoUtil.tileToScreen(_metrics, tpos.x, tpos.y, spos);
}
}
/**
* Renders the base and fringe layer tiles that intersect the
* specified clipping rectangle.
@@ -1446,7 +1369,7 @@ public class MisoScenePanel extends VirtualMediaPanel
{
// go through rendering our tiles
_paintOp.setGraphics(gfx);
applyToTiles(clip, _paintOp);
_applicator.applyToTiles(clip, _paintOp);
_paintOp.setGraphics(null);
}
@@ -1482,26 +1405,18 @@ public class MisoScenePanel extends VirtualMediaPanel
/** Computes the fringe tile for the specified coordinate. */
protected BaseTile computeFringeTile (int tx, int ty)
{
return _ctx.getTileManager().getAutoFringer().getFringeTile(
_model, tx, ty, _masks);
return _ctx.getTileManager().getAutoFringer().getFringeTile(_model, tx, ty, _masks);
}
/**
* Returns true if we're responding to user input. This is used to
* control the display of tooltips and other potential user
* interactions. By default we are always responsive.
* Returns true if we're responding to user input. This is used to control the display of
* tooltips and other potential user interactions. By default we are always responsive.
*/
protected boolean isResponsive ()
{
return true;
}
/** Used with {@link #applyToTiles}. */
protected static interface TileOp
{
public void apply (int tx, int ty, Rectangle tbounds);
}
/** Used by {@link #paintTiles}. */
protected class PaintTileOp implements TileOp
{
@@ -1597,24 +1512,6 @@ public class MisoScenePanel extends VirtualMediaPanel
protected Font _font = new Font("Arial", Font.PLAIN, 7);
}
/** Used by {@link #rethink}. */
protected class RethinkOp implements TileOp
{
public Set<Point> blocks = Sets.newHashSet();
public void apply (int tx, int ty, Rectangle tbounds) {
_key.x = MathUtil.floorDiv(tx, _metrics.blockwid) *
_metrics.blockwid;
_key.y = MathUtil.floorDiv(ty, _metrics.blockhei) *
_metrics.blockhei;
if (!blocks.contains(_key)) {
blocks.add(new Point(_key.x, _key.y));
}
}
protected Point _key = new Point();
}
/** Provides access to a few things. */
protected MisoContext _ctx;
@@ -1638,7 +1535,7 @@ public class MisoScenePanel extends VirtualMediaPanel
protected Rectangle _vibounds = new Rectangle();
/** Used by {@link #rethink}. */
protected RethinkOp _rethinkOp = new RethinkOp();
protected RethinkOp _rethinkOp;
/** Contains our scene blocks. See {@link #getBlock} for details. */
protected HashIntMap<SceneBlock> _blocks = new HashIntMap<SceneBlock>();
@@ -1665,9 +1562,6 @@ public class MisoScenePanel extends VirtualMediaPanel
/** The working sprites list used when calculating dirty regions. */
protected List<Sprite> _dirtySprites = Lists.newArrayList();
/** Used when rendering tiles. */
protected Rectangle _tbounds;
/** Used to paint tiles. */
protected PaintTileOp _paintOp = new PaintTileOp();
@@ -1709,6 +1603,8 @@ public class MisoScenePanel extends VirtualMediaPanel
// used to display debugging information on scene block resolution
protected JFrame _dframe;
protected ResolutionView _dpanel;
protected TileOpApplicator _applicator;
/** A debug hook that toggles debug rendering of traversable tiles. */
protected static RuntimeAdjust.BooleanAdjust _traverseDebug =
@@ -0,0 +1,36 @@
package com.threerings.miso.client;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.Set;
import com.google.common.collect.Sets;
import com.threerings.media.util.MathUtil;
import com.threerings.miso.util.MisoSceneMetrics;
/**
* Constructs the set of blocks that intersect the op bounds.
*/
public class RethinkOp
implements TileOp
{
public Set<Point> blocks = Sets.newHashSet();
public RethinkOp (MisoSceneMetrics metrics)
{
_metrics = metrics;
}
public void apply (int tx, int ty, Rectangle tbounds)
{
_key.x = MathUtil.floorDiv(tx, _metrics.blockwid) * _metrics.blockwid;
_key.y = MathUtil.floorDiv(ty, _metrics.blockhei) * _metrics.blockhei;
if (!blocks.contains(_key)) {
blocks.add(new Point(_key.x, _key.y));
}
}
protected Point _key = new Point();
protected MisoSceneMetrics _metrics;
}
@@ -36,40 +36,46 @@ import com.threerings.geom.GeomUtil;
import com.threerings.media.tile.NoSuchTileSetException;
import com.threerings.media.tile.ObjectTile;
import com.threerings.media.tile.Tile;
import com.threerings.media.tile.TileManager;
import com.threerings.media.tile.TileSet;
import com.threerings.media.tile.TileUtil;
import com.threerings.media.util.MathUtil;
import com.threerings.miso.data.MisoSceneModel;
import com.threerings.miso.data.ObjectInfo;
import com.threerings.miso.tile.BaseTile;
import com.threerings.miso.util.MisoSceneMetrics;
import com.threerings.miso.util.MisoUtil;
import com.threerings.miso.util.ObjectSet;
import static com.threerings.miso.Log.log;
/**
* Contains the base and object tile information on a particular
* rectangular region of a scene.
* Contains the base and object tile information on a particular rectangular region of a scene.
*/
public class SceneBlock
{
/**
* Creates a scene block and resolves the base and object tiles that
* reside therein.
* Creates a scene block belonging to panel in preparation for its later resolution.
*/
public SceneBlock (
MisoScenePanel panel, int tx, int ty, int width, int height)
public SceneBlock (MisoScenePanel panel, int tx, int ty, int width, int height)
{
_panel = panel;
this(panel.getSceneModel(), panel.getSceneMetrics(), panel.getTileManager(), tx, ty,
width, height);
}
public SceneBlock (MisoSceneModel model, MisoSceneMetrics metrics, TileManager tileMgr,
int tx, int ty, int width, int height)
{
_model = model;
_metrics = metrics;
_tileMgr = tileMgr;
_bounds = new Rectangle(tx, ty, width, height);
_base = new BaseTile[width*height];
_fringe = new BaseTile[width*height];
_covered = new boolean[width*height];
// compute our screen-coordinate footprint polygon
_footprint = MisoUtil.getFootprintPolygon(
panel.getSceneMetrics(), tx, ty, width, height);
_footprint = MisoUtil.getFootprintPolygon(_metrics, tx, ty, width, height);
// the rest of our resolution will happen in resolve()
}
@@ -88,15 +94,17 @@ public class SceneBlock
* block resolution thread to allow us to load up our image data
* without blocking the AWT thread.
*/
protected boolean resolve ()
public boolean resolve ()
{
// if we got canned before we were resolved, go ahead and bail now
if (_panel.getBlock(_bounds.x, _bounds.y) != this) {
// Log.info("Not resolving abandoned block " + this + ".");
_panel.blockAbandoned(this);
return false;
if (_panel != null) {
if (_panel.getBlock(_bounds.x, _bounds.y) != this) {
// Log.info("Not resolving abandoned block " + this + ".");
_panel.blockAbandoned(this);
return false;
}
_panel.blockResolving(this);
}
_panel.blockResolving(this);
// start with the bounds of the footprint polygon
Rectangle sbounds = new Rectangle(_footprint.getBounds());
@@ -105,11 +113,10 @@ public class SceneBlock
// resolve our base tiles
long now = System.currentTimeMillis();
int baseCount = 0, fringeCount = 0;
MisoSceneModel model = _panel.getSceneModel();
for (int yy = 0; yy < _bounds.height; yy++) {
for (int xx = 0; xx < _bounds.width; xx++) {
int x = _bounds.x + xx, y = _bounds.y + yy;
int fqTileId = model.getBaseTileId(x, y);
int fqTileId = _model.getBaseTileId(x, y);
if (fqTileId <= 0) {
continue;
}
@@ -125,7 +132,7 @@ public class SceneBlock
}
// compute the fringe for this tile
_fringe[tidx] = _panel.computeFringeTile(x, y);
_fringe[tidx] = computeFringeTile(x, y);
fringeCount++;
}
}
@@ -142,11 +149,11 @@ public class SceneBlock
// resolve our objects
ObjectSet set = new ObjectSet();
model.getObjects(_bounds, set);
_model.getObjects(_bounds, set);
ArrayList<SceneObject> scobjs = new ArrayList<SceneObject>();
now = System.currentTimeMillis();
for (int ii = 0, ll = set.size(); ii < ll; ii++) {
SceneObject scobj = new SceneObject(_panel, set.get(ii));
SceneObject scobj = makeSceneObject(set.get(ii));
// ignore this object if it failed to resolve
if (scobj.bounds == null) {
continue;
@@ -168,10 +175,10 @@ public class SceneBlock
_objects = scobjs.toArray(new SceneObject[scobjs.size()]);
// resolve our default tileset
int bsetid = model.getDefaultBaseTileSet();
int bsetid = _model.getDefaultBaseTileSet();
try {
if (bsetid > 0) {
_defset = _panel.getTileManager().getTileSet(bsetid);
_defset = _tileMgr.getTileSet(bsetid);
}
} catch (Exception e) {
log.warning("Unable to fetch default base tileset [tsid=" + bsetid +
@@ -187,10 +194,22 @@ public class SceneBlock
return true;
}
protected SceneObject makeSceneObject (ObjectInfo info)
{
return new SceneObject(_metrics, _tileMgr,
_panel == null ? null : _panel.getColorizer(info), info);
}
/** Computes the fringe tile for the specified coordinate. */
protected BaseTile computeFringeTile (int tx, int ty)
{
return _panel == null ? null : _panel.computeFringeTile(tx, ty);
}
/**
* This is called by the {@link SceneBlockResolver} on the AWT thread
* when our resolution has completed. We inform our containing panel.
* This is called by the {@link SceneBlockResolver} on the AWT thread when our resolution has
* completed. We inform our containing panel.
*/
protected void wasResolved ()
{
@@ -292,8 +311,7 @@ public class SceneBlock
if (fqTileId <= 0) {
_base[tidx] = null;
} else {
_base[tidx] = (BaseTile)
_panel.getTileManager().getTile(fqTileId);
_base[tidx] = (BaseTile)_tileMgr.getTile(fqTileId);
}
// clear out the fringe (it must be recomputed by the caller)
_fringe[tidx] = null;
@@ -318,7 +336,7 @@ public class SceneBlock
{
int tidx = index(tx, ty);
if (_base[tidx] != null) {
_fringe[tidx] = _panel.computeFringeTile(tx, ty);
_fringe[tidx] = computeFringeTile(tx, ty);
}
}
@@ -340,7 +358,7 @@ public class SceneBlock
}
}
_objects = ArrayUtil.append(_objects, new SceneObject(_panel, info));
_objects = ArrayUtil.append(_objects, makeSceneObject(info));
// clear out our neighbors array so that the subsequent update
// causes us to recompute our coverage
@@ -554,8 +572,14 @@ public class SceneBlock
_covered[index(tx, ty)] = true;
}
/** The panel for which we contain a block. */
/** The panel for which we contain a block or null if we aren't backed by a panel. */
protected MisoScenePanel _panel;
protected MisoSceneMetrics _metrics;
protected MisoSceneModel _model;
protected TileManager _tileMgr;
/** The bounds of (in tile coordinates) of this block. */
protected Rectangle _bounds;
@@ -29,13 +29,15 @@ import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import com.samskivert.swing.RuntimeAdjust;
import com.samskivert.util.StringUtil;
import com.samskivert.swing.RuntimeAdjust;
import com.threerings.media.tile.NoSuchTileSetException;
import com.threerings.media.tile.ObjectTile;
import com.threerings.media.tile.TileManager;
import com.threerings.media.tile.TileUtil;
import com.threerings.media.tile.TileSet.Colorizer;
import com.threerings.miso.MisoPrefs;
import com.threerings.miso.data.ObjectInfo;
import com.threerings.miso.util.MisoSceneMetrics;
@@ -59,16 +61,21 @@ public class SceneObject
public Rectangle bounds;
/**
* Creates a scene object for display by the specified panel. The
* appropriate object tile is resolved and the object's in-situ bounds
* are computed.
* Creates a scene object for display by the specified panel. The appropriate object tile is
* resolved and the object's in-situ bounds are computed.
*/
public SceneObject (MisoScenePanel panel, ObjectInfo info)
{
this(panel.getSceneMetrics(), panel.getTileManager(), panel.getColorizer(info), info);
}
public SceneObject (MisoSceneMetrics metrics, TileManager mgr, Colorizer colorizer,
ObjectInfo info)
{
this.info = info;
// resolve our object tile
refreshObjectTile(panel);
refreshObjectTile(metrics, mgr, colorizer);
}
/**
@@ -241,30 +248,38 @@ public class SceneObject
}
/**
* Reloads and recolorizes our object tile. It is not intended for the
* actual object tile used by a scene object to change in its
* lifetime, only attributes of that object like its colorizations. So
* don't do anything crazy like change our {@link ObjectInfo}'s
* Reloads and recolorizes our object tile. It is not intended for the actual object tile used
* by a scene object to change in its lifetime, only attributes of that object like its
* colorizations. So don't do anything crazy like change our {@link ObjectInfo}'s
* <code>tileId</code> and call this method or things might break.
*/
public void refreshObjectTile (MisoScenePanel panel)
{
refreshObjectTile(panel.getSceneMetrics(), panel.getTileManager(),
panel.getColorizer(info));
}
/**
* Reloads and recolorizes our object tile. It is not intended for the actual object tile used
* by a scene object to change in its lifetime, only attributes of that object like its
* colorizations. So don't do anything crazy like change our {@link ObjectInfo}'s
* <code>tileId</code> and call this method or things might break.
*/
public void refreshObjectTile (MisoSceneMetrics metrics, TileManager mgr, Colorizer colorizer)
{
int tsid = TileUtil.getTileSetId(info.tileId);
int tidx = TileUtil.getTileIndex(info.tileId);
try {
tile = (ObjectTile)panel.getTileManager().getTile(
tsid, tidx, panel.getColorizer(info));
computeInfo(panel.getSceneMetrics());
tile = (ObjectTile)mgr.getTile(tsid, tidx, colorizer);
computeInfo(metrics);
} catch (NoSuchTileSetException te) {
log.warning("Scene contains non-existent object tileset " +
"[info=" + info + "].");
log.warning("Scene contains non-existent object tileset [info=" + info + "].");
}
}
/**
* Computes our screen bounds, tile footprint and other useful cached
* metrics.
* Computes our screen bounds, tile footprint and other useful cached metrics.
*/
protected void computeInfo (MisoSceneMetrics metrics)
{
@@ -0,0 +1,11 @@
package com.threerings.miso.client;
import java.awt.Rectangle;
/**
* Exposes an operation to be applied to tiles by {@link TileOpApplicator}.
*/
public interface TileOp
{
public void apply (int tx, int ty, Rectangle tbounds);
}
@@ -0,0 +1,111 @@
package com.threerings.miso.client;
import java.awt.Point;
import java.awt.Rectangle;
import com.threerings.miso.util.MisoSceneMetrics;
import com.threerings.miso.util.MisoUtil;
/**
* Applies a TileOp to all tiles within a region.
*/
public class TileOpApplicator
{
public TileOpApplicator (MisoSceneMetrics metrics)
{
_metrics = metrics;
_tbounds = new Rectangle(0, 0, _metrics.tilewid, _metrics.tilehei);
}
/**
* Applies the supplied tile operation to all tiles that intersect the supplied screen
* rectangle.
*/
public void applyToTiles (Rectangle region, TileOp op)
{
// determine which tiles intersect this region: this is going to
// be nearly incomprehensible without some sort of diagram; i'll
// do what i can to comment it, but you'll want to print out a
// scene diagram (docs/miso/scene.ps) and start making notes if
// you want to follow along
// obtain our upper left tile
Point tpos = MisoUtil.screenToTile(_metrics, region.x, region.y, new Point());
// determine which quadrant of the upper left tile we occupy
Point spos = MisoUtil.tileToScreen(_metrics, tpos.x, tpos.y, new Point());
boolean left = (region.x - spos.x < _metrics.tilehwid);
boolean top = (region.y - spos.y < _metrics.tilehhei);
// set up our tile position counters
int dx, dy;
if (left) {
dx = 0;
dy = 1;
} else {
dx = 1;
dy = 0;
}
// if we're in the top-half of the tile we need to move up a row,
// either forward or back depending on whether we're in the left
// or right half of the tile
if (top) {
if (left) {
tpos.x -= 1;
} else {
tpos.y -= 1;
}
// we'll need to start zig-zagging the other way as well
dx = 1 - dx;
dy = 1 - dy;
}
// these will bound our loops
int rightx = region.x + region.width, bottomy = region.y + region.height;
// Log.info("Preparing to apply [tpos=" + StringUtil.toString(tpos) +
// ", left=" + left + ", top=" + top +
// ", bounds=" + StringUtil.toString(bounds) +
// ", spos=" + StringUtil.toString(spos) +
// "].");
// obtain the coordinates of the tile that starts the first row
// and loop through, applying to the intersecting tiles
MisoUtil.tileToScreen(_metrics, tpos.x, tpos.y, spos);
while (spos.y < bottomy) {
// set up our row counters
int tx = tpos.x, ty = tpos.y;
_tbounds.x = spos.x;
_tbounds.y = spos.y;
// Log.info("Applying to row [tx=" + tx + ", ty=" + ty + "].");
// apply to the tiles in this row
while (_tbounds.x < rightx) {
op.apply(tx, ty, _tbounds);
// move one tile to the right
tx += 1;
ty -= 1;
_tbounds.x += _metrics.tilewid;
}
// update our tile coordinates
tpos.x += dx;
dx = 1 - dx;
tpos.y += dy;
dy = 1 - dy;
// obtain the screen coordinates of the next starting tile
MisoUtil.tileToScreen(_metrics, tpos.x, tpos.y, spos);
}
}
protected MisoSceneMetrics _metrics;
/** Used when rendering tiles. */
protected Rectangle _tbounds;
}