diff --git a/src/java/com/threerings/cast/CharacterManager.java b/src/java/com/threerings/cast/CharacterManager.java index c45ed3741..42f7a6655 100644 --- a/src/java/com/threerings/cast/CharacterManager.java +++ b/src/java/com/threerings/cast/CharacterManager.java @@ -1,5 +1,5 @@ // -// $Id: CharacterManager.java,v 1.2 2001/10/22 18:21:41 shaper Exp $ +// $Id: CharacterManager.java,v 1.3 2001/10/24 00:55:08 shaper Exp $ package com.threerings.miso.scene; @@ -26,9 +26,11 @@ public class CharacterManager /** * Construct the character manager. */ - public CharacterManager (Config config, TileManager tilemgr) + public CharacterManager ( + Config config, TileManager tilemgr, IsoSceneViewModel model) { _tilemgr = tilemgr; + _model = model; // load character data descriptions String file = config.getValue(CHARFILE_KEY, DEFAULT_CHARFILE); @@ -58,8 +60,8 @@ public class CharacterManager MultiFrameImage[] anims = TileUtil.getAmbulatorySpriteFrames( _tilemgr, tsid, info.frameCount); - AmbulatorySprite sprite = new AmbulatorySprite(0, 0, anims); - sprite.setFrameRate(info.frameRate); + AmbulatorySprite sprite = new AmbulatorySprite(_model, 0, 0, anims); + sprite.setFrameRate(info.fps); sprite.setOrigin(info.origin.x, info.origin.y); return sprite; @@ -79,6 +81,9 @@ public class CharacterManager /** The tile manager. */ protected TileManager _tilemgr; + /** The iso scene view model. */ + protected IsoSceneViewModel _model; + /** * A class that contains character description information for a * single character for use when constructing the character's @@ -88,13 +93,13 @@ public class CharacterManager { public int tsid; public int frameCount; - public int frameRate; + public int fps; public Point origin = new Point(); public String toString () { return "[tsid=" + tsid + ", frameCount=" + frameCount + - ", frameRate=" + frameRate + ", origin=" + origin + "]"; + ", fps=" + fps + ", origin=" + origin + "]"; } } } diff --git a/src/java/com/threerings/cast/CharacterSprite.java b/src/java/com/threerings/cast/CharacterSprite.java index c21766967..bbda2b412 100644 --- a/src/java/com/threerings/cast/CharacterSprite.java +++ b/src/java/com/threerings/cast/CharacterSprite.java @@ -1,12 +1,15 @@ // -// $Id: CharacterSprite.java,v 1.13 2001/10/22 18:21:41 shaper Exp $ +// $Id: CharacterSprite.java,v 1.14 2001/10/24 00:55:08 shaper Exp $ package com.threerings.miso.scene; +import java.awt.Point; + import com.threerings.media.sprite.*; import com.threerings.miso.Log; import com.threerings.miso.tile.MisoTile; +import com.threerings.miso.scene.util.IsoUtil; /** * An ambulatory sprite is a sprite that animates itself while walking @@ -25,11 +28,13 @@ public class AmbulatorySprite extends Sprite implements Traverser * @param anims the set of multi-frame images to use when animating * the sprite in each of the compass directions. */ - public AmbulatorySprite (int x, int y, MultiFrameImage[] anims) + public AmbulatorySprite ( + IsoSceneViewModel model, int x, int y, MultiFrameImage[] anims) { super(x, y); // keep track of these + _model = model; _anims = anims; // give ourselves an initial orientation @@ -115,9 +120,111 @@ public class AmbulatorySprite extends Sprite implements Traverser return tile.passable; } + /** + * Returns the sprite's location on the x-axis in tile coordinates. + */ + public int getTileX () + { + return _tilex; + } + + /** + * Returns the sprite's location on the y-axis in tile coordinates. + */ + public int getTileY () + { + return _tiley; + } + + /** + * Returns the sprite's location on the x-axis within its current + * tile in fine coordinates. + */ + public int getFineX () + { + return _finex; + } + + /** + * Returns the sprite's location on the y-axis within its current + * tile in fine coordinates. + */ + public int getFineY () + { + return _finey; + } + + // documentation inherited + public void setLocation (int x, int y) + { + super.setLocation(x, y); + + if (_path == null) { + // we only calculate the sprite's tile and fine + // coordinates if we have no path, since paths that move + // us about are responsible for keeping our scene + // coordinates up to date since only they can know where + // we really are while in transition from one place to + // another. + + // get the sprite's position in full coordinates + Point fpos = new Point(); + IsoUtil.screenToFull(_model, _x, _y, fpos); + + // save off the sprite's tile and fine coordinates + _tilex = IsoUtil.fullToTile(fpos.x); + _tiley = IsoUtil.fullToTile(fpos.y); + _finex = IsoUtil.fullToFine(fpos.x); + _finey = IsoUtil.fullToFine(fpos.y); + } + } + + /** + * Sets the sprite's location in tile coordinates; the sprite is + * not actually moved in any way. This method is only intended + * for use in updating the sprite's stored position which is made + * accessible to others that may care to review it. + */ + public void setTileLocation (int x, int y) + { + _tilex = x; + _tiley = y; + } + + /** + * Sets the sprite's location in fine coordinates; the sprite is + * not actually moved in any way. This method is only intended + * for use in updating the sprite's stored position which is made + * accessible to others that may care to review it. + */ + public void setFineLocation (int x, int y) + { + _finex = x; + _finey = y; + } + + // documentation inherited + protected void toString (StringBuffer buf) + { + super.toString(buf); + buf.append(", tilex=").append(_tilex); + buf.append(", tiley=").append(_tiley); + buf.append(", finex=").append(_finex); + buf.append(", finey=").append(_finey); + } + /** The animation frames for the sprite facing each direction. */ protected MultiFrameImage[] _anims; + /** The iso scene view model. */ + protected IsoSceneViewModel _model; + /** The origin of the sprite. */ protected int _xorigin, _yorigin; + + /** The sprite location in tile coordinates. */ + protected int _tilex, _tiley; + + /** The sprite location in fine coordinates. */ + protected int _finex, _finey; } diff --git a/src/java/com/threerings/media/sprite/DirtyItemList.java b/src/java/com/threerings/media/sprite/DirtyItemList.java deleted file mode 100644 index 13ebe54ca..000000000 --- a/src/java/com/threerings/media/sprite/DirtyItemList.java +++ /dev/null @@ -1,93 +0,0 @@ -// -// $Id: DirtyItemList.java,v 1.4 2001/10/22 18:14:57 shaper Exp $ - -package com.threerings.media.sprite; - -import java.awt.*; -import java.util.*; - -import com.threerings.media.sprite.Sprite; -import com.threerings.media.tile.ObjectTile; - -import com.threerings.media.Log; - -/** - * The dirty item list keeps track of dirty sprites and object tiles - * in a scene. - */ -public class DirtyItemList extends ArrayList -{ - /** - * Appends the dirty sprite at the given coordinates to the dirty - * item list. - */ - public void appendDirtySprite ( - Sprite sprite, int x, int y, Rectangle dirtyRect) - { - add(new DirtyItem(sprite, null, x, y, dirtyRect)); - } - - /** - * Appends the dirty object tile at the given coordinates to the - * dirty item list. - */ - public void appendDirtyObject ( - ObjectTile tile, Shape bounds, int x, int y, Rectangle dirtyRect) - { - add(new DirtyItem(tile, bounds, x, y, dirtyRect)); - } - - /** - * 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; - - /** - * 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 portion of the item that falls within the given dirty - * rectangle is actually drawn. - */ - public void paint (Graphics2D gfx, Rectangle clip) - { - Shape oclip = gfx.getClip(); - - // clip the draw region to the dirty portion of the item - gfx.clip(clip); - - // paint the item - if (obj instanceof Sprite) { - ((Sprite)obj).paint(gfx); - } else { - ((ObjectTile)obj).paint(gfx, bounds); - } - - // restore original clipping region - gfx.setClip(oclip); - } - - public String toString () - { - return "[obj=" + obj + ", x=" + x + ", y=" + y + "]"; - } - } -} diff --git a/src/java/com/threerings/media/util/LineSegmentPath.java b/src/java/com/threerings/media/util/LineSegmentPath.java index d80e308d1..69b458bd2 100644 --- a/src/java/com/threerings/media/util/LineSegmentPath.java +++ b/src/java/com/threerings/media/util/LineSegmentPath.java @@ -1,5 +1,5 @@ // -// $Id: LineSegmentPath.java,v 1.11 2001/10/12 00:36:03 shaper Exp $ +// $Id: LineSegmentPath.java,v 1.12 2001/10/24 00:55:08 shaper Exp $ package com.threerings.media.sprite; @@ -9,21 +9,22 @@ import java.awt.Graphics2D; import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import com.samskivert.util.StringUtil; import com.threerings.media.util.MathUtil; /** - * The LineSegmentPath class is used to cause a sprite to - * follow a path that is made up of a sequence of line segments. There - * must be at least two nodes in any worthwhile path. The direction of the - * first node in the path is meaningless since the sprite begins at that + * The line segment path is used to cause a sprite to follow a path + * that is made up of a sequence of line segments. There must be at + * least two nodes in any worthwhile path. The direction of the first + * node in the path is meaningless since the sprite begins at that * node and will therefore never be heading towards it. */ public class LineSegmentPath implements Path { /** - * Construct a LineSegmentPath object. + * Constructs a line segment path. */ public LineSegmentPath () { @@ -31,45 +32,17 @@ public class LineSegmentPath implements Path } /** - * Construct a LineSegmentPath object with the specified - * starting node coordinates. An arbitrary direction will be assigned - * to the starting node. + * Constructs a line segment path with the specified list of + * points. An arbitrary direction will be assigned to the + * starting node. * * @param x the starting node x-position. * @param y the starting node y-position. */ - public LineSegmentPath (int x, int y) + public LineSegmentPath (List points) { _nodes = new ArrayList(); - - // add the starting node with an arbitrarily chosen direction - // since direction is meaningless here - addNode(x, y, Sprite.DIR_NORTH); - } - - /** - * Add a node to the path with the specified destination point and - * facing direction. - * - * @param x the x-position. - * @param y the y-position. - * @param dir the facing direction. - */ - public void addNode (int x, int y, int dir) - { - _nodes.add(new PathNode(x, y, dir)); - } - - /** - * Add a node to the path with the specified destination point. An - * arbitrary direction will be assigned to the node. - * - * @param x the x-position. - * @param y the y-position. - */ - public void addNode (int x, int y) - { - _nodes.add(new PathNode(x, y, Sprite.DIR_NORTH)); + createPath(points); } /** @@ -132,7 +105,7 @@ public class LineSegmentPath implements Path _niter = _nodes.iterator(); // pretend like we were previously heading to our starting position - _dest = (PathNode)_niter.next(); + _dest = getNextNode(); // begin traversing the path headToNextNode(sprite, timestamp, timestamp); @@ -169,6 +142,12 @@ public class LineSegmentPath implements Path return false; } + /** + * Place the sprite moving along the path at the end of the + * previous path node, face it appropriately for the next node, + * and start it on its way. Returns whether the sprite position + * moved. + */ protected boolean headToNextNode (Sprite sprite, long startstamp, long now) { // check to see if we've completed our path @@ -183,7 +162,7 @@ public class LineSegmentPath implements Path _src = _dest; // pop the next node off the path - _dest = (PathNode)_niter.next(); + _dest = getNextNode(); // adjust the sprite's orientation sprite.setOrientation(_dest.dir); @@ -205,6 +184,7 @@ public class LineSegmentPath implements Path return updatePosition(sprite, now); } + // documentation inherited public void paint (Graphics2D gfx) { gfx.setColor(Color.red); @@ -219,11 +199,79 @@ public class LineSegmentPath implements Path } } + // documentation inherited public String toString () { return StringUtil.toString(_nodes.iterator()); } + /** + * Populate the path with the path nodes that lead the sprite from + * its starting position to the given destination coordinates + * following the given list of screen coordinates. + */ + protected void createPath (List points) + { + Point last = null; + int size = points.size(); + for (int ii = 0; ii < size; ii++) { + Point p = (Point)points.get(ii); + + int dir = (ii == 0) ? Sprite.DIR_NORTH : getDirection(last, p); + addNode(p.x, p.y, dir); + last = p; + } + } + + /** + * Add a node to the path with the specified destination point and + * facing direction. + * + * @param x the x-position. + * @param y the y-position. + * @param dir the facing direction. + */ + protected void addNode (int x, int y, int dir) + { + _nodes.add(new PathNode(x, y, dir)); + } + + /** + * Gets the next node in the path. + */ + protected PathNode getNextNode () + { + return (PathNode)_niter.next(); + } + + /** + * Returns the direction that point b lies in from + * point a as one of the Sprite + * direction constants. + */ + protected int getDirection (Point a, Point b) + { + if (a.x == b.x && a.y > b.y) { + return Sprite.DIR_NORTH; + } else if (a.x < b.x && a.y > b.y) { + return Sprite.DIR_NORTHEAST; + } else if (a.x > b.x && a.y == b.y) { + return Sprite.DIR_EAST; + } else if (a.x > b.x && a.y < b.y) { + return Sprite.DIR_SOUTHEAST; + } else if (a.x == b.x && a.y < b.y) { + return Sprite.DIR_SOUTH; + } else if (a.x > b.x && a.y < b.y) { + return Sprite.DIR_SOUTHWEST; + } else if (a.x > b.x && a.y == b.y) { + return Sprite.DIR_WEST; + } else if (a.x > b.x && a.y > b.y) { + return Sprite.DIR_NORTHWEST; + } + + return Sprite.DIR_NONE; + } + /** The nodes that make up the path. */ protected ArrayList _nodes; diff --git a/src/java/com/threerings/media/util/PathNode.java b/src/java/com/threerings/media/util/PathNode.java index 22800c4cd..e8607dc9b 100644 --- a/src/java/com/threerings/media/util/PathNode.java +++ b/src/java/com/threerings/media/util/PathNode.java @@ -1,5 +1,5 @@ // -// $Id: PathNode.java,v 1.5 2001/09/05 00:40:33 shaper Exp $ +// $Id: PathNode.java,v 1.6 2001/10/24 00:55:08 shaper Exp $ package com.threerings.media.sprite; @@ -35,9 +35,20 @@ public class PathNode public String toString () { StringBuffer buf = new StringBuffer(); - buf.append("[x=").append(loc.x); - buf.append(", y=").append(loc.y); - buf.append(", dir=").append(dir); + buf.append("["); + toString(buf); return buf.append("]").toString(); } + + /** + * This should be overridden by derived classes (which should be sure + * to call super.toString()) to append the derived class + * specific path node information to the string buffer. + */ + public void toString (StringBuffer buf) + { + buf.append("x=").append(loc.x); + buf.append(", y=").append(loc.y); + buf.append(", dir=").append(dir); + } } diff --git a/src/java/com/threerings/miso/client/DirtyItemList.java b/src/java/com/threerings/miso/client/DirtyItemList.java new file mode 100644 index 000000000..8f249ff6c --- /dev/null +++ b/src/java/com/threerings/miso/client/DirtyItemList.java @@ -0,0 +1,234 @@ +// +// $Id: DirtyItemList.java,v 1.1 2001/10/24 00:55:08 shaper Exp $ + +package com.threerings.miso.scene; + +import java.awt.*; +import java.util.*; + +import com.threerings.media.sprite.Sprite; +import com.threerings.media.tile.ObjectTile; + +import com.threerings.media.Log; + +/** + * The dirty item list keeps track of dirty sprites and object tiles + * in a scene. + */ +public class DirtyItemList extends ArrayList +{ + /** The dirty item comparator used to sort dirty items back to front. */ + public static final Comparator DIRTY_COMP = new DirtyItemComparator(); + + /** + * Appends the dirty sprite at the given coordinates to the dirty + * item list. + */ + public void appendDirtySprite ( + Sprite sprite, int x, int y, Rectangle dirtyRect) + { + add(new DirtyItem(sprite, null, x, y, dirtyRect)); + } + + /** + * Appends the dirty object tile at the given coordinates to the + * dirty item list. + */ + public void appendDirtyObject ( + ObjectTile tile, Shape bounds, int x, int y, Rectangle dirtyRect) + { + add(new DirtyItem(tile, bounds, x, y, dirtyRect)); + } + + /** + * Returns an array of the {@link DirtyItem} objects in the list + * sorted with the {@link DirtyItemComparator}. + */ + public DirtyItem[] sort () + { + DirtyItem items[] = new DirtyItem[size()]; + toArray(items); + Arrays.sort(items, DirtyItemList.DIRTY_COMP); + return items; + } + + /** + * 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; + + /** + * 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 portion of the item that falls within the given dirty + * rectangle is actually drawn. + */ + public void paint (Graphics2D gfx, Rectangle clip) + { + Shape oclip = gfx.getClip(); + + // clip the draw region to the dirty portion of the item + gfx.clip(clip); + + // paint the item + if (obj instanceof Sprite) { + ((Sprite)obj).paint(gfx); + } else { + ((ObjectTile)obj).paint(gfx, bounds); + } + + // restore original clipping region + gfx.setClip(oclip); + } + + public boolean equals (Object other) + { + // we're never equal to something that's not our kind + if (!(other instanceof DirtyItem)) { + return false; + } + + // sprites are equivalent if they're the same sprite + DirtyItem b = (DirtyItem)other; + if ((obj instanceof Sprite) && (b.obj instanceof Sprite)) { + return (obj == b.obj); + } + + // object-to-object or object-to-sprite are distinguished + // simply by tile coordinate since they can never occupy + // the same tile + return (x == b.x && y == b.y); + } + + public String toString () + { + return "[obj=" + obj + ", x=" + x + ", y=" + y + + ", drect=" + dirtyRect + "]"; + } + } + + /** + * A comparator class for use in sorting the dirty sprites and + * objects in a scene in ascending x- and y-coordinate order + * suitable for rendering in the isometric view with proper visual + * results. + */ + public static class DirtyItemComparator implements Comparator + { + public int compare (Object a, Object b) + { + DirtyItem da = (DirtyItem)a; + DirtyItem db = (DirtyItem)b; + + if (da.x == db.x && + da.y == db.y && + da.obj != db.obj) { + // we're comparing two sprites co-existing on the same + // tile, so study their fine coordinates to determine + // rendering order + AmbulatorySprite as = (AmbulatorySprite)da.obj; + AmbulatorySprite bs = (AmbulatorySprite)db.obj; + + int aloc = as.getFineX() + as.getFineY(); + int bloc = bs.getFineX() + bs.getFineY(); + + if (aloc < bloc) { + return -1; + } else if (aloc > bloc) { + return 1; + } else { + // if they're at the same vertical row of + // intra-tile tiles, just use something consistent + return as.hashCode() - bs.hashCode(); + } + } + + // check whether right edge of a overlaps with left edge of b + int comp = getRightOverlap(da, db); + if (comp != 0) { + return comp; + } + + // check whether right edge of b overlaps with left edge of a + comp = getRightOverlap(db, da); + if (comp != 0) { + // reverse ordering per reversed overlap check + return (comp == -1) ? 1 : -1; + } + + // determine ordering based purely on coordinates + if (da.x <= db.x && da.y <= db.y) { + return -1; + } + + return 1; + } + + public boolean equals (Object obj) + { + return (obj == this); + } + + /** + * Checks the right edge of da to see whether it + * overlaps with the left edge of db. + * + * @return -1 if da should be rendered behind + * db, 0 if the right edge of da + * does not overlap with db, and 1 if + * da should be rendered in front of + * db. + */ + protected int getRightOverlap (DirtyItem da, DirtyItem db) + { + int ax = da.x, bx = db.x; + int ay = da.y, by = db.y; + + // get da's rightmost corner coordinate + if (da.obj instanceof ObjectTile) { + ay -= (((ObjectTile)da.obj).baseHeight - 1); + } + + // get db's leftmost corner coordinate + if (db.obj instanceof ObjectTile) { + bx -= (((ObjectTile)db.obj).baseWidth - 1); + } + + if (ax < bx && ay > by) { + // we most certainly don't overlap + return 0; + } + + // calculate inequality constant for db's leftmost corner + int k = (bx + by); + + // we need to determine whether to render da in front of + // db, so we check whether da's rightmost corner is above + // or below db's leftmost corner. + if (ay <= k - ax) { + return -1; + } else { + return 1; + } + } + } +} diff --git a/src/java/com/threerings/miso/client/IsoSceneView.java b/src/java/com/threerings/miso/client/IsoSceneView.java index 978f64109..88df9e02b 100644 --- a/src/java/com/threerings/miso/client/IsoSceneView.java +++ b/src/java/com/threerings/miso/client/IsoSceneView.java @@ -1,5 +1,5 @@ // -// $Id: IsoSceneView.java,v 1.66 2001/10/23 02:04:07 shaper Exp $ +// $Id: IsoSceneView.java,v 1.67 2001/10/24 00:55:08 shaper Exp $ package com.threerings.miso.scene; @@ -13,21 +13,21 @@ 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; import com.threerings.miso.Log; +import com.threerings.miso.scene.DirtyItemList.DirtyItem; import com.threerings.miso.scene.util.*; /** - * The IsoSceneView provides an isometric view of a - * particular scene. + * The iso scene view provides an isometric view of a particular + * scene. */ public class IsoSceneView implements SceneView { /** - * Construct an IsoSceneView object. + * Constructs an iso scene view. * * @param spritemgr the sprite manager. * @param model the data model. @@ -190,7 +190,7 @@ public class IsoSceneView implements SceneView } /** - * Render the scene to the given graphics context. + * Renders the scene to the given graphics context. * * @param gfx the graphics context. */ @@ -236,21 +236,19 @@ public class IsoSceneView implements SceneView */ protected void renderDirtyItems (Graphics2D gfx) { + // Log.info("renderDirtyItems"); + // sort the dirty sprites and objects visually back-to-front - int size = _dirtyItems.size(); - DirtyItem items[] = new DirtyItem[size]; - _dirtyItems.toArray(items); - Arrays.sort(items, IsoUtil.DIRTY_COMP); + DirtyItem items[] = _dirtyItems.sort(); // merge all dirty rectangles for each item into a single // rectangle before painting Rectangle dirtyRect = new Rectangle(); DirtyItem cur = null, last = null; - for (int ii = 0; ii < size; ii++) { + for (int ii = 0; ii < items.length; ii++) { cur = items[ii]; - if (last == null || - (cur.x != last.x || cur.y != last.y)) { + if (last == null || !cur.equals(last)) { if (last != null) { // paint the item with its full dirty rectangle @@ -590,16 +588,13 @@ 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); + AmbulatorySprite sprite = (AmbulatorySprite)_dirtySprites.get(ii); // get the dirty portion of the sprite Rectangle drect = sprite.getBounds().intersection(r); - _dirtyItems.appendDirtySprite(sprite, tpos.x, tpos.y, drect); + _dirtyItems.appendDirtySprite( + sprite, sprite.getTileX(), sprite.getTileY(), drect); // Log.info("Dirtied item: " + sprite); } @@ -633,78 +628,18 @@ public class IsoSceneView implements SceneView return null; } - // constrain destination pixels to fine coordinates - Point fpos = new Point(); - IsoUtil.screenToFull(_model, x, y, fpos); + // get the destination tile coordinates + Point dest = new Point(); + IsoUtil.screenToTile(_model, x, y, dest); - // calculate tile coordinates for start and end position - Point stpos = new Point(); - IsoUtil.screenToTile(_model, sprite.getX(), sprite.getY(), stpos); - int tbx = IsoUtil.fullToTile(fpos.x); - int tby = IsoUtil.fullToTile(fpos.y); - - // Log.info("Seeking path [sx=" + stpos.x + ", sy=" + stpos.y + - // ", dx=" + tbx + ", dy=" + tby + " ,fdx=" + fpos.x + - // ", fdy=" + fpos.y + "]."); - - // get a reasonable path from start to end - List tilepath = AStarPathUtil.getPath( + // get a reasonable tile path through the scene + List points = AStarPathUtil.getPath( _scene.getBaseLayer(), _model.scenewid, _model.scenehei, - sprite, stpos.x, stpos.y, tbx, tby); - if (tilepath == null) { - return null; - } + sprite, sprite.getTileX(), sprite.getTileY(), dest.x, dest.y); - // TODO: make more visually appealing path segments from start - // to second tile, and penultimate to ultimate tile. - - // construct path with starting screen position - LineSegmentPath path = - new LineSegmentPath(sprite.getX(), sprite.getY()); - - // add all nodes on the calculated path - Point nspos = new Point(); - Point prev = stpos; - int size = tilepath.size(); - for (int ii = 1; ii < size - 1; ii++) { - Point n = (Point)tilepath.get(ii); - - // determine the direction this node lies in from the - // previous node - int dir = IsoUtil.getIsoDirection(prev.x, prev.y, n.x, n.y); - - // determine the node's position in screen pixel coordinates - IsoUtil.tileToScreen(_model, n.x, n.y, nspos); - - // add the node to the path, wandering through the middle - // of each tile in the path for now - path.addNode(nspos.x + _model.tilehwid, - nspos.y + _model.tilehhei, dir); - - prev = n; - } - - // get the final destination point's screen coordinates - // constrained to the closest full coordinate - Point spos = new Point(); - IsoUtil.fullToScreen(_model, fpos.x, fpos.y, spos); - - // get the direction we're to face while heading toward the end - int dir; - 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); - } else { - // else it's based on the last tile we traversed - dir = IsoUtil.getIsoDirection(prev.x, prev.y, tbx, tby); - } - - // add the final destination path node - path.addNode(spos.x, spos.y, dir); - - return path; + // construct a path object to guide the sprite on its merry way + return (points == null) ? null : + new TilePath(_model, sprite, points, x, y); } /** The stroke used to draw dirty rectangles. */ diff --git a/src/java/com/threerings/miso/client/TilePath.java b/src/java/com/threerings/miso/client/TilePath.java new file mode 100644 index 000000000..640ded525 --- /dev/null +++ b/src/java/com/threerings/miso/client/TilePath.java @@ -0,0 +1,185 @@ +// +// $Id: TilePath.java,v 1.1 2001/10/24 00:55:08 shaper Exp $ + +package com.threerings.miso.scene; + +import java.awt.*; +import java.util.List; + +import com.threerings.media.sprite.*; + +import com.threerings.miso.Log; +import com.threerings.miso.scene.util.IsoUtil; + +/** + * The tile path represents a path of tiles through a scene. The path + * is traversed by treating each pair of connected tiles as a line + * segment. Only ambulatory sprites can follow a tile path, and their + * tile coordinates are updated as the path is traversed. + */ +public class TilePath extends LineSegmentPath +{ + /** + * Constructs a tile path. + * + * @param model the iso scene view model the path is associated with. + * @param sprite the sprite to follow the path. + * @param tiles the tiles to be traversed during the path. + * @param destx the destination x-position in screen pixel + * coordinates. + * @param desty the destination y-position in screen pixel + * coordinates. + */ + public TilePath ( + IsoSceneViewModel model, AmbulatorySprite sprite, List tiles, + int destx, int desty) + { + _model = model; + + // set up the path nodes + createPath(sprite, tiles, destx, desty); + } + + // documentation inherited + public boolean updatePosition (Sprite sprite, long timestamp) + { + boolean moved = super.updatePosition(sprite, timestamp); + + if (moved) { + AmbulatorySprite as = (AmbulatorySprite)sprite; + int sx = sprite.getX(), sy = sprite.getY(); + Point pos = new Point(); + + // check whether we've arrived at the destination tile + if (!_arrived) { + // get the sprite's latest tile coordinates + IsoUtil.screenToTile(_model, sx, sy, pos); + + // if the sprite has reached the destination tile, + // update the sprite's tile location and remember + // we've arrived + int dtx = _dest.getTileX(), dty = _dest.getTileY(); + if (pos.x == dtx && pos.y == dty) { + as.setTileLocation(dtx, dty); + // Log.info("Sprite arrived [dtx=" + dtx + + // ", dty=" + dty + "]."); + _arrived = true; + } + } + + // get the sprite's latest fine coordinates + IsoUtil.tileToScreen(_model, as.getTileX(), as.getTileY(), pos); + Point fpos = new Point(); + IsoUtil.pixelToFine(_model, sx - pos.x, sy - pos.y, fpos); + + // inform the sprite + as.setFineLocation(fpos.x, fpos.y); + + // Log.info("Sprite moved [s=" + as + "]."); + } + + return moved; + } + + // documentation inherited + protected PathNode getNextNode () + { + // upgrade the path node to a tile path node + _dest = (TilePathNode)super.getNextNode(); + + // note that we've not yet arrived at the destination tile + _arrived = false; + + return _dest; + } + + /** + * Populate the path with the tile path nodes that lead the sprite + * from its starting position to the given destination coordinates + * following the given list of tile coordinates. + */ + protected void createPath ( + AmbulatorySprite sprite, List tiles, int destx, int desty) + { + // constrain destination pixels to fine coordinates + Point fpos = new Point(); + IsoUtil.screenToFull(_model, destx, desty, fpos); + + // add the starting path node + int stx = sprite.getTileX(), sty = sprite.getTileY(); + int sx = sprite.getX(), sy = sprite.getY(); + addNode(stx, sty, sx, sy, Sprite.DIR_NORTH); + + // TODO: make more visually appealing path segments from start + // to second tile, and penultimate to ultimate tile. + + // add all remaining path nodes excepting the last one + Point prev = new Point(stx, sty); + Point spos = new Point(); + int size = tiles.size(); + for (int ii = 1; ii < size - 1; ii++) { + Point next = (Point)tiles.get(ii); + + // determine the direction from previous to next node + int dir = IsoUtil.getIsoDirection(prev.x, prev.y, next.x, next.y); + + // determine the node's position in screen pixel coordinates + IsoUtil.tileToScreen(_model, next.x, next.y, spos); + + // add the node to the path, wandering through the middle + // of each tile in the path for now + int dsx = spos.x + _model.tilehwid; + int dsy = spos.y + _model.tilehhei; + addNode(next.x, next.y, dsx, dsy, dir); + + prev = next; + } + + // get the final destination point's screen coordinates + // constrained to the closest full coordinate + IsoUtil.fullToScreen(_model, fpos.x, fpos.y, spos); + + // get the tile coordinates for the final destination tile + int tdestx = IsoUtil.fullToTile(fpos.x); + int tdesty = IsoUtil.fullToTile(fpos.y); + + // get the facing direction for the final node + int dir; + if (prev.x == stx && prev.y == sty) { + // if destination is within starting tile, direction is + // determined by studying the fine coordinates + dir = IsoUtil.getDirection(_model, sx, sy, spos.x, spos.y); + + } else { + // else it's based on the last tile we traversed + dir = IsoUtil.getIsoDirection(prev.x, prev.y, tdestx, tdesty); + } + + // add the final destination path node + addNode(tdestx, tdesty, spos.x, spos.y, dir); + } + + /** + * Add a node to the path with the specified destination + * coordinates and facing direction. + * + * @param tx the tile x-position. + * @param ty the tile y-position. + * @param x the x-position. + * @param y the y-position. + * @param dir the facing direction. + */ + protected void addNode (int tx, int ty, int x, int y, int dir) + { + _nodes.add(new TilePathNode(tx, ty, x, y, dir)); + } + + /** Whether the sprite has arrived at the current destination tile. */ + protected boolean _arrived; + + /** The destination tile path node. */ + protected TilePathNode _dest; + + /** The iso scene view model. */ + protected IsoSceneViewModel _model; +} diff --git a/src/java/com/threerings/miso/client/TilePathNode.java b/src/java/com/threerings/miso/client/TilePathNode.java new file mode 100644 index 000000000..55427bada --- /dev/null +++ b/src/java/com/threerings/miso/client/TilePathNode.java @@ -0,0 +1,51 @@ +// +// $Id: TilePathNode.java,v 1.1 2001/10/24 00:55:08 shaper Exp $ + +package com.threerings.miso.scene; + +import com.threerings.media.sprite.PathNode; + +/** + * The tile path nodes extends the path node class to allow + * associating tile coordinates with a node in a path. + */ +public class TilePathNode extends PathNode +{ + /** + * Constructs a tile path node. + */ + public TilePathNode (int tilex, int tiley, int x, int y, int dir) + { + super(x, y, dir); + + _tilex = tilex; + _tiley = tiley; + } + + /** + * Returns the node's x-axis tile coordinates. + */ + public int getTileX () + { + return _tilex; + } + + /** + * Returns the node's y-axis tile coordinates. + */ + public int getTileY () + { + return _tiley; + } + + // documentation inherited + public void toString (StringBuffer buf) + { + super.toString(buf); + buf.append(", tilex=").append(_tilex); + buf.append(", tiley=").append(_tiley); + } + + /** The path node tile coordinates. */ + protected int _tilex, _tiley; +} diff --git a/src/java/com/threerings/miso/client/util/IsoUtil.java b/src/java/com/threerings/miso/client/util/IsoUtil.java index cb5ff64b7..9573e1224 100644 --- a/src/java/com/threerings/miso/client/util/IsoUtil.java +++ b/src/java/com/threerings/miso/client/util/IsoUtil.java @@ -1,13 +1,11 @@ // -// $Id: IsoUtil.java,v 1.12 2001/10/22 18:21:41 shaper Exp $ +// $Id: IsoUtil.java,v 1.13 2001/10/24 00:55:08 shaper Exp $ package com.threerings.miso.scene.util; import java.awt.*; -import java.util.Comparator; import com.threerings.media.sprite.Sprite; -import com.threerings.media.sprite.DirtyItemList.DirtyItem; import com.threerings.media.tile.ObjectTile; import com.threerings.media.util.MathUtil; @@ -20,9 +18,6 @@ import com.threerings.miso.scene.*; */ public class IsoUtil { - /** The dirty item comparator used to sort dirty items back to front. */ - public static final Comparator DIRTY_COMP = new DirtyItemComparator(); - /** * Returns a polygon bounding all footprint tiles of the given * object tile. @@ -399,87 +394,4 @@ public class IsoUtil /** Multiplication factor to embed tile coords in full coords. */ protected static final int FULL_TILE_FACTOR = 100; - - /** - * A comparator class for use in sorting the dirty sprites and - * objects in a scene in ascending x- and y-coordinate order - * suitable for rendering in the isometric view with proper visual - * results. - */ - public static class DirtyItemComparator implements Comparator - { - public int compare (Object a, Object b) - { - DirtyItem da = (DirtyItem)a; - DirtyItem db = (DirtyItem)b; - - // check whether right edge of a overlaps with left edge of b - int comp = getRightOverlap(da, db); - if (comp != 0) { - return comp; - } - - // check whether right edge of b overlaps with left edge of a - comp = getRightOverlap(db, da); - if (comp != 0) { - // reverse ordering per reversed overlap check - return (comp == -1) ? 1 : -1; - } - - // determine ordering based purely on coordinates - if (da.x <= db.x && da.y <= db.y) { - return -1; - } - - return 1; - } - - public boolean equals (Object obj) - { - return (obj == this); - } - - /** - * Checks the right edge of da to see whether it - * overlaps with the left edge of db. - * - * @return -1 if da should be rendered behind - * db, 0 if the right edge of da - * does not overlap with db, and 1 if - * da should be rendered in front of - * db. - */ - protected int getRightOverlap (DirtyItem da, DirtyItem db) - { - int ax = da.x, bx = db.x; - int ay = da.y, by = db.y; - - // get da's rightmost corner coordinate - if (da.obj instanceof ObjectTile) { - ay -= (((ObjectTile)da.obj).baseHeight - 1); - } - - // get db's leftmost corner coordinate - if (db.obj instanceof ObjectTile) { - bx -= (((ObjectTile)db.obj).baseWidth - 1); - } - - if (ax < bx && ay > by) { - // we most certainly don't overlap - return 0; - } - - // calculate inequality constant for db's leftmost corner - int k = (bx + by); - - // we need to determine whether to render da in front of - // db, so we check whether da's rightmost corner is above - // or below db's leftmost corner. - if (ay <= k - ax) { - return -1; - } else { - return 1; - } - } - } } diff --git a/tests/src/java/com/threerings/miso/viewer/ViewerFrame.java b/tests/src/java/com/threerings/miso/viewer/ViewerFrame.java index d3c20f04e..e1affd587 100644 --- a/tests/src/java/com/threerings/miso/viewer/ViewerFrame.java +++ b/tests/src/java/com/threerings/miso/viewer/ViewerFrame.java @@ -1,5 +1,5 @@ // -// $Id: ViewerFrame.java,v 1.23 2001/10/23 02:03:49 shaper Exp $ +// $Id: ViewerFrame.java,v 1.24 2001/10/24 00:55:08 shaper Exp $ package com.threerings.miso.viewer; @@ -9,7 +9,6 @@ import com.threerings.media.sprite.SpriteManager; import com.threerings.media.tile.TileManager; import com.threerings.miso.Log; -import com.threerings.miso.scene.CharacterManager; import com.threerings.miso.viewer.util.ViewerContext; /** @@ -35,13 +34,9 @@ public class ViewerFrame extends JFrame SpriteManager spritemgr = new SpriteManager(); TileManager tilemgr = ctx.getTileManager(); - // construct the character manager from which we obtain our sprite - CharacterManager charmgr = new CharacterManager( - ctx.getConfig(), tilemgr); - // set up the scene view panel with a default scene ViewerSceneViewPanel svpanel = - new ViewerSceneViewPanel(ctx, spritemgr, charmgr); + new ViewerSceneViewPanel(ctx, spritemgr); // add the main panel to the frame getContentPane().add(svpanel); diff --git a/tests/src/java/com/threerings/miso/viewer/ViewerSceneViewPanel.java b/tests/src/java/com/threerings/miso/viewer/ViewerSceneViewPanel.java index 21f7d2ef6..b9dc9aa84 100644 --- a/tests/src/java/com/threerings/miso/viewer/ViewerSceneViewPanel.java +++ b/tests/src/java/com/threerings/miso/viewer/ViewerSceneViewPanel.java @@ -1,5 +1,5 @@ // -// $Id: ViewerSceneViewPanel.java,v 1.20 2001/10/23 02:03:49 shaper Exp $ +// $Id: ViewerSceneViewPanel.java,v 1.21 2001/10/24 00:55:08 shaper Exp $ package com.threerings.miso.viewer; @@ -25,8 +25,7 @@ public class ViewerSceneViewPanel extends SceneViewPanel /** * Construct the panel and initialize it with a context. */ - public ViewerSceneViewPanel ( - ViewerContext ctx, SpriteManager spritemgr, CharacterManager charmgr) + public ViewerSceneViewPanel (ViewerContext ctx, SpriteManager spritemgr) { super(ctx.getConfig(), spritemgr); @@ -35,6 +34,19 @@ public class ViewerSceneViewPanel extends SceneViewPanel // create an animation manager for this panel _animmgr = new AnimationManager(spritemgr, this); + // load up the initial scene + prepareStartingScene(); + + // construct the character manager from which we obtain sprites + CharacterManager charmgr = new CharacterManager( + ctx.getConfig(), ctx.getTileManager(), _scenemodel); + + // create the manipulable sprite + _sprite = createSprite(spritemgr, charmgr, TSID_CHAR_USER); + + // create the decoy sprites + createDecoys(spritemgr, charmgr); + // listen to the desired events addMouseListener(new MouseAdapter() { public void mousePressed (MouseEvent e) { @@ -42,15 +54,6 @@ public class ViewerSceneViewPanel extends SceneViewPanel } }); - // load up the initial scene - prepareStartingScene(); - - // create the manipulable sprite - _sprite = createSprite(spritemgr, charmgr); - - // create the decoy sprites - createDecoys(spritemgr, charmgr); - PerformanceMonitor.register(this, "paint", 1000); } @@ -58,9 +61,9 @@ public class ViewerSceneViewPanel extends SceneViewPanel * Creates a new sprite. */ protected AmbulatorySprite createSprite ( - SpriteManager spritemgr, CharacterManager charmgr) + SpriteManager spritemgr, CharacterManager charmgr, int tsid) { - AmbulatorySprite s = charmgr.getCharacter(TSID_CHAR); + AmbulatorySprite s = charmgr.getCharacter(tsid); if (s != null) { s.setLocation(300, 300); s.addSpriteObserver(this); @@ -78,7 +81,8 @@ public class ViewerSceneViewPanel extends SceneViewPanel { _decoys = new AmbulatorySprite[NUM_DECOYS]; for (int ii = 0; ii < NUM_DECOYS; ii++) { - if ((_decoys[ii] = createSprite(spritemgr, charmgr)) != null) { + _decoys[ii] = createSprite(spritemgr, charmgr, TSID_CHAR); + if (_decoys[ii] != null) { createRandomPath(_decoys[ii]); } } @@ -153,8 +157,6 @@ public class ViewerSceneViewPanel extends SceneViewPanel do { x = RandomUtil.getInt(d.width); y = RandomUtil.getInt(d.height); - // Log.info("Moving sprite [s=" + s + ", x=" + x + - // ", y=" + y + "]."); } while (!createPath(s, x, y)); } @@ -163,7 +165,6 @@ public class ViewerSceneViewPanel extends SceneViewPanel { if (event instanceof PathCompletedEvent) { AmbulatorySprite s = (AmbulatorySprite)event.getSprite(); - // Log.info("Path completed [sprite=" + s + "]."); if (s != _sprite) { // move the sprite to a new random location @@ -173,13 +174,16 @@ public class ViewerSceneViewPanel extends SceneViewPanel } /** The number of decoy characters milling about. */ - protected static final int NUM_DECOYS = 10; + protected static final int NUM_DECOYS = 2; - /** The tileset id for the character tiles. */ + /** The tileset id for the decoy character tiles. */ protected static final int TSID_CHAR = 1011; + /** The tileset id for the user character tiles. */ + protected static final int TSID_CHAR_USER = 1012; + /** The animation manager. */ - AnimationManager _animmgr; + protected AnimationManager _animmgr; /** The sprite we're manipulating within the view. */ protected AmbulatorySprite _sprite;