//
// MUPoint.java
// Thud
//
// Copyright (c) 2001-2007 Anthony Parker & the THUD team.
// All rights reserved. See LICENSE.TXT for more information.
//
package net.sourceforge.btthud.data;
import java.awt.geom.Point2D;
/**
* Represents a point on the map grid. These values are unscaled; multiply the
* floating point coordinates by MUConstants.SCALEMAP if you want scaled
* values. Also note that the "hex_z" value is in units of levels, not hexes
* (MUConstants.HEXLEVEL levels = 1 hex unit).
*/
public class MUPoint extends Point2D {
/*
* Some private constants.
*/
private enum HexRegion {
I, II, III, IV, V
}
/*
* Coordinate values. These shouldn't be publicly accessible, since
* the hex_*, hexf_*, and f_* values depend on each other.
*/
protected int hex_x;
protected int hex_y;
protected int hex_z;
protected float hexf_x;
protected float hexf_y;
protected float hexf_z;
protected float f_x;
protected float f_y;
protected float f_z;
/*
* Constructors galore.
*/
public MUPoint () {
hex_x = 0;
hex_y = 0;
hex_z = 0;
hexf_x = 2.0f * MUConstants.ALPHA;
hexf_y = 0.5f;
hexf_z = 0.0f;
f_x = hexf_x;
f_y = hexf_y;
f_z = hexf_z;
}
public MUPoint (final MUPoint p) {
setLocation(p);
}
public MUPoint (final Point2D pt) {
setLocation(pt);
}
public MUPoint (final int hex_x, final int hex_y) {
this(hex_x, hex_y, 0);
}
public MUPoint (final int hex_x, final int hex_y, final int hex_z) {
setHexLocation(hex_x, hex_y, hex_z);
}
public MUPoint (final float f_x, final float f_y) {
this(f_x, f_y, 0);
}
public MUPoint (final float f_x, final float f_y, final float f_z) {
setLocation(f_x, f_y, f_z);
}
public MUPoint (final int hex_x, final int hex_y,
final float rtc, final int btc) {
this(hex_x, hex_y, 0, rtc, btc);
}
public MUPoint (final int hex_x, final int hex_y, final int hex_z,
final float rtc, final int btc) {
setFromCenterLocation(hex_x, hex_y, hex_z, rtc, btc);
}
/*
* MUPoint-specific methods.
*/
public int getHexX () {
return hex_x;
}
public int getHexY () {
return hex_y;
}
public int getHexZ () {
return hex_z;
}
public float getCenterFX() {
return hexf_x;
}
public float getCenterFY() {
return hexf_y;
}
public float getCenterFZ() {
return hexf_z;
}
public float getFX () {
return f_x;
}
public float getFY () {
return f_y;
}
public float getFZ () {
return f_z;
}
public void setHexLocation (final int x, final int y) {
setHexLocation(x, y, 0);
}
public void setHexLocation (final int x, final int y, final int z) {
hex_x = x;
hex_y = y;
hex_z = z;
setHexCenter();
f_x = hexf_x;
f_y = hexf_y;
f_z = hexf_z;
}
public void setFromCenterLocation (final int x, final int y,
final float rtc, final int btc) {
setFromCenterLocation(x, y, 0, rtc, btc);
}
public void setFromCenterLocation (final int x, final int y, final int z,
final float rtc, final int btc) {
// We should never be given values that cause us to leave the
// hex, so we can just go ahead and set our location now.
setHexLocation(x, y, z);
// TODO: We should probably assert/check that the above is
// true. Even if we don't, though, the worst that will happen
// is that the hex coords won't match the (correct) floating
// point coords.
// Compute and apply the offset vector.
final float dirX = (float)Math.sin(Math.toRadians(btc));
final float dirY = (float)-Math.cos(Math.toRadians(btc));
f_x -= rtc * dirX;
f_y -= rtc * dirY;
}
public void setLocation (final float x, final float y, final float z) {
f_x = x;
f_y = y;
f_z = z;
// Set hex_* values from f_* values. This is essentially
// RealCoordToMapCoord() from hcode/btech/mech.utils.c.
//
// The following diagram comes from there, modified slightly:
//
// x 0a 1a 2a 3a 4a 5a
// y ________________________
// 0 | / \
// |IV/ \ III
// | / \
// |/ \________
// 1 |\ I /
// | \ / II
// |V \ /
// | \ /
//
// The original diagram is shifted to combine the two regions
// on the left edge into one. While we do have more regions
// than the original algorithm, the number of operations is the
// same. Meanwhile, we avoid having to pre-shift the
// horizontal hex coordinate.
//
// Another change in the algorithm is that we're more
// consistent about which edges are part of which hexes. The
// topmost, leftmost edges are considered part of a hex, while
// the other three lowermost, rightmost edges are considered
// part of neighboring hexes:
// __
// /
// \
//
// We also don't special case hexes off the left edge of the
// map; like any other hex, user code is expected to check that
// these coordinates are out of bounds. They might even be
// useful for some applications.
// Guess hex coordinates from "repeatable box".
hex_x = (int)Math.floor(f_x / (MUConstants.ALPHA * 6.f)) * 2;
hex_y = (int)Math.floor(f_y);
// Compute "repeatable box" offsets.
final float off_x = f_x / MUConstants.ALPHA - 3 * hex_x;
final float off_y = f_y - hex_y;
// Determine region.
HexRegion region;
switch ((int)off_x) {
case -1:
// This should almost never happen?
System.out.println("Warning: off_x < 0: " + off_x);
case 0:
// [0,1): Region I, IV, or V.
if (off_y < 0.5f) {
// Region I or IV.
if (off_x < 2f * (0.5f - off_y)) {
region = HexRegion.IV;
} else {
region = HexRegion.I;
}
} else {
// Region I or V.
if (off_x < 2f * (off_y - 0.5f)) {
region = HexRegion.V;
} else {
region = HexRegion.I;
}
}
break;
case 1:
case 2:
// [1,3): Region I.
region = HexRegion.I;
break;
case 3:
// [3,4): Region I, II, or III.
if (off_y < 0.5f) {
// Region I or III.
if ((off_x - 3f) < 2f * (off_y - 0.0f)) {
region = HexRegion.I;
} else {
region = HexRegion.III;
}
} else {
// Region I or II.
if ((off_x - 3f) < 2f * (1.0f - off_y)) {
region = HexRegion.I;
} else {
region = HexRegion.II;
}
}
break;
case 6:
// This should almost never happen?
System.out.println("Warning: off_x >= 6: " + off_x);
case 4:
case 5:
// [4,6): Region II or III.
if (off_y < 0.5f) {
region = HexRegion.III;
} else {
region = HexRegion.II;
}
break;
default:
// This should never happen. Really.
throw new Error ("Bad off_x computation: " + off_x);
}
// Adjust hex coordinates.
switch (region) {
case I: // 0 0
break;
case II: // + +
hex_x++;
hex_y++;
break;
case III: // + 0
hex_x++;
break;
case IV: // - 0
hex_x--;
break;
case V: // - +
hex_x--;
hex_y++;
break;
}
// Update hexf_* values.
setHexCenter();
}
/*
* Internal routines.
*/
private void setHexCenter () {
// Set hexf_* values from hex_* values. This is essentially
// MapCoordToRealCoord() from hcode/btech/mech.utils.c.
// TODO: Can use some integer math if we're careful about
// overflow.
hexf_x = (2.f + 3.f * (float)hex_x) * MUConstants.ALPHA;
hexf_y = (((hex_x & 0x1) == 0) ? 0.5f : 0.f) + (float)hex_y;
hexf_z = (float)hex_z / (float)MUConstants.HEXLEVEL;
}
/*
* Required to implement Point2D.
*/
public double getX () {
return f_x;
}
public double getY () {
return f_y;
}
public void setLocation (double x, double y) {
setLocation(x, y, 0.);
}
/*
* Override for correct behavior.
*/
// Bit of a dilemma here, since setting the Z coordinate will create
// the potential for 2D points that aren't equal, yet equal. You may
// want to create a "real" Point2D from this object if this is
// important to you.
//
// Also, there are a lot of ways to define equal (by value) MUPoints.
// This one uses the strictest, where all the floating point
// coordinates must match.
public boolean equals (final Object obj) {
if (obj instanceof MUPoint) {
if (getZ() != ((MUPoint)obj).getZ())
return false;
}
return super.equals(obj);
}
// A better hash code would probably distribute values more evenly, and
// take into account the floating point values.
public int hashCode () {
return hex_z + super.hashCode();
}
public Object clone () {
MUPoint cloned_pt = (MUPoint)super.clone();
cloned_pt.setLocation(this);
return cloned_pt;
}
/*
* Logical 3D extensions of various Point2D methods.
*/
public double getZ () {
return f_z;
}
public void setLocation (double x, double y, double z) {
setLocation((float)x, (float)y, (float)z);
}
public void setLocation (MUPoint p) {
hex_x = p.getHexX();
hex_y = p.getHexY();
hex_z = p.getHexZ();
hexf_x = p.getCenterFX();
hexf_y = p.getCenterFY();
hexf_z = p.getCenterFZ();
f_x = p.getFX();
f_y = p.getFY();
f_z = p.getFZ();
}
public double distance (double PX, double PY, double PZ) {
return Math.sqrt(distanceSq(PX, PY, PZ));
}
public static double distance (double X1, double Y1, double Z1,
double X2, double Y2, double Z2) {
return Math.sqrt(distanceSq(X1, Y1, Z1, X2, Y2, Z2));
}
public double distance (final MUPoint pt) {
return Math.sqrt(distanceSq(pt));
}
public double distanceSq (double PX, double PY, double PZ) {
return distanceSq(getX(), getY(), getZ(), PX, PY, PZ);
}
public static double distanceSq (double X1, double Y1, double Z1,
double X2, double Y2, double Z2) {
return X1 * X2 + Y1 * Y2 + Z1 * Z2;
}
public double distanceSq (final MUPoint pt) {
return pt.distanceSq(getX(), getY(), getZ());
}
// For debugging.
public String toString () {
return "(" + getHexX() + ", " + getHexY() + ", " + getHexZ() + ")+("
+ (getFX() - getCenterFX()) + ", " + (getFY() - getCenterFY()) + ", " + (getFZ() - getCenterFZ()) + ")";
}
}