Revamped iso scene rendering yet again to remove unnecessary dirty

rectangle propagation and to only render the dirty portions of dirty
objects and sprites.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@509 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Walter Korman
2001-10-19 23:26:31 +00:00
parent 9b89a3f994
commit ef97b42acd
4 changed files with 102 additions and 161 deletions
@@ -1,10 +1,9 @@
//
// $Id: DirtyItemList.java,v 1.2 2001/10/17 22:13:53 shaper Exp $
// $Id: DirtyItemList.java,v 1.3 2001/10/19 23:26:31 shaper Exp $
package com.threerings.media.sprite;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.*;
import java.util.*;
import com.threerings.media.sprite.Sprite;
@@ -14,98 +13,68 @@ import com.threerings.media.Log;
/**
* The dirty item list keeps track of dirty sprites and object tiles
* in a scene. Since scenes can only ever have one of either item at
* any given coordinate, dirty items are stored and checked for
* equality based solely on their coordinate, and each coordinate can
* exist in the list only once.
* in a scene.
*/
public class DirtyItemList extends ArrayList
{
/**
* Appends the dirty sprite at the given coordinates to the dirty
* item list if no item already exists at those coordinates.
* Returns whether the item was added to the list.
* item list.
*/
public boolean appendDirtySprite (Sprite sprite, int x, int y)
public void appendDirtySprite (
Sprite sprite, int x, int y, Rectangle dirtyRect)
{
if (!contains(x, y)) {
add(new DirtyItem(sprite, null, x, y));
return true;
}
return false;
add(new DirtyItem(sprite, null, x, y, dirtyRect));
}
/**
* Appends the dirty object tile at the given coordinates to the
* dirty item list if no item already exists at those coordinates.
* Returns whether the item was added to the list.
* dirty item list.
*/
public boolean appendDirtyObject (
ObjectTile tile, Shape bounds, int x, int y)
public void appendDirtyObject (
ObjectTile tile, Shape bounds, int x, int y, Rectangle dirtyRect)
{
if (!contains(x, y)) {
add(new DirtyItem(tile, bounds, x, y));
return true;
}
return false;
add(new DirtyItem(tile, bounds, x, y, dirtyRect));
}
/**
* Sort the items in the list using the given comparator.
*/
public void sort (Comparator comp)
{
Object[] items = new Object[size()];
toArray(items);
Arrays.sort(items, comp);
clear();
for (int ii = 0; ii < items.length; ii++) {
add(items[ii]);
}
}
/**
* Returns whether the list contains a dirty item at the given
* coordinates.
*/
protected boolean contains (int x, int y)
{
int size = size();
for (int ii = 0; ii < size; ii++) {
Object o = get(ii);
if (o instanceof DirtyItem) {
DirtyItem di = (DirtyItem)o;
return (x == di.x && y == di.y);
}
}
return false;
}
/**
* A wrapper class to hold the items inserted in the dirty list
* along with their coordinates in the scene.
* A class to hold the items inserted in the dirty list along with
* all of the information necessary to render their dirty regions
* to the target graphics context when the time comes to do so.
*/
public class DirtyItem
{
public Object obj;
public Shape bounds;
public int x, y;
public Rectangle dirtyRect;
public DirtyItem (Object obj, Shape bounds, int x, int y)
/**
* Constructs a dirty item.
*/
public DirtyItem (
Object obj, Shape bounds, int x, int y, Rectangle dirtyRect)
{
this.obj = obj;
this.bounds = bounds;
this.x = x;
this.y = y;
this.dirtyRect = dirtyRect;
}
/**
* Paints the dirty item to the given graphics context. Only
* the dirty rectangle of the item is question is actually
* drawn.
*/
public void paint (Graphics2D gfx)
{
// clip the draw region to the dirty portion of the item
gfx.setClip(dirtyRect);
// paint the item
if (obj instanceof Sprite) {
((Sprite)obj).paint(gfx);
} else {
((ObjectTile)obj).paint(gfx, bounds);
}
@@ -1,5 +1,5 @@
//
// $Id: IsoSceneView.java,v 1.63 2001/10/18 20:25:46 shaper Exp $
// $Id: IsoSceneView.java,v 1.64 2001/10/19 23:26:31 shaper Exp $
package com.threerings.miso.scene;
@@ -13,6 +13,7 @@ import java.util.*;
import com.samskivert.util.HashIntMap;
import com.threerings.media.sprite.*;
import com.threerings.media.sprite.DirtyItemList.DirtyItem;
import com.threerings.media.tile.Tile;
import com.threerings.media.tile.ObjectTile;
@@ -227,13 +228,21 @@ public class IsoSceneView implements SceneView
protected void renderDirtyItems (Graphics2D gfx)
{
// sort the dirty sprites and objects visually back-to-front
_dirtyItems.sort(IsoUtil.DIRTY_COMP);
int size = _dirtyItems.size();
DirtyItem items[] = new DirtyItem[size];
_dirtyItems.toArray(items);
Arrays.sort(items, IsoUtil.DIRTY_COMP);
// save original clipping region
Shape clip = gfx.getClip();
// render dirty sprites and objects
int size = _dirtyItems.size();
for (int ii = 0; ii < size; ii++) {
((DirtyItemList.DirtyItem)_dirtyItems.get(ii)).paint(gfx);
items[ii].paint(gfx);
}
// restore original clipping region
gfx.setClip(clip);
}
/**
@@ -403,10 +412,10 @@ public class IsoSceneView implements SceneView
Rectangle r = (Rectangle)rects.get(ii);
// dirty the tiles impacted by this rectangle
invalidateScreenRect(rects, r.x, r.y, r.width, r.height);
Rectangle tileBounds = invalidateScreenRect(r);
// dirty any sprites or objects impacted by this rectangle
invalidateItems(r);
invalidateItems(tileBounds);
// save the rectangle for potential display later
_dirtyRects.add(r);
@@ -417,51 +426,46 @@ public class IsoSceneView implements SceneView
* Invalidate the specified rectangle in screen pixel coordinates
* in the view.
*
* @param rects the dirty rectangle list that we're processing because
* we may have to add dirty rectangles to it when invalidating this
* particular rect.
* @param x the rectangle x-position.
* @param y the rectangle y-position.
* @param width the rectangle width.
* @param height the rectangle height.
* @param rect the dirty rectangle.
*/
public void invalidateScreenRect (
DirtyRectList rects, int x, int y, int width, int height)
public Rectangle invalidateScreenRect (Rectangle r)
{
Rectangle tileBounds = new Rectangle();
// note that corner tiles may be included unnecessarily, but
// checking to determine whether they're actually needed
// complicates the code with likely-insufficient benefit
// determine the top-left tile impacted by this rect
Point tpos = new Point();
IsoUtil.screenToTile(_model, x, y, tpos);
IsoUtil.screenToTile(_model, r.x, r.y, tpos);
// determine screen coordinates for top-left tile
Point topleft = new Point();
IsoUtil.tileToScreen(_model, tpos.x, tpos.y, topleft);
// determine number of horizontal and vertical tiles for rect
int numh = (int)Math.ceil((float)width / (float)_model.tilewid);
int numv = (int)Math.ceil((float)height / (float)_model.tilehhei);
int numh = (int)Math.ceil((float)r.width / (float)_model.tilewid);
int numv = (int)Math.ceil((float)r.height / (float)_model.tilehhei);
// set up iterating variables
int tx = tpos.x, ty = tpos.y, mx = tpos.x, my = tpos.y;;
int tx = tpos.x, ty = tpos.y, mx = tpos.x, my = tpos.y;
// set the starting screen y-position
int screenY = topleft.y;
// add top row if rect may overlap
if (y < (screenY + _model.tilehhei)) {
if (r.y < (screenY + _model.tilehhei)) {
ty--;
for (int ii = 0; ii < numh; ii++) {
addDirtyTile(rects, tx++, ty--);
addDirtyTile(tileBounds, tx++, ty--);
}
}
// add rows to the bottom if rect may overlap
int ypos = screenY + (numv * _model.tilehhei);
if ((y + height) > ypos) {
numv += ((y + height) > (ypos + _model.tilehhei)) ? 2 : 1;
if ((r.y + r.height) > ypos) {
numv += ((r.y + r.height) > (ypos + _model.tilehhei)) ? 2 : 1;
}
// add dirty tiles from each affected row
@@ -480,20 +484,20 @@ public class IsoSceneView implements SceneView
}
// skip leftmost tile if rect doesn't overlap
if (x > screenX + _model.tilewid) {
if (r.x > screenX + _model.tilewid) {
tx++;
ty--;
screenX += _model.tilewid;
}
// add to the right edge if rect may overlap
if (x + width > (screenX + (length * _model.tilewid))) {
if (r.x + r.width > (screenX + (length * _model.tilewid))) {
length++;
}
// add all tiles in the row to the dirty set
for (int jj = 0; jj < length; jj++) {
addDirtyTile(rects, tx++, ty--);
addDirtyTile(tileBounds, tx++, ty--);
}
// step along the x- or y-axis appropriately
@@ -509,14 +513,16 @@ public class IsoSceneView implements SceneView
// toggle whether we're drawing an odd-numbered row
isodd = !isodd;
}
return tileBounds;
}
/**
* Marks the tile at the given coordinates dirty and adds dirty
* rectangles to the dirty rectangle list for any sprites
* currently occupying the tile.
* Marks the tile at the given coordinates dirty and expands the
* tile bounds rectangle to include the rectangle for the dirtied
* tile.
*/
protected void addDirtyTile (DirtyRectList rects, int x, int y)
protected void addDirtyTile (Rectangle tileBounds, int x, int y)
{
// constrain x-coordinate to a valid range
if (x < 0) {
@@ -532,6 +538,9 @@ public class IsoSceneView implements SceneView
y = _model.scenehei - 1;
}
// expand the tile bounds rectangle to include this tile
tileBounds.add(_polys[x][y].getBounds());
// do nothing if the tile's already dirty
if (_dirty[x][y]) {
return;
@@ -540,14 +549,6 @@ public class IsoSceneView implements SceneView
// mark the tile dirty
_numDirty++;
_dirty[x][y] = true;
// add the dirty rectangles of any sprites that we've just
// inadvertently touched by dirtying this tile
invalidateIntersectingSprites(rects, _polys[x][y]);
// similarly, add any objects that we've just touched to the
// dirty item list
invalidateIntersectingObjects(rects, _polys[x][y]);
}
/**
@@ -564,13 +565,17 @@ public class IsoSceneView implements SceneView
int size = _dirtySprites.size();
for (int ii = 0; ii < size; ii++) {
Sprite sprite = (Sprite)_dirtySprites.get(ii);
// get the sprite's position in tile coordinates
Point tpos = new Point();
IsoUtil.screenToTile(_model, sprite.getX(), sprite.getY(), tpos);
if (_dirtyItems.appendDirtySprite(sprite, tpos.x, tpos.y)) {
// get the dirty portion of the sprite
Rectangle drect = sprite.getBounds().intersection(r);
_dirtyItems.appendDirtySprite(sprite, tpos.x, tpos.y, drect);
// Log.info("Dirtied item: " + sprite);
}
}
// add any objects impacted by the dirty rectangle
ObjectTile tiles[][] = _scene.getObjectLayer();
@@ -581,65 +586,27 @@ public class IsoSceneView implements SceneView
Polygon poly = (Polygon)_objpolys.get(coord);
if (poly.intersects(r)) {
// get the dirty portion of the object
Rectangle drect = poly.getBounds().intersection(r);
int tx = coord >> 16, ty = coord & 0x0000FFFF;
if (_dirtyItems.appendDirtyObject(
tiles[tx][ty], poly, tx, ty)) {
_dirtyItems.appendDirtyObject(
tiles[tx][ty], poly, tx, ty, drect);
// Log.info("Dirtied item: Object(" + tx + ", " +
// ty + ")");
}
}
}
}
/**
* Adds dirty rectangles to the dirty rectangle list for any
* sprites in the scene whose bounding rectangle overlaps with the
* given shape.
*/
protected void invalidateIntersectingSprites (
DirtyRectList rects, Shape bounds)
{
_dirtySprites.clear();
_spritemgr.getIntersectingSprites(_dirtySprites, bounds);
int size = _dirtySprites.size();
for (int ii = 0; ii < size; ii++) {
Sprite sprite = (Sprite)_dirtySprites.get(ii);
if (rects.appendDirtyRect(sprite.getRenderedBounds())) {
// Log.info("Expanded for: " + sprite);
}
}
}
/**
* Adds dirty rectangles to the dirty rectangle list for any
* objects in the scene whose bounding rectangle overlaps with the
* given shape's bounding rectangle.
*/
protected void invalidateIntersectingObjects (
DirtyRectList rects, Shape bounds)
{
ObjectTile tiles[][] = _scene.getObjectLayer();
Iterator iter = _objpolys.keys();
while (iter.hasNext()) {
// get the object's coordinates and bounding polygon
int coord = ((Integer)iter.next()).intValue();
Polygon poly = (Polygon)_objpolys.get(coord);
if (poly.intersects(bounds.getBounds())) {
if (rects.appendDirtyRect(poly.getBounds())) {
// Log.info("Expanded for: " + poly.getBounds());
}
}
}
}
// documentation inherited
public Path getPath (AmbulatorySprite sprite, int x, int y)
{
// make sure the destination point is within our bounds
if (x < 0 || x >= _model.bounds.width ||
y < 0 || y >= _model.bounds.height) {
if (x < 0 ||
x >= _model.bounds.width ||
y < 0 ||
y >= _model.bounds.height) {
return null;
}
@@ -654,8 +621,7 @@ public class IsoSceneView implements SceneView
int tby = IsoUtil.fullToTile(fpos.y);
// get a reasonable path from start to end
List tilepath =
AStarPathUtil.getPath(
List tilepath = AStarPathUtil.getPath(
_scene.getBaseLayer(), _model.scenewid, _model.scenehei,
sprite, stpos.x, stpos.y, tbx, tby);
if (tilepath == null) {
@@ -701,8 +667,8 @@ public class IsoSceneView implements SceneView
if (prev == stpos) {
// if our destination is within our origination tile,
// direction is based on fine coordinates
dir = IsoUtil.getDirection(_model, sprite.getX(), sprite.getY(),
spos.x, spos.y);
dir = IsoUtil.getDirection(
_model, sprite.getX(), sprite.getY(), spos.x, spos.y);
} else {
// else it's based on the last tile we traversed
dir = IsoUtil.getIsoDirection(prev.x, prev.y, tbx, tby);
@@ -1,5 +1,5 @@
//
// $Id: SceneViewPanel.java,v 1.17 2001/10/18 21:01:44 shaper Exp $
// $Id: SceneViewPanel.java,v 1.18 2001/10/19 23:26:31 shaper Exp $
package com.threerings.miso.scene;
@@ -29,7 +29,7 @@ public class SceneViewPanel extends AnimatedPanel
_scenemodel = new IsoSceneViewModel(config);
// listen to the iso scene view model to receive notice when
// location or coordinate display is toggled
// the scene display has changed and needs must be repainted
_scenemodel.addListener(this);
// create the scene view
@@ -1,5 +1,5 @@
//
// $Id: IsoUtil.java,v 1.10 2001/10/18 20:24:05 shaper Exp $
// $Id: IsoUtil.java,v 1.11 2001/10/19 23:26:31 shaper Exp $
package com.threerings.miso.scene.util;
@@ -204,11 +204,17 @@ public class IsoUtil
}
}
/**
* Returns the tile coordinate of the given full coordinate.
*/
public static int fullToTile (int val)
{
return (val / FULL_TILE_FACTOR);
}
/**
* Returns the fine coordinate of the given full coordinate.
*/
public static int fullToFine (int val)
{
return (val - ((val / FULL_TILE_FACTOR) * FULL_TILE_FACTOR));