ccbc47a7de
follow a path (specifically, I want to allow the media panel to be "scrolled" along a path). git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1409 542714f4-19e9-0310-aa3c-eee0fc999fb1
922 lines
29 KiB
Java
922 lines
29 KiB
Java
//
|
|
// $Id: IsoSceneView.java,v 1.110 2002/05/31 03:38:03 mdb Exp $
|
|
|
|
package com.threerings.miso.scene;
|
|
|
|
import java.awt.BasicStroke;
|
|
import java.awt.Color;
|
|
import java.awt.Font;
|
|
import java.awt.FontMetrics;
|
|
import java.awt.Graphics2D;
|
|
import java.awt.Graphics;
|
|
import java.awt.Point;
|
|
import java.awt.Polygon;
|
|
import java.awt.Rectangle;
|
|
import java.awt.Shape;
|
|
import java.awt.Stroke;
|
|
import java.awt.event.MouseEvent;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
|
|
import com.samskivert.util.StringUtil;
|
|
import com.samskivert.util.HashIntMap;
|
|
|
|
import com.threerings.media.RegionManager;
|
|
|
|
import com.threerings.media.sprite.SpriteManager;
|
|
import com.threerings.media.util.Path;
|
|
|
|
import com.threerings.media.tile.ObjectTile;
|
|
import com.threerings.media.tile.Tile;
|
|
|
|
import com.threerings.miso.Log;
|
|
import com.threerings.miso.scene.DirtyItemList.DirtyItem;
|
|
import com.threerings.miso.scene.util.AStarPathUtil;
|
|
import com.threerings.miso.scene.util.IsoUtil;
|
|
|
|
/**
|
|
* The iso scene view provides an isometric view of a particular scene. It
|
|
* presently supports scrolling in a limited form. Object tiles are not
|
|
* handled properly, nor is mouse highlighting. Those should only be used
|
|
* if the view will not be scrolled.
|
|
*/
|
|
public class IsoSceneView implements SceneView
|
|
{
|
|
/** Instructs the scene view never to draw highlights around object
|
|
* tiles. */
|
|
public static final int HIGHLIGHT_NEVER = 0;
|
|
|
|
/** Instructs the scene view to highlight only object tiles that have
|
|
* a non-empty action string. */
|
|
public static final int HIGHLIGHT_WITH_ACTION = 1;
|
|
|
|
/** Instructs the scene view to highlight every object tile,
|
|
* regardless of whether it has a valid action string. */
|
|
public static final int HIGHLIGHT_ALWAYS = 2;
|
|
|
|
/** Instructs the scene view to highlight whatever tile the mouse is
|
|
* over, regardless of whether or not it is an object tile. This is
|
|
* generally only useful in an editor rather than a game. */
|
|
public static final int HIGHLIGHT_ALL = 3;
|
|
|
|
/**
|
|
* Constructs an iso scene view.
|
|
*
|
|
* @param spritemgr the sprite manager.
|
|
* @param model the data model.
|
|
* @param remgr the region manager that is collecting invalid regions
|
|
* for this view.
|
|
*/
|
|
public IsoSceneView (SpriteManager spritemgr, IsoSceneViewModel model,
|
|
RegionManager remgr)
|
|
{
|
|
// save off references
|
|
_spritemgr = spritemgr;
|
|
_model = model;
|
|
_remgr = remgr;
|
|
|
|
// create a cache which will be populated with the tile polygons
|
|
// as they are requested. We manually adjust the size/load factor
|
|
// to optimize memory usage (because we happen to know things
|
|
// hash weirdly because of the way we make the keys.
|
|
_polys = new HashIntMap(model.scenewid, (float) model.scenehei);
|
|
|
|
// create the array used to mark dirty tiles
|
|
_dirty = new boolean[model.scenewid][model.tilehei];
|
|
}
|
|
|
|
/**
|
|
* Configures the scene view to highlight object tiles either never
|
|
* ({@link #HIGHLIGHT_NEVER}), only when an object tile has an
|
|
* associated action string ({@link #HIGHLIGHT_WITH_ACTION}), or
|
|
* always ({@link #HIGHLIGHT_ALWAYS}). It is also possible to
|
|
* configure the view to highlight whatever tile is under the cursor,
|
|
* even if it's not an object tile which is done in the {@link
|
|
* #HIGHLIGHT_ALL} mode.
|
|
*/
|
|
public void setHighlightMode (int hmode)
|
|
{
|
|
_hmode = hmode;
|
|
}
|
|
|
|
/**
|
|
* Returns a reference to the scene being displayed by this view.
|
|
*/
|
|
public DisplayMisoScene getScene ()
|
|
{
|
|
return _scene;
|
|
}
|
|
|
|
// documentation inherited
|
|
public void setScene (DisplayMisoScene scene)
|
|
{
|
|
_scene = scene;
|
|
|
|
// obtain a list of the objects in the scene and generate records
|
|
// for each of them that contain precomputed metrics
|
|
prepareObjectList();
|
|
|
|
// invalidate the entire screen as there's a new scene in town
|
|
invalidate();
|
|
}
|
|
|
|
/**
|
|
* Invalidate the entire visible scene view.
|
|
*/
|
|
public void invalidate ()
|
|
{
|
|
_remgr.invalidateRegion(_model.bounds);
|
|
}
|
|
|
|
// documentation inherited from interface
|
|
public void viewWillScroll (int dx, int dy)
|
|
{
|
|
// adjust our offsets
|
|
_xoff += dx;
|
|
_yoff += dy;
|
|
|
|
// determine whether or not this scrolling causes us to cross
|
|
// boundaries in the x or y directions
|
|
if (_xoff >= _model.tilehwid) {
|
|
_tiledx += 1;
|
|
_tiledy -= 1;
|
|
_xoff -= _model.tilewid;
|
|
|
|
} else if (_xoff < -_model.tilehwid) {
|
|
_tiledx -= 1;
|
|
_tiledy += 1;
|
|
_xoff += _model.tilewid;
|
|
}
|
|
|
|
if (_yoff >= _model.tilehhei) {
|
|
_tiledx += 1;
|
|
_tiledy += 1;
|
|
_yoff -= _model.tilehei;
|
|
|
|
} else if (_yoff < -_model.tilehhei) {
|
|
_tiledx -= 1;
|
|
_tiledy -= 1;
|
|
_yoff += _model.tilehei;
|
|
}
|
|
}
|
|
|
|
// documentation inherited from interface
|
|
public void paint (Graphics2D gfx, Rectangle[] dirtyRects)
|
|
{
|
|
if (_scene == null) {
|
|
Log.warning("Scene view painted with null scene.");
|
|
return;
|
|
}
|
|
|
|
// Log.info("Rendering: " + StringUtil.toString(dirtyRects) +
|
|
// ", clip: " + StringUtil.toString(gfx.getClip()));
|
|
|
|
// // invalidate the invalid rectangles
|
|
// int rsize = dirtyRects.length;
|
|
// for (int ii = 0; ii < rsize; ii++) {
|
|
// invalidateRect(dirtyRects[ii]);
|
|
// }
|
|
|
|
// translate according to our scroll parameters
|
|
gfx.translate(-_xoff, -_yoff);
|
|
|
|
// render the scrolling part of the scene
|
|
int rcount = dirtyRects.length;
|
|
for (int ii = 0; ii < rcount; ii++) {
|
|
Rectangle rect = dirtyRects[ii];
|
|
rect.translate(_xoff, _yoff);
|
|
Shape oclip = gfx.getClip();
|
|
gfx.clipRect(rect.x, rect.y, rect.width, rect.height);
|
|
renderScrollingScene(gfx, rect);
|
|
gfx.setClip(oclip);
|
|
rect.translate(-_xoff, -_yoff);
|
|
}
|
|
|
|
// untranslate according to our scroll parameters
|
|
gfx.translate(_xoff, _yoff);
|
|
|
|
// render the fixed part of the scene
|
|
for (int ii = 0; ii < rcount; ii++) {
|
|
Rectangle rect = dirtyRects[ii];
|
|
Shape oclip = gfx.getClip();
|
|
gfx.clipRect(rect.x, rect.y, rect.width, rect.height);
|
|
renderFixedScene(gfx, rect);
|
|
gfx.setClip(oclip);
|
|
}
|
|
|
|
// draw sprite paths
|
|
if (_model.showPaths) {
|
|
_spritemgr.renderSpritePaths(gfx);
|
|
}
|
|
|
|
// paint our highlighted tile (if any)
|
|
paintHighlights(gfx);
|
|
|
|
// paint any extra goodies
|
|
paintExtras(gfx);
|
|
}
|
|
|
|
/**
|
|
* Paints the highlighted tile.
|
|
*
|
|
* @param gfx the graphics context.
|
|
*/
|
|
protected void paintHighlights (Graphics2D gfx)
|
|
{
|
|
// if we're not highlighting object tiles, bail now
|
|
if (_hmode == HIGHLIGHT_NEVER) {
|
|
return;
|
|
}
|
|
|
|
Polygon hpoly = null;
|
|
|
|
// if the highlighted object is an object tile, we want to
|
|
// highlight that
|
|
if (_hobject instanceof ObjectTile) {
|
|
ObjectTile otile = (ObjectTile)_hobject;
|
|
// if we're only highlighting objects with actions, make sure
|
|
// this one has an action
|
|
String action = _scene.getObjectAction(otile);
|
|
if (_hmode != HIGHLIGHT_WITH_ACTION || !StringUtil.blank(action)) {
|
|
Polygon tpoly = getTilePoly(_hcoords.x, _hcoords.y);
|
|
hpoly = IsoUtil.getObjectFootprint(_model, tpoly, otile);
|
|
}
|
|
}
|
|
|
|
// if we have no highlight object, but we're in HIGHLIGHT_ALL,
|
|
// then paint the bounds of the highlighted base tile
|
|
if (hpoly == null && _hmode == HIGHLIGHT_ALL &&
|
|
_hcoords.x != -1 && _hcoords.y != -1) {
|
|
hpoly = getTilePoly(_hcoords.x, _hcoords.y);
|
|
}
|
|
|
|
// if we've determined that there's something to highlight
|
|
if (hpoly != null) {
|
|
// set the desired stroke and color
|
|
Stroke ostroke = gfx.getStroke();
|
|
gfx.setStroke(_hstroke);
|
|
gfx.setColor(Color.green);
|
|
|
|
// draw the outline
|
|
gfx.draw(hpoly);
|
|
|
|
// restore the original stroke
|
|
gfx.setStroke(ostroke);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A function where derived classes can paint extra stuff while we've
|
|
* got the clipping region set up.
|
|
*/
|
|
protected void paintExtras (Graphics2D gfx)
|
|
{
|
|
// nothing for now
|
|
}
|
|
|
|
/**
|
|
* Renders the scrolling part of the scene to the given graphics
|
|
* context.
|
|
*
|
|
* @param gfx the graphics context.
|
|
*/
|
|
protected void renderScrollingScene (Graphics2D gfx, Rectangle clip)
|
|
{
|
|
// render any intersecting tiles
|
|
renderTiles(gfx, clip);
|
|
|
|
// render anything that goes on top of the tiles
|
|
renderBaseDecorations(gfx, clip);
|
|
}
|
|
|
|
/**
|
|
* Renders the non-scrolling part of the scene to the given graphics
|
|
* context.
|
|
*
|
|
* @param gfx the graphics context.
|
|
*/
|
|
protected void renderFixedScene (Graphics2D gfx, Rectangle clip)
|
|
{
|
|
renderDirtyItems(gfx, clip);
|
|
}
|
|
|
|
/**
|
|
* Renders the base and fringe layer tiles that intersect the
|
|
* specified clipping rectangle.
|
|
*/
|
|
protected void renderTiles (Graphics2D gfx, Rectangle clip)
|
|
{
|
|
FontMetrics fm = gfx.getFontMetrics(_font);
|
|
int fhei = fm.getAscent();
|
|
int cx = _model.tilehwid, cy = _model.tilehhei;
|
|
|
|
if (_model.showCoords) {
|
|
gfx.setFont(_font);
|
|
gfx.setColor(Color.white);
|
|
}
|
|
|
|
for (int yy = 0; yy < _model.scenehei; yy++) {
|
|
for (int xx = 0; xx < _model.scenewid; xx++) {
|
|
Polygon tpoly = getTilePoly(xx, yy);
|
|
|
|
// skip non-intersecting tiles
|
|
// if (!tpoly.intersects(clip)) {
|
|
if (!tpoly.getBounds().intersects(clip)) {
|
|
continue;
|
|
}
|
|
|
|
// offset the tile coordinates by our scrolled deltas
|
|
int tx = xx + _tiledx, ty = yy + _tiledy;
|
|
|
|
// draw the base and fringe tile images
|
|
Tile tile;
|
|
if ((tile = _scene.getBaseTile(tx, ty)) != null) {
|
|
tile.paint(gfx, tpoly);
|
|
}
|
|
if ((tile = _scene.getFringeTile(tx, ty)) != null) {
|
|
tile.paint(gfx, tpoly);
|
|
}
|
|
|
|
// if we're showing coordinates, do that
|
|
if (_model.showCoords) {
|
|
// outline the tile
|
|
gfx.draw(tpoly);
|
|
|
|
// get the top-left screen coordinates of the tile
|
|
Rectangle bounds = tpoly.getBounds();
|
|
int sx = bounds.x, sy = bounds.y;
|
|
|
|
// draw x-coordinate
|
|
String str = String.valueOf(xx + _tiledx);
|
|
int xpos = sx + cx - (fm.stringWidth(str) / 2);
|
|
gfx.drawString(str, xpos, sy + cy);
|
|
|
|
// draw y-coordinate
|
|
str = String.valueOf(yy + _tiledy);
|
|
xpos = sx + cx - (fm.stringWidth(str) / 2);
|
|
gfx.drawString(str, xpos, sy + cy + fhei);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A function where derived classes can paint things after the base
|
|
* tiles have been rendered but before anything else has been rendered
|
|
* (so that whatever is painted appears to be on the ground).
|
|
*/
|
|
protected void renderBaseDecorations (Graphics2D gfx, Rectangle clip)
|
|
{
|
|
// nothing for now
|
|
}
|
|
|
|
/**
|
|
* Renders the dirty sprites and objects in the scene to the given
|
|
* graphics context.
|
|
*/
|
|
protected void renderDirtyItems (Graphics2D gfx, Rectangle clip)
|
|
{
|
|
invalidateItems(clip);
|
|
|
|
// Log.info("renderDirtyItems [items=" + _dirtyItems.size() + "].");
|
|
|
|
// sort the dirty sprites and objects visually back-to-front
|
|
DirtyItem items[] = _dirtyItems.sort();
|
|
|
|
// render each item clipping to its dirty rectangle
|
|
for (int ii = 0; ii < items.length; ii++) {
|
|
items[ii].paint(gfx, items[ii].dirtyRect);
|
|
// Log.info("Painting item [item=" + items[ii] + "].");
|
|
}
|
|
|
|
_dirtyItems.clear();
|
|
}
|
|
|
|
/**
|
|
* Generates and stores bounding polygons for all object tiles in the
|
|
* scene for later use while rendering.
|
|
*/
|
|
protected void prepareObjectList ()
|
|
{
|
|
// clear out any previously existing object data
|
|
_objects.clear();
|
|
|
|
// generate metric records for all objects
|
|
Iterator oiter = _scene.getObjectTiles();
|
|
while (oiter.hasNext()) {
|
|
ObjectTile tile = (ObjectTile)oiter.next();
|
|
Point coords = _scene.getObjectCoords(tile);
|
|
generateObjectMetrics(tile, coords.x, coords.y);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generates object tile metrics for the supplied object tile and adds
|
|
* them to the list.
|
|
*/
|
|
protected void generateObjectMetrics (ObjectTile tile, int x, int y)
|
|
{
|
|
// create a metrics record for this object
|
|
ObjectMetrics metrics = new ObjectMetrics();
|
|
metrics.tile = tile;
|
|
metrics.x = x;
|
|
metrics.y = y;
|
|
metrics.bounds = IsoUtil.getObjectBounds(
|
|
_model, getTilePoly(x, y), tile);
|
|
|
|
// and add it to the list
|
|
_objects.add(metrics);
|
|
}
|
|
|
|
/**
|
|
* Clears out the object metrics for the specified object.
|
|
*/
|
|
protected void clearObjectMetrics (ObjectTile tile)
|
|
{
|
|
int ocount = _objects.size();
|
|
for (int ii = 0; ii < ocount; ii++) {
|
|
ObjectMetrics metrics = (ObjectMetrics)_objects.get(ii);
|
|
if (metrics.tile == tile) {
|
|
_objects.remove(ii);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the polygon bounding the tile at the specified coordinates.
|
|
*/
|
|
protected Polygon getTilePoly (int x, int y)
|
|
{
|
|
int key = IsoUtil.coordsToKey(x, y);
|
|
|
|
Polygon poly = (Polygon) _polys.get(key);
|
|
if (poly == null) {
|
|
poly = IsoUtil.getTilePolygon(_model, x, y);
|
|
_polys.put(key, poly);
|
|
}
|
|
return poly;
|
|
}
|
|
|
|
/**
|
|
* Invalidates the given rectangle in screen pixel coordinates in the
|
|
* view. Returns a rectangle that bounds all tiles that were dirtied.
|
|
*
|
|
* @param rect the dirty rectangle.
|
|
*/
|
|
protected Rectangle invalidateScreenRect (Rectangle r)
|
|
{
|
|
// Log.info("Invalidating [rect=" + r +
|
|
// ", xoff=" + _xoff + ", yoff=" + _yoff + "].");
|
|
|
|
// account for our current scrolling offset
|
|
int rx = r.x + _xoff, ry = r.y + _yoff;
|
|
|
|
// initialize the rectangle bounding all tiles dirtied by the
|
|
// invalidated rectangle
|
|
Rectangle tileBounds = new Rectangle(-1, -1, 0, 0);
|
|
|
|
// 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, rx, ry, tpos);
|
|
|
|
// determine screen coordinates for top-left tile
|
|
Point topleft = new Point();
|
|
IsoUtil.tileToScreen(_model, tpos.x, tpos.y, topleft);
|
|
|
|
// determine number of horizontal tiles for rect
|
|
int numh = (int)Math.ceil((float)r.width / (float)_model.tilewid);
|
|
|
|
// set up iterating variables
|
|
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 (ry < (screenY + _model.tilehhei)) {
|
|
ty--;
|
|
for (int ii = 0; ii < numh; ii++) {
|
|
addDirtyTile(tileBounds, tx++, ty--);
|
|
}
|
|
}
|
|
|
|
// determine the bottom-left tile impacted by this rect
|
|
Point bpos = new Point();
|
|
IsoUtil.screenToTile(_model, rx, ry + r.height, bpos);
|
|
|
|
// determine screen coordinates for bottom-left tile
|
|
Point botleft = new Point();
|
|
IsoUtil.tileToScreen(_model, bpos.x, bpos.y, botleft);
|
|
|
|
// determine the number of vertical rows for our rect (we do this
|
|
// by subtracting the "height" of the top tile from that of the
|
|
// bottom tile, the height being the sum of the x and y
|
|
// coordinate)
|
|
int numv = (bpos.x + bpos.y) - (tpos.x + tpos.y);
|
|
|
|
// now we need to extend the rect to contain the row containing
|
|
// the bottom tile, and potentially the row below that
|
|
numv += ((ry + r.height) > (botleft.y + _model.tilehhei)) ? 2 : 1;
|
|
|
|
// add dirty tiles from each affected row
|
|
for (int ii = 0; ii < numv; ii++) {
|
|
|
|
// set up iterating variables for this row
|
|
tx = mx;
|
|
ty = my;
|
|
int length = numh;
|
|
|
|
// set the starting screen x-position
|
|
int screenX = topleft.x;
|
|
if (ii%2 == 1) {
|
|
screenX -= _model.tilehwid;
|
|
}
|
|
|
|
// skip leftmost tile if rect doesn't overlap
|
|
if (rx > screenX + _model.tilewid) {
|
|
tx++;
|
|
ty--;
|
|
screenX += _model.tilewid;
|
|
}
|
|
|
|
// add to the right edge if rect may overlap
|
|
if (rx + 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(tileBounds, tx++, ty--);
|
|
}
|
|
|
|
// step along the x- or y-axis appropriately
|
|
if (ii%2 == 1) {
|
|
mx++;
|
|
} else {
|
|
my++;
|
|
}
|
|
}
|
|
|
|
return tileBounds;
|
|
}
|
|
|
|
/**
|
|
* Marks the tile at the given coordinates dirty and expands the tile
|
|
* bounds rectangle to include the rectangle for the dirtied tile.
|
|
*/
|
|
protected boolean addDirtyTile (Rectangle tileBounds, int x, int y)
|
|
{
|
|
if (!_model.isCoordinateValid(x, y)) {
|
|
return false;
|
|
}
|
|
|
|
// expand the tile bounds rectangle to include this tile
|
|
Rectangle bounds = getTilePoly(x, y).getBounds();
|
|
if (tileBounds.x == -1) {
|
|
tileBounds.setBounds(bounds);
|
|
} else {
|
|
tileBounds.add(bounds);
|
|
}
|
|
|
|
// do nothing if the tile's already dirty
|
|
if (_dirty[x][y]) {
|
|
return false;
|
|
}
|
|
|
|
// mark the tile dirty
|
|
_numDirty++;
|
|
_dirty[x][y] = true;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Adds any sprites or objects in the scene whose bounds overlap
|
|
* with the given dirty rectangle to the dirty item list for later
|
|
* re-rendering.
|
|
*/
|
|
protected void invalidateItems (Rectangle r)
|
|
{
|
|
// add any sprites impacted by the dirty rectangle
|
|
_dirtySprites.clear();
|
|
_spritemgr.getIntersectingSprites(_dirtySprites, r);
|
|
|
|
int size = _dirtySprites.size();
|
|
for (int ii = 0; ii < size; ii++) {
|
|
MisoCharacterSprite sprite =
|
|
(MisoCharacterSprite)_dirtySprites.get(ii);
|
|
|
|
// get the dirty portion of the sprite
|
|
Rectangle drect = sprite.getBounds().intersection(r);
|
|
|
|
_dirtyItems.appendDirtySprite(
|
|
sprite, sprite.getTileX(), sprite.getTileY(), drect);
|
|
// Log.info("Dirtied item: " + sprite);
|
|
}
|
|
|
|
// add any objects impacted by the dirty rectangle
|
|
if (_scene != null) {
|
|
Iterator iter = _objects.iterator();
|
|
while (iter.hasNext()) {
|
|
ObjectMetrics metrics = (ObjectMetrics)iter.next();
|
|
Rectangle obounds = metrics.bounds;
|
|
if (!obounds.intersects(r)) {
|
|
continue;
|
|
}
|
|
|
|
// get the dirty portion of the object
|
|
Rectangle drect = obounds.intersection(r);
|
|
int tx = metrics.x, ty = metrics.y;
|
|
|
|
// compute the footprint if we're rendering those
|
|
Polygon foot = null;
|
|
if (_model.showFootprints) {
|
|
foot = IsoUtil.getObjectFootprint(
|
|
_model, getTilePoly(tx, ty), metrics.tile);
|
|
}
|
|
|
|
// add the intersected section of the object to the dirty
|
|
// items list
|
|
_dirtyItems.appendDirtyObject(
|
|
metrics.tile, obounds, foot, tx, ty, drect);
|
|
// Log.info("Dirtied item: Object(" + tx + ", " + ty + ")");
|
|
}
|
|
}
|
|
}
|
|
|
|
// documentation inherited
|
|
public Path getPath (MisoCharacterSprite sprite, int x, int y)
|
|
{
|
|
// make sure we have a scene
|
|
if (_scene == null) {
|
|
return null;
|
|
}
|
|
|
|
// make sure the destination point is within our bounds
|
|
if (!_model.bounds.contains(x, y)) {
|
|
return null;
|
|
}
|
|
|
|
// get the destination tile coordinates
|
|
Point dest = new Point();
|
|
IsoUtil.screenToTile(_model, x + _xoff, y + _yoff, dest);
|
|
|
|
// get a reasonable tile path through the scene
|
|
List points = AStarPathUtil.getPath(
|
|
_scene, _model.scenewid, _model.scenehei,
|
|
sprite, sprite.getTileX(), sprite.getTileY(), dest.x, dest.y);
|
|
|
|
// construct a path object to guide the sprite on its merry way
|
|
return (points == null) ? null :
|
|
new TilePath(_model, sprite, points, x, y);
|
|
}
|
|
|
|
// documentation inherited
|
|
public Point getScreenCoords (int x, int y)
|
|
{
|
|
Point coords = new Point();
|
|
IsoUtil.fullToScreen(_model, x, y, coords);
|
|
// adjust for our scrolling offset
|
|
coords.x -= _xoff;
|
|
coords.y -= _yoff;
|
|
return coords;
|
|
}
|
|
|
|
// documentation inherited
|
|
public Point getFullCoords (int x, int y)
|
|
{
|
|
Point coords = new Point();
|
|
IsoUtil.screenToFull(_model, x + _xoff, y + _yoff, coords);
|
|
return coords;
|
|
}
|
|
|
|
// documentation inherited
|
|
public boolean mouseMoved (MouseEvent e)
|
|
{
|
|
int x = e.getX(), y = e.getY();
|
|
boolean repaint = false;
|
|
|
|
// update the base tile coordinates that the mouse is over (if
|
|
// it's also over an object tile, we'll override these values)
|
|
if (_hmode == HIGHLIGHT_ALL) {
|
|
repaint = (updateTileCoords(x, y, _hcoords) || repaint);
|
|
}
|
|
|
|
// compute the list of objects over which the mouse is hovering
|
|
_hitList.clear();
|
|
_hitSprites.clear();
|
|
// add the sprites that contain the point
|
|
_spritemgr.getHitSprites(_hitSprites, x, y);
|
|
int hslen = _hitSprites.size();
|
|
for (int i = 0; i < hslen; i++) {
|
|
MisoCharacterSprite sprite =
|
|
(MisoCharacterSprite)_hitSprites.get(i);
|
|
_hitList.appendDirtySprite(
|
|
sprite, sprite.getTileX(), sprite.getTileY(),
|
|
sprite.getBounds());
|
|
}
|
|
|
|
// add the object tiles that contain the point
|
|
getHitObjects(_hitList, x, y);
|
|
|
|
// sort the list of hit items by rendering order
|
|
Object hobject = null;
|
|
DirtyItem[] items = _hitList.sort();
|
|
// the last element in the array is what we want (assuming there
|
|
// are any items in the array)
|
|
if (items.length > 0) {
|
|
hobject = items[items.length-1].obj;
|
|
}
|
|
|
|
// if this is an object tile, we need to update the hcoords
|
|
if (hobject != null && hobject instanceof ObjectTile) {
|
|
DirtyItem item = items[items.length-1];
|
|
_hcoords.x = item.ox;
|
|
_hcoords.y = item.oy;
|
|
}
|
|
|
|
// if this hover object is different than before, we'll need to be
|
|
// repainted
|
|
if (hobject != _hobject) {
|
|
_hobject = hobject;
|
|
|
|
// we need to repaint if we're highlighting objects, but if
|
|
// we're only highlighting objects with actions, we only need
|
|
// to repaint if the object has an action
|
|
if (_hmode != HIGHLIGHT_NEVER) {
|
|
repaint = (_hmode == HIGHLIGHT_WITH_ACTION) ?
|
|
(_scene.getObjectAction((ObjectTile)_hobject) != null) :
|
|
true;
|
|
}
|
|
}
|
|
|
|
return repaint;
|
|
}
|
|
|
|
/**
|
|
* Adds to the supplied dirty item list, all of the object tiles that
|
|
* are hit by the specified point (meaning the point is contained
|
|
* within their bounds and intersects a non-transparent pixel in the
|
|
* actual object image.
|
|
*/
|
|
protected void getHitObjects (DirtyItemList list, int x, int y)
|
|
{
|
|
Iterator iter = _objects.iterator();
|
|
while (iter.hasNext()) {
|
|
ObjectMetrics metrics = (ObjectMetrics)iter.next();
|
|
// skip polys that don't contain the point
|
|
if (!metrics.bounds.contains(x, y)) {
|
|
continue;
|
|
}
|
|
|
|
// now check that the pixel in the tile image is
|
|
// non-transparent at that point
|
|
int tx = metrics.x, ty = metrics.y;
|
|
Rectangle pbounds = metrics.bounds.getBounds();
|
|
if (!metrics.tile.hitTest(x - pbounds.x, y - pbounds.y)) {
|
|
continue;
|
|
}
|
|
|
|
// we've passed the test, add the object to the list
|
|
list.appendDirtyObject(metrics.tile, metrics.bounds, null,
|
|
tx, ty, pbounds);
|
|
}
|
|
}
|
|
|
|
// documentation inherited
|
|
public void mouseExited (MouseEvent e)
|
|
{
|
|
// clear the highlight tracking data
|
|
_hcoords.setLocation(-1, -1);
|
|
_hobject = null;
|
|
}
|
|
|
|
// documentation inherited
|
|
public Object getHoverObject ()
|
|
{
|
|
return _hobject;
|
|
}
|
|
|
|
/**
|
|
* Returns the tile coordinates of the tile over which the mouse is
|
|
* hovering (which are the origin coordinates in the case of an object
|
|
* tile).
|
|
*/
|
|
public Point getHoverCoords ()
|
|
{
|
|
return _hcoords;
|
|
}
|
|
|
|
/**
|
|
* Converts the supplied screen coordinates into tile coordinates,
|
|
* writing the values into the supplied {@link Point} instance and
|
|
* returning true if the screen coordinates translated into a
|
|
* different set of tile coordinates than were already contained in
|
|
* the point (so that the caller can know to update a highlight, for
|
|
* example).
|
|
*
|
|
* @return true if the tile coordinates have changed.
|
|
*/
|
|
protected boolean updateTileCoords (int sx, int sy, Point tpos)
|
|
{
|
|
Point npos = new Point();
|
|
IsoUtil.screenToTile(_model, sx + _xoff, sy + _yoff, npos);
|
|
|
|
// make sure the new coordinate is both valid and different
|
|
if (_model.isCoordinateValid(npos.x, npos.y) && !tpos.equals(npos)) {
|
|
tpos.setLocation(npos.x, npos.y);
|
|
return true;
|
|
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A class used to cache necessary information on all object tiles in
|
|
* the scene.
|
|
*/
|
|
protected static class ObjectMetrics
|
|
{
|
|
/** The x and y tile coordinates of the object. */
|
|
public int x, y;
|
|
|
|
/** A reference to the object tile itself. */
|
|
public ObjectTile tile;
|
|
|
|
/** The object's bounding rectangle. */
|
|
public Rectangle bounds;
|
|
}
|
|
|
|
/** The sprite manager. */
|
|
protected SpriteManager _spritemgr;
|
|
|
|
/** The scene view model data. */
|
|
protected IsoSceneViewModel _model;
|
|
|
|
/** Our region manager. */
|
|
protected RegionManager _remgr;
|
|
|
|
/** The scene to be displayed. */
|
|
protected DisplayMisoScene _scene;
|
|
|
|
/** Polygon outlines for all of our base tiles. */
|
|
protected HashIntMap _polys;
|
|
|
|
/** Metric information for all of the object tiles. */
|
|
protected ArrayList _objects = new ArrayList();
|
|
|
|
/** The rendering offsets used to support scrolling. */
|
|
protected int _xoff = 0, _yoff = 0;
|
|
|
|
/** The offsets from (0, 0) in tile coordinates to which we have
|
|
* scrolled. */
|
|
protected int _tiledx, _tiledy;
|
|
|
|
/** The dirty tiles that need to be re-painted. */
|
|
protected boolean _dirty[][];
|
|
|
|
/** The number of dirty tiles. */
|
|
protected int _numDirty;
|
|
|
|
/** The dirty sprites and objects that need to be re-painted. */
|
|
protected DirtyItemList _dirtyItems = new DirtyItemList();
|
|
|
|
/** The working sprites list used when calculating dirty regions. */
|
|
protected ArrayList _dirtySprites = new ArrayList();
|
|
|
|
/** Used to collect the list of sprites "hit" by a particular mouse
|
|
* location. */
|
|
protected List _hitSprites = new ArrayList();
|
|
|
|
/** The list that we use to track and sort the items over which the
|
|
* mouse is hovering. */
|
|
protected DirtyItemList _hitList = new DirtyItemList();
|
|
|
|
/** The highlight mode. */
|
|
protected int _hmode = HIGHLIGHT_NEVER;
|
|
|
|
/** The coordinates of the currently highlighted base or object
|
|
* tile. */
|
|
protected Point _hcoords = new Point(-1, -1);
|
|
|
|
/** The object that the mouse is currently hovering over. */
|
|
protected Object _hobject;
|
|
|
|
/** The font to draw tile coordinates. */
|
|
protected Font _font = new Font("Arial", Font.PLAIN, 7);
|
|
|
|
/** The stroke object used to draw highlighted tiles and coordinates. */
|
|
protected BasicStroke _hstroke = new BasicStroke(2);
|
|
|
|
/** The stroke used to draw dirty rectangles. */
|
|
protected static final Stroke DIRTY_RECT_STROKE = new BasicStroke(2);
|
|
}
|