b6f5317ad2
git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@158 ed5b42cb-e716-0410-a449-f6a68f950b19
519 lines
18 KiB
Java
519 lines
18 KiB
Java
//
|
|
// $Id$
|
|
//
|
|
// Nenya library - tools for developing networked games
|
|
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
|
|
// http://www.threerings.net/code/nenya/
|
|
//
|
|
// This library is free software; you can redistribute it and/or modify it
|
|
// under the terms of the GNU Lesser General Public License as published
|
|
// by the Free Software Foundation; either version 2.1 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
package com.threerings.miso.util;
|
|
|
|
import java.awt.Point;
|
|
import java.awt.Polygon;
|
|
|
|
import com.samskivert.swing.SmartPolygon;
|
|
|
|
import com.threerings.util.DirectionCodes;
|
|
import com.threerings.util.DirectionUtil;
|
|
|
|
import com.threerings.media.util.MathUtil;
|
|
|
|
/**
|
|
* Miscellaneous isometric-display-related utility routines.
|
|
*/
|
|
public class MisoUtil
|
|
implements DirectionCodes
|
|
{
|
|
/**
|
|
* Given two points in screen pixel coordinates, return the
|
|
* compass direction that point B lies in from point A from an
|
|
* isometric perspective.
|
|
*
|
|
* @param ax the x-position of point A.
|
|
* @param ay the y-position of point A.
|
|
* @param bx the x-position of point B.
|
|
* @param by the y-position of point B.
|
|
*
|
|
* @return the direction specified as one of the <code>Sprite</code>
|
|
* class's direction constants.
|
|
*/
|
|
public static int getDirection (
|
|
MisoSceneMetrics metrics, int ax, int ay, int bx, int by)
|
|
{
|
|
Point afpos = new Point(), bfpos = new Point();
|
|
|
|
// convert screen coordinates to full coordinates to get both
|
|
// tile coordinates and fine coordinates
|
|
screenToFull(metrics, ax, ay, afpos);
|
|
screenToFull(metrics, bx, by, bfpos);
|
|
|
|
// pull out the tile coordinates for each point
|
|
int tax = fullToTile(afpos.x);
|
|
int tay = fullToTile(afpos.y);
|
|
|
|
int tbx = fullToTile(bfpos.x);
|
|
int tby = fullToTile(bfpos.y);
|
|
|
|
// compare tile coordinates to determine direction
|
|
int dir = getIsoDirection(tax, tay, tbx, tby);
|
|
if (dir != DirectionCodes.NONE) {
|
|
return dir;
|
|
}
|
|
|
|
// destination point is in the same tile as the
|
|
// origination point, so consider fine coordinates
|
|
|
|
// pull out the fine coordinates for each point
|
|
int fax = afpos.x - (tax * FULL_TILE_FACTOR);
|
|
int fay = afpos.y - (tay * FULL_TILE_FACTOR);
|
|
|
|
int fbx = bfpos.x - (tbx * FULL_TILE_FACTOR);
|
|
int fby = bfpos.y - (tby * FULL_TILE_FACTOR);
|
|
|
|
// compare fine coordinates to determine direction
|
|
dir = getIsoDirection(fax, fay, fbx, fby);
|
|
|
|
// arbitrarily return southwest if fine coords were also equivalent
|
|
return (dir == -1) ? SOUTHWEST : dir;
|
|
}
|
|
|
|
/**
|
|
* Given two points in an isometric coordinate system (in which {@link
|
|
* #NORTH} is in the direction of the negative x-axis and {@link
|
|
* #WEST} in the direction of the negative y-axis), return the compass
|
|
* direction that point B lies in from point A. This method is used
|
|
* to determine direction for both tile coordinates and fine
|
|
* coordinates within a tile, since the coordinate systems are the
|
|
* same.
|
|
*
|
|
* @param ax the x-position of point A.
|
|
* @param ay the y-position of point A.
|
|
* @param bx the x-position of point B.
|
|
* @param by the y-position of point B.
|
|
*
|
|
* @return the direction specified as one of the <code>Sprite</code>
|
|
* class's direction constants, or <code>DirectionCodes.NONE</code> if
|
|
* point B is equivalent to point A.
|
|
*/
|
|
public static int getIsoDirection (int ax, int ay, int bx, int by)
|
|
{
|
|
// head off a div by 0 at the pass..
|
|
if (bx == ax) {
|
|
if (by == ay) {
|
|
return DirectionCodes.NONE;
|
|
}
|
|
return (by < ay) ? EAST : WEST;
|
|
}
|
|
|
|
// figure direction base on the slope of the line
|
|
float slope = ((float) (ay - by)) / ((float) Math.abs(ax - bx));
|
|
if (slope > 2f) {
|
|
return EAST;
|
|
}
|
|
if (slope > .5f) {
|
|
return (bx < ax) ? NORTHEAST : SOUTHEAST;
|
|
}
|
|
if (slope > -.5f) {
|
|
return (bx < ax) ? NORTH : SOUTH;
|
|
}
|
|
if (slope > -2f) {
|
|
return (bx < ax) ? NORTHWEST : SOUTHWEST;
|
|
}
|
|
return WEST;
|
|
}
|
|
|
|
/**
|
|
* Given two points in screen coordinates, return the isometrically
|
|
* projected compass direction that point B lies in from point A.
|
|
*
|
|
* @param ax the x-position of point A.
|
|
* @param ay the y-position of point A.
|
|
* @param bx the x-position of point B.
|
|
* @param by the y-position of point B.
|
|
*
|
|
* @return the direction specified as one of the <code>Sprite</code>
|
|
* class's direction constants, or <code>DirectionCodes.NONE</code> if
|
|
* point B is equivalent to point A.
|
|
*/
|
|
public static int getProjectedIsoDirection (int ax, int ay, int bx, int by)
|
|
{
|
|
return toIsoDirection(DirectionUtil.getDirection(ax, ay, bx, by));
|
|
}
|
|
|
|
/**
|
|
* Converts a non-isometric orientation (where north points toward the
|
|
* top of the screen) to an isometric orientation where north points
|
|
* toward the upper-left corner of the screen.
|
|
*/
|
|
public static int toIsoDirection (int dir)
|
|
{
|
|
if (dir != DirectionCodes.NONE) {
|
|
// rotate the direction clockwise (ie. change SOUTHEAST to
|
|
// SOUTH)
|
|
dir = DirectionUtil.rotateCW(dir, 2);
|
|
}
|
|
return dir;
|
|
}
|
|
|
|
/**
|
|
* Returns the tile coordinate of the given full coordinate.
|
|
*/
|
|
public static int fullToTile (int val)
|
|
{
|
|
return MathUtil.floorDiv(val, FULL_TILE_FACTOR);
|
|
}
|
|
|
|
/**
|
|
* Returns the fine coordinate of the given full coordinate.
|
|
*/
|
|
public static int fullToFine (int val)
|
|
{
|
|
return (val - (fullToTile(val) * FULL_TILE_FACTOR));
|
|
}
|
|
|
|
/**
|
|
* Convert the given screen-based pixel coordinates to their
|
|
* corresponding tile-based coordinates. Converted coordinates
|
|
* are placed in the given point object.
|
|
*
|
|
* @param sx the screen x-position pixel coordinate.
|
|
* @param sy the screen y-position pixel coordinate.
|
|
* @param tpos the point object to place coordinates in.
|
|
*
|
|
* @return the point instance supplied via the <code>tpos</code>
|
|
* parameter.
|
|
*/
|
|
public static Point screenToTile (
|
|
MisoSceneMetrics metrics, int sx, int sy, Point tpos)
|
|
{
|
|
// determine the upper-left of the quadrant that contains our
|
|
// point
|
|
int zx = (int)Math.floor((float)sx / metrics.tilewid);
|
|
int zy = (int)Math.floor((float)sy / metrics.tilehei);
|
|
|
|
// these are the screen coordinates of the tile's top
|
|
int ox = (zx * metrics.tilewid), oy = (zy * metrics.tilehei);
|
|
|
|
// these are the tile coordinates
|
|
tpos.x = zy + zx; tpos.y = zy - zx;
|
|
|
|
// now determine which of the four tiles our point occupies
|
|
int dx = sx - ox, dy = sy - oy;
|
|
|
|
if (Math.round(metrics.slopeY * dx + metrics.tilehei) <= dy) {
|
|
tpos.x += 1;
|
|
}
|
|
|
|
if (Math.round(metrics.slopeX * dx) > dy) {
|
|
tpos.y -= 1;
|
|
}
|
|
|
|
// Log.info("Converted [sx=" + sx + ", sy=" + sy +
|
|
// ", zx=" + zx + ", zy=" + zy +
|
|
// ", ox=" + ox + ", oy=" + oy +
|
|
// ", dx=" + dx + ", dy=" + dy +
|
|
// ", tpos.x=" + tpos.x + ", tpos.y=" + tpos.y + "].");
|
|
return tpos;
|
|
}
|
|
|
|
/**
|
|
* Convert the given tile-based coordinates to their corresponding
|
|
* screen-based pixel coordinates. The screen coordinate for a tile is
|
|
* the upper-left coordinate of the rectangle that bounds the tile
|
|
* polygon. Converted coordinates are placed in the given point
|
|
* object.
|
|
*
|
|
* @param x the tile x-position coordinate.
|
|
* @param y the tile y-position coordinate.
|
|
* @param spos the point object to place coordinates in.
|
|
*
|
|
* @return the point instance supplied via the <code>spos</code>
|
|
* parameter.
|
|
*/
|
|
public static Point tileToScreen (
|
|
MisoSceneMetrics metrics, int x, int y, Point spos)
|
|
{
|
|
spos.x = (x - y - 1) * metrics.tilehwid;
|
|
spos.y = (x + y) * metrics.tilehhei;
|
|
return spos;
|
|
}
|
|
|
|
/**
|
|
* Convert the given fine coordinates to pixel coordinates within
|
|
* the containing tile. Converted coordinates are placed in the
|
|
* given point object.
|
|
*
|
|
* @param x the x-position fine coordinate.
|
|
* @param y the y-position fine coordinate.
|
|
* @param ppos the point object to place coordinates in.
|
|
*/
|
|
public static void fineToPixel (
|
|
MisoSceneMetrics metrics, int x, int y, Point ppos)
|
|
{
|
|
ppos.x = metrics.tilehwid + ((x - y) * metrics.finehwid);
|
|
ppos.y = (x + y) * metrics.finehhei;
|
|
}
|
|
|
|
/**
|
|
* Convert the given pixel coordinates, whose origin is at the
|
|
* top-left of a tile's containing rectangle, to fine coordinates
|
|
* within that tile. Converted coordinates are placed in the
|
|
* given point object.
|
|
*
|
|
* @param x the x-position pixel coordinate.
|
|
* @param y the y-position pixel coordinate.
|
|
* @param fpos the point object to place coordinates in.
|
|
*/
|
|
public static void pixelToFine (
|
|
MisoSceneMetrics metrics, int x, int y, Point fpos)
|
|
{
|
|
// calculate line parallel to the y-axis (from the given
|
|
// x/y-pos to the x-axis)
|
|
float bY = y - (metrics.fineSlopeY * x);
|
|
|
|
// determine intersection of x- and y-axis lines
|
|
int crossx = (int)((bY - metrics.fineBX) /
|
|
(metrics.fineSlopeX - metrics.fineSlopeY));
|
|
int crossy = (int)((metrics.fineSlopeY * crossx) + bY);
|
|
|
|
// TODO: final position should check distance between our
|
|
// position and the surrounding fine coords and return the
|
|
// actual closest fine coord, rather than just dividing.
|
|
|
|
// determine distance along the x-axis
|
|
float xdist = MathUtil.distance(metrics.tilehwid, 0, crossx, crossy);
|
|
fpos.x = (int)(xdist / metrics.finelen);
|
|
|
|
// determine distance along the y-axis
|
|
float ydist = MathUtil.distance(x, y, crossx, crossy);
|
|
fpos.y = (int)(ydist / metrics.finelen);
|
|
|
|
// Log.info("Pixel to fine " + StringUtil.coordsToString(x, y) +
|
|
// " -> " + StringUtil.toString(fpos) + ".");
|
|
}
|
|
|
|
/**
|
|
* Convert the given screen-based pixel coordinates to full
|
|
* scene-based coordinates that include both the tile coordinates
|
|
* and the fine coordinates in each dimension. Converted
|
|
* coordinates are placed in the given point object.
|
|
*
|
|
* @param sx the screen x-position pixel coordinate.
|
|
* @param sy the screen y-position pixel coordinate.
|
|
* @param fpos the point object to place coordinates in.
|
|
*
|
|
* @return the point passed in to receive the coordinates.
|
|
*/
|
|
public static Point screenToFull (
|
|
MisoSceneMetrics metrics, int sx, int sy, Point fpos)
|
|
{
|
|
// get the tile coordinates
|
|
Point tpos = new Point();
|
|
screenToTile(metrics, sx, sy, tpos);
|
|
|
|
// get the screen coordinates for the containing tile
|
|
Point spos = tileToScreen(metrics, tpos.x, tpos.y, new Point());
|
|
|
|
// Log.info("Screen to full " +
|
|
// "[screen=" + StringUtil.coordsToString(sx, sy) +
|
|
// ", tpos=" + StringUtil.toString(tpos) +
|
|
// ", spos=" + StringUtil.toString(spos) +
|
|
// ", fpix=" + StringUtil.coordsToString(
|
|
// sx-spos.x, sy-spos.y) + "].");
|
|
|
|
// get the fine coordinates within the containing tile
|
|
pixelToFine(metrics, sx - spos.x, sy - spos.y, fpos);
|
|
|
|
// toss in the tile coordinates for good measure
|
|
fpos.x += (tpos.x * FULL_TILE_FACTOR);
|
|
fpos.y += (tpos.y * FULL_TILE_FACTOR);
|
|
|
|
return fpos;
|
|
}
|
|
|
|
/**
|
|
* Convert the given full coordinates to screen-based pixel
|
|
* coordinates. Converted coordinates are placed in the given
|
|
* point object.
|
|
*
|
|
* @param x the x-position full coordinate.
|
|
* @param y the y-position full coordinate.
|
|
* @param spos the point object to place coordinates in.
|
|
*
|
|
* @return the point passed in to receive the coordinates.
|
|
*/
|
|
public static Point fullToScreen (
|
|
MisoSceneMetrics metrics, int x, int y, Point spos)
|
|
{
|
|
// get the tile screen position
|
|
int tx = fullToTile(x), ty = fullToTile(y);
|
|
Point tspos = tileToScreen(metrics, tx, ty, new Point());
|
|
|
|
// get the pixel position of the fine coords within the tile
|
|
Point ppos = new Point();
|
|
int fx = x - (tx * FULL_TILE_FACTOR), fy = y - (ty * FULL_TILE_FACTOR);
|
|
fineToPixel(metrics, fx, fy, ppos);
|
|
|
|
// final position is tile position offset by fine position
|
|
spos.x = tspos.x + ppos.x;
|
|
spos.y = tspos.y + ppos.y;
|
|
|
|
return spos;
|
|
}
|
|
|
|
/**
|
|
* Converts the given fine coordinate to a full coordinate (a tile
|
|
* coordinate plus a fine coordinate remainder). The fine coordinate
|
|
* is assumed to be relative to tile <code>(0, 0)</code>.
|
|
*/
|
|
public static int fineToFull (MisoSceneMetrics metrics, int fine)
|
|
{
|
|
return toFull(fine / metrics.finegran, fine % metrics.finegran);
|
|
}
|
|
|
|
/**
|
|
* Composes the supplied tile coordinate and fine coordinate offset
|
|
* into a full coordinate.
|
|
*/
|
|
public static int toFull (int tile, int fine)
|
|
{
|
|
return tile * FULL_TILE_FACTOR + fine;
|
|
}
|
|
|
|
/**
|
|
* Return a polygon framing the specified tile.
|
|
*
|
|
* @param x the tile x-position coordinate.
|
|
* @param y the tile y-position coordinate.
|
|
*/
|
|
public static Polygon getTilePolygon (
|
|
MisoSceneMetrics metrics, int x, int y)
|
|
{
|
|
return getFootprintPolygon(metrics, x, y, 1, 1);
|
|
}
|
|
|
|
/**
|
|
* Return a screen-coordinates polygon framing the two specified
|
|
* tile-coordinate points.
|
|
*/
|
|
public static Polygon getMultiTilePolygon (MisoSceneMetrics metrics,
|
|
Point sp1, Point sp2)
|
|
{
|
|
int x = Math.min(sp1.x, sp2.x), y = Math.min(sp1.y, sp2.y);
|
|
int width = Math.abs(sp1.x-sp2.x)+1, height = Math.abs(sp1.y-sp2.y)+1;
|
|
return getFootprintPolygon(metrics, x, y, width, height);
|
|
}
|
|
|
|
/**
|
|
* Returns a polygon framing the specified scene footprint.
|
|
*
|
|
* @param x the x tile coordinate of the "upper-left" of the footprint.
|
|
* @param y the y tile coordinate of the "upper-left" of the footprint.
|
|
* @param width the width in tiles of the footprint.
|
|
* @param height the height in tiles of the footprint.
|
|
*/
|
|
public static Polygon getFootprintPolygon (
|
|
MisoSceneMetrics metrics, int x, int y, int width, int height)
|
|
{
|
|
SmartPolygon footprint = new SmartPolygon();
|
|
Point tpos = MisoUtil.tileToScreen(metrics, x, y, new Point());
|
|
|
|
// start with top-center point
|
|
int rx = tpos.x + metrics.tilehwid, ry = tpos.y;
|
|
footprint.addPoint(rx, ry);
|
|
// right point
|
|
rx += width * metrics.tilehwid;
|
|
ry += width * metrics.tilehhei;
|
|
footprint.addPoint(rx, ry);
|
|
// bottom-center point
|
|
rx -= height * metrics.tilehwid;
|
|
ry += height * metrics.tilehhei;
|
|
footprint.addPoint(rx, ry);
|
|
// left point
|
|
rx -= width * metrics.tilehwid;
|
|
ry -= width * metrics.tilehhei;
|
|
footprint.addPoint(rx, ry);
|
|
// end with top-center point
|
|
rx += height * metrics.tilehwid;
|
|
ry -= height * metrics.tilehhei;
|
|
footprint.addPoint(rx, ry);
|
|
|
|
return footprint;
|
|
}
|
|
|
|
/**
|
|
* Adds the supplied fine coordinates to the supplied tile coordinates
|
|
* to compute full coordinates.
|
|
*
|
|
* @return the point object supplied as <code>full</code>.
|
|
*/
|
|
public static Point tilePlusFineToFull (MisoSceneMetrics metrics,
|
|
int tileX, int tileY,
|
|
int fineX, int fineY,
|
|
Point full)
|
|
{
|
|
int dtx = fineX / metrics.finegran;
|
|
int dty = fineY / metrics.finegran;
|
|
int fx = fineX - dtx * metrics.finegran;
|
|
if (fx < 0) {
|
|
dtx--;
|
|
fx += metrics.finegran;
|
|
}
|
|
int fy = fineY - dty * metrics.finegran;
|
|
if (fy < 0) {
|
|
dty--;
|
|
fy += metrics.finegran;
|
|
}
|
|
|
|
full.x = toFull(tileX + dtx, fx);
|
|
full.y = toFull(tileY + dty, fy);
|
|
return full;
|
|
}
|
|
|
|
/**
|
|
* Turns x and y scene coordinates into an integer key.
|
|
*
|
|
* @return the hash key, given x and y.
|
|
*/
|
|
public static final int coordsToKey (int x, int y)
|
|
{
|
|
return ((y << 16) & (0xFFFF0000)) | (x & 0xFFFF);
|
|
}
|
|
|
|
/**
|
|
* Gets the x coordinate from an integer hash key.
|
|
*
|
|
* @return the x coordinate.
|
|
*/
|
|
public static final int xCoordFromKey (int key)
|
|
{
|
|
return (key & 0xFFFF);
|
|
}
|
|
|
|
/**
|
|
* Gets the y coordinate from an integer hash key.
|
|
*
|
|
* @return the y coordinate from the hash key.
|
|
*/
|
|
public static final int yCoordFromKey (int key)
|
|
{
|
|
return ((key >> 16) & 0xFFFF);
|
|
}
|
|
|
|
/** Multiplication factor to embed tile coords in full coords. */
|
|
protected static final int FULL_TILE_FACTOR = 100;
|
|
}
|