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:
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user