//
// HexShape.java
// Thud
//
// Created by Anthony Parker on Wed Mar 27 2002.
// Copyright (c) 2001-2002 Anthony Parker. All rights reserved.
// Please see LICENSE.TXT for more information.
//
//
package btthud.ui;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.font.*;
import java.awt.image.*;
public class HexShape implements Shape {
public static final int HEX_CENTER = 0; // Center of a hex
public static final int HEX_UPPER_LEFT = 1; // Upper-left corner of a hex
public static final int HEX_LEFT = 2; // Leftmost point of a hex
public static final int HEX_UPPER_LEFT_BOUND = 3; // Upper-left boundary of a hex (outside, for a bounding box)
public static final int HEX_LOWER_RIGHT_BOUND = 4; // Lower-right boundary of a hex (outside, for a bounding box)
float x[] = {0f, 0f, 0f, 0f, 0f, 0f};
float y[] = {0f, 0f, 0f, 0f, 0f, 0f};
static final float tan60 = (float) Math.tan(MUXMapComponent.toRadians(60.0f));
static final float sin60 = (float) Math.sin(MUXMapComponent.toRadians(60.0f));
float h = 40;
float w;
float l;
GeneralPath gp;
public HexShape(float h)
{
this.h = h;
w = -h / (2 * sin60);
l = h / (2 * tan60);
gp = new GeneralPath(GeneralPath.WIND_NON_ZERO, 6);
// Figure out the proper coordinates
// top left
x[0] = l;
y[0] = 0f;
// leftmost side
x[1] = 0f;
y[1] = h / 2f;
// bottom left
x[2] = l;
y[2] = h;
// bottom right
x[3] = w + l;
y[3] = h;
// rightmost side
x[4] = w + (2f * l);
y[4] = h / 2f;
// top right
x[5] = w + l;
y[5] = 0f;
gp.moveTo(x[0], y[0]);
for (int i = 1; i < 6; i++)
gp.lineTo(x[i], y[i]);
gp.lineTo(x[0], y[0]);
}
public double getX(int i)
{
if (i < 0 || i > 5)
return 0;
else
return x[i];
}
public double getY(int i)
{
if (i < 0 || i > 5)
return 0;
else
return y[i];
}
// ------------------
/*
Definition of insideness: A point is considered to lie inside a Shape if and only if:
� it lies completely inside theShape boundary or
� it lies exactly on the Shape boundary and the space immediately adjacent to the point in the increasing X direction is entirely inside the boundary or
� it lies exactly on a horizontal boundary segment and the space immediately adjacent to the point in the increasing Y direction is inside the boundary.
*/
public boolean contains(double x, double y)
{
return (gp.contains(x, y));
}
public boolean contains(double x, double y, double w, double h)
{
return (gp.contains(x, y, w, h));
}
public boolean contains(Rectangle2D r)
{
return (gp.contains(r));
}
public boolean contains(Point2D p)
{
return (gp.contains(p));
}
// ---------------
public Rectangle getBounds()
{
return (gp.getBounds());
}
public Rectangle2D getBounds2D()
{
return (gp.getBounds2D());
}
/**
* Gives us the real coordinates (appropriate for drawing in a window) of a specified hex at a certain height.
* If the center flag is true, then it will give us the exact center of the hex. Otherwise, it returns the upper-left corner.
* -> __
* / \ upper left corner of hex
* \__/
*
* __
* /..\ .. = w (width of hex at narrowest point)
* \__/
*
* _____
* /| |\ _ = l (2*l + w = width of hex at widest point)
* /_| |_\
* \ | | /
* \|___|/
*
* __
* /+ \ +
* \+_/ + = h (height of hex at tallest point)
*
* @param x The x coordinate, in hexes.
* @param y The y coordinate, in hexes.
* @param h The height of each hex
* @param center HEX_CENTER for center, HEX_LEFT for the leftmost point of the hex, HEX_UPPER_LEFT for upper-left corner.
*/
public Point2D hexToReal(int x, int y, int center)
{
Point2D.Float p = new Point2D.Float();
hexToReal(x, y, center, p);
return p;
}
/**
* Only calculates the X part of a hex
*/
public float hexToRealXPart(int x, int y, int center)
{
// If we want the leftmost point, we don't add the offset of l
// If we want the center, we add an offset of w/2
// If we want the upper-left, we start with offset of l and don't add the offset of w/2
// Add (w + l) * desired_x_coord...
float xoffset;
xoffset = (float)x * (w + l);
if (center == HEX_CENTER)
{
xoffset += l + (w / 2f);
}
else if (center == HEX_UPPER_LEFT)
{
xoffset += l;
}
else if (center == HEX_LOWER_RIGHT_BOUND)
{
xoffset += (2 * l) + w;
}
return xoffset;
}
/**
* Only calculates the Y part of a hex
*/
public float hexToRealYPart(int x, int y, int center)
{
// If we want the center or leftmost point we add an offset of h/2
float yoffset;
yoffset = (float)y * h;
if (x % 2 == 0)
yoffset += h / 2f;
if (center == HEX_CENTER || center == HEX_LEFT)
{
yoffset += h / 2f;
}
else if (center == HEX_LOWER_RIGHT_BOUND)
{
yoffset += h;
}
return yoffset;
}
// For saving memory - use this method and pass in a Point instead of creating a new one
public void hexToReal(int x, int y, int center, Point2D pt)
{
pt.setLocation(hexToRealXPart(x, y, center), hexToRealYPart(x, y, center));
}
/**
* Gives us a box which encloses a particular hex (for drawing/clipping purposes)
*/
public Rectangle2D hexToRect(int x, int y)
{
Rectangle2D hexRect = new Rectangle2D.Double();
hexToRect(x, y, hexRect);
return hexRect;
}
public void hexToRect(int x, int y, Rectangle2D r)
{
r.setFrame(hexToRealXPart(x, y, HEX_UPPER_LEFT_BOUND),
hexToRealYPart(x, y, HEX_UPPER_LEFT_BOUND),
(2f * l) + w,
h);
}
/**
* Gives us a box which encloses a particular hex and all surrounding hexes
* Useful for redrawing when something has been drawn on a border between hexes
*/
public void hexToExpandedRect(int x, int y, Rectangle2D r)
{
hexToRect(x, y, r);
if (x % 2 == 0)
{
r.add(hexToRealXPart(x-1, y , HEX_UPPER_LEFT_BOUND),
hexToRealYPart(x-1, y , HEX_UPPER_LEFT_BOUND));
r.add(hexToRealXPart(x+1, y+1, HEX_LOWER_RIGHT_BOUND),
hexToRealYPart(x+1, y+1, HEX_LOWER_RIGHT_BOUND));
}
else
{
r.add(hexToRealXPart(x-1, y-1, HEX_UPPER_LEFT_BOUND),
hexToRealYPart(x-1, y-1, HEX_UPPER_LEFT_BOUND));
r.add(hexToRealXPart(x+1, y , HEX_LOWER_RIGHT_BOUND),
hexToRealYPart(x+1, y , HEX_LOWER_RIGHT_BOUND));
}
r.add(hexToRealXPart(x , y-1, HEX_UPPER_LEFT_BOUND),
hexToRealYPart(x , y-1, HEX_UPPER_LEFT_BOUND));
r.add(hexToRealXPart(x , y+1, HEX_LOWER_RIGHT_BOUND),
hexToRealYPart(x , y+1, HEX_LOWER_RIGHT_BOUND));
}
/*
This picture represents the smallest repeatable area in the hex map.
________________________
| \ /|
| \ III / |
| \ /IV|
| \________/ |
| I / \ |
| / II \ |
| / \ |
|________/______________\|
Sizes:
I (at top, bottom): w
I (at middle): w + l
II (at top): w
III (at bottom): w + l + l
III (at top): w + l + l
III (at bottom): w
IV (at top, bottom): 0
IV (at middle): l
First, we take the point the mouse hit and see what 'repeatable box' it's in.
The whole box is 'h' high and 'w + l + w + l' wide.
We then figure out the point within this box that the mouse hit.
Next, we split the box into sections for comparison:
_______1___2_______3____
| 1\ 2 3 /|
| 1 \ 2 3 / |
| 1 \2 3 / |
4444444444444444444444444|
| 1 2 3\ |
| 1 /2 3 \ |
| 1 / 2 3 \ |
|_______1/__2_______3___\|
This allows us to make quick checks to see which general section a coordinate is in.
Now we determine which hex they clicked in based on which section of the box they clicked.
Note: We multiply boxX by 2 because each box contains 2 'x' columns.
I: (boxX*2, boxY)
II: (boxX*2 + 1, boxY + 1)
III: (boxX*2 + 1, boxY)
IV: (boxX*2 + 2, boxY)
*/
// Get the hex coordinate from a real coordinate
// This code is based (loosely) on the code from mech.util.c in the 3030MUX source
public Point realToHex(int mX, int mY)
{
int boxX, boxY;
double x, y;
int section = 0;
double beta = (h / 2.0d) / l;
// So we start out with the first repeatable box at the proper location
double rX = mX - l;
double rY = mY - h/2;
// ******* Need to handle special cases: rX < l (before adjustment), rY < h/2
// Figure out the x and y-coordinates of the 'repeatable box' we're in
boxX = (int) Math.floor(rX / (double) (l*2 + w*2));
boxY = (int) Math.floor(rY / (double) h);
// And the offsets inside the box, from the left edge
x = rX - (boxX * (l*2 + w*2));
y = rY - (boxY * h);
// Start checking the coordinates
if (x < w)
{
// L1
section = 1;
}
else if (x > (w + l) && x < (2*w + l))
{
// x > L2 && x < L3
if (y < h/2)
section = 3;
else
section = 2;
}
else if (x >= w && x <= (w + l))
{
// between the for-sure I and the for-sure II or III
if (y < h/2)
{
if (y >= beta * (x - w))
section = 1;
else
section = 3;
}
else
{
if (y - h/2 >= -beta * (x - w) + h/2)
section = 2;
else
section = 1;
}
}
else
{
// II, III, or IV
if (y < h/2)
{
if (y >= -beta * (x - (2*w + l)) + h/2)
section = 4;
else
section = 3;
}
else
{
if (y - h/2 >= beta * (x - (2*w + l)))
section = 2;
else
section = 4;
}
}
// Return the proper data
if (section == 1)
return new Point(boxX * 2, boxY);
else if (section == 2)
return new Point(boxX * 2 + 1, boxY + 1);
else if (section == 3)
return new Point(boxX * 2 + 1, boxY);
else if (section == 4)
return new Point(boxX * 2 + 2, boxY);
else // Bad hex, maybe
return new Point(-1, -1);
}
/*
* Tests if a given hex intersects a particular Rectangle
*/
public boolean hexIntersectsClipRect(int x, int y, Rectangle r)
{
return r.intersects(hexToRealXPart(x, y, HexShape.HEX_UPPER_LEFT),
hexToRealYPart(x, y, HexShape.HEX_UPPER_LEFT), w + 2f * l, h);
}
// ----------------
public PathIterator getPathIterator(AffineTransform at)
{
return (gp.getPathIterator(at));
}
public PathIterator getPathIterator(AffineTransform at, double flatness)
{
return (gp.getPathIterator(at, flatness));
}
// ----------------
public boolean intersects(double x, double y, double w, double h)
{
return (gp.intersects(x, y, w, h));
}
public boolean intersects(Rectangle2D r)
{
return (gp.intersects(r));
}
}