Hexagon demo
/*
* @(#)Hexagon.java 1.0 Apr 27, 2008
*
* The MIT License
*
* Copyright (c) 2008 Malachi de AElfweald <malachid@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
//package org.eoti.awt.geom;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RectangularShape;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import javax.imageio.ImageIO;
public class Hexagon
extends Polygon
{
public enum Corner
{
Top, UpperRight, LowerRight, Bottom, LowerLeft, UpperLeft
}
public enum Direction
{
NorthEast, East, SouthEast, SouthWest, West, NorthWest
}
public enum BoundingCorner
{
TopLeft, TopRight, BottomLeft, BottomRight
}
protected Point2D center;
protected int size;
protected double height, width;
protected double hOffset, wOffset;
protected Rectangle2D boundingBox;
protected HashMap<Corner,Point2D> corners;
protected HashMap<BoundingCorner,Point2D> boundingCorners;
public Hexagon(double centerX, double centerY, int size)
{
this(new Point2D.Double(centerX,centerY), size);
}
public Hexagon(Point2D center, int size)
{
super();
this.center = center;
this.size = size;
/**
* MATH:
* With the hexagon points={TOP, UPPER-RIGHT, LOWER-RIGHT, BOTTOM, LOWER-LEFT, UPPER-RIGHT}
* size = length of each actual segment of the hexagon
* width = bounding rectangle width
* height = bounding rectangle height
* each inner angle is 120 degrees
* outside angles are 30-60-90 triangles with 30 near TOP and BOTTOM and 60 near sides
* hOffset = height difference between 'size' edge and bounding rectangle corners
* wOffset = width difference between TOP/BOTTOM points and bounding rectangle corners
*/
double thirtyDegrees = Math.toRadians(30);
hOffset = Math.sin(thirtyDegrees) * size;
wOffset = Math.cos(thirtyDegrees) * size;
height = (2 * hOffset) + size;
width = (2 * wOffset);
double left = center.getX() - (width/2);
double right = center.getX() + (width/2);
double top = center.getY() - (height/2);
double bottom = center.getY() + (height/2);
boundingBox = new Rectangle2D.Double(left, top, width, height);
boundingCorners = new HashMap<BoundingCorner, Point2D>();
boundingCorners.put(BoundingCorner.TopRight, new Point2D.Double(right, top));
boundingCorners.put(BoundingCorner.TopLeft, new Point2D.Double(left, top));
boundingCorners.put(BoundingCorner.BottomRight, new Point2D.Double(right, bottom));
boundingCorners.put(BoundingCorner.BottomLeft, new Point2D.Double(left, bottom));
corners = new HashMap<Corner,Point2D>();
corners.put(Corner.Top, new Point2D.Double(center.getX(), top));
corners.put(Corner.UpperRight, new Point2D.Double(right, (top + hOffset)));
corners.put(Corner.LowerRight, new Point2D.Double(right, (bottom - hOffset)));
corners.put(Corner.Bottom, new Point2D.Double(center.getX(), bottom));
corners.put(Corner.LowerLeft, new Point2D.Double(left, (bottom - hOffset)));
corners.put(Corner.UpperLeft, new Point2D.Double(left, (top + hOffset)));
for(Corner corner : Corner.values())
{
Point2D p2d = corners.get(corner);
addPoint((int)p2d.getX(), (int)p2d.getY());
}
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("[Hexagon: ");
sb.append(String.format(
"[Size: %d]",
size
));
sb.append(String.format(
"[Height: %2.1f, hOffset: %2.1f]",
height, hOffset
));
sb.append(String.format(
"[Width: %2.1f, wOffset: %2.1f]",
width, wOffset
));
sb.append(String.format(
"[Center: %2.1fx%2.1f]",
center.getX(), center.getY()
));
sb.append("[Corners: ");
for(Corner corner : Corner.values())
{
Point2D p2d = corners.get(corner);
sb.append(String.format(
"[%s: %2.1fx%2.1f]",
corner, p2d.getX(), p2d.getY()
));
}
sb.append("]");
sb.append("[Bounds: ");
for(BoundingCorner corner : BoundingCorner.values())
{
Point2D p2d = boundingCorners.get(corner);
sb.append(String.format(
"[%s: %2.1fx%2.1f]",
corner, p2d.getX(), p2d.getY()
));
}
sb.append("]");
sb.append(String.format(
"[BoundingBox: %2.1fx%2.1f to %2.1fx%2.1f]",
boundingBox.getMinX(),
boundingBox.getMinY(),
boundingBox.getMaxX(),
boundingBox.getMaxY()
));
sb.append("]");
return sb.toString();
}
public Point2D getCenter(){return center;}
public int getSize(){return size;}
public double getHeight(){return height;}
public double getWidth(){return width;}
@Override
public Rectangle2D getBounds2D(){return boundingBox;}
public double getHeightOffset(){return hOffset;}
public double getWidthOffset(){return wOffset;}
//public HashMap<Corner,Point2D> getCorners(){return corners;}
//public HashMap<BoundingCorner,Point2D> getBoundingCorners(){return boundingCorners;}
public Hexagon resize(int newSize)
{
return new Hexagon(center, newSize);
}
@Override
public Object clone()
{
return new Hexagon(center, size);
}
public Point2D getHexPoint(Corner corner)
{
return corners.get(corner);
}
public Point2D getBoundPoint(BoundingCorner corner)
{
return boundingCorners.get(corner);
}
public void translate(double deltaX, double deltaY)
{
translate((int)deltaX, (int)deltaY);
}
@Override
public void translate(int deltaX, int deltaY)
{
super.translate(deltaX, deltaY);
boundingBox = super.getBounds2D();
double top = boundingBox.getMinY();
double left = boundingBox.getMinX();
double bottom = boundingBox.getMaxY();
double right = boundingBox.getMaxX();
double centerX = boundingBox.getCenterX();
double centerY = boundingBox.getCenterY();
center = new Point2D.Double(centerX, centerY);
boundingCorners.put(BoundingCorner.TopRight, new Point2D.Double(right, top));
boundingCorners.put(BoundingCorner.TopLeft, new Point2D.Double(left, top));
boundingCorners.put(BoundingCorner.BottomRight, new Point2D.Double(right, bottom));
boundingCorners.put(BoundingCorner.BottomLeft, new Point2D.Double(left, bottom));
corners.put(Corner.Top, new Point2D.Double(center.getX(), top));
corners.put(Corner.UpperRight, new Point2D.Double(right, (top + hOffset)));
corners.put(Corner.LowerRight, new Point2D.Double(right, (bottom - hOffset)));
corners.put(Corner.Bottom, new Point2D.Double(center.getX(), bottom));
corners.put(Corner.LowerLeft, new Point2D.Double(left, (bottom - hOffset)));
corners.put(Corner.UpperLeft, new Point2D.Double(left, (top + hOffset)));
}
public void align(Rectangle2D bounds, Direction direction)
{
// these are defined here INSTEAD of in the switch, or it won't compile
Point2D newTopRight, newTopLeft, newBottomRight, newBottomLeft;
Point2D oldTopRight, oldTopLeft, oldBottomRight, oldBottomLeft;
switch(direction)
{
case NorthEast:
newTopRight = new Point2D.Double(bounds.getMaxX(), bounds.getMinY());
oldTopRight = boundingCorners.get(BoundingCorner.TopRight);
translate(
newTopRight.getX() - oldTopRight.getX(), // deltaX
newTopRight.getY() - oldTopRight.getY() // deltaY
);
break;
case East:
newTopRight = new Point2D.Double(bounds.getMaxX(), bounds.getMinY());
oldTopRight = boundingCorners.get(BoundingCorner.TopRight);
translate(
newTopRight.getX() - oldTopRight.getX(), // deltaX
0 // deltaY
);
break;
case SouthEast:
newBottomRight = new Point2D.Double(bounds.getMaxX(), bounds.getMaxY());
oldBottomRight = boundingCorners.get(BoundingCorner.BottomRight);
translate(
newBottomRight.getX() - oldBottomRight.getX(), // deltaX
newBottomRight.getY() - oldBottomRight.getY() // deltaY
);
break;
case SouthWest:
newBottomLeft = new Point2D.Double(bounds.getMinX(), bounds.getMaxY());
oldBottomLeft = boundingCorners.get(BoundingCorner.BottomLeft);
translate(
newBottomLeft.getX() - oldBottomLeft.getX(), // deltaX
newBottomLeft.getY() - oldBottomLeft.getY() // deltaY
);
break;
case West:
newTopLeft = new Point2D.Double(bounds.getMinX(), bounds.getMinY());
oldTopLeft = boundingCorners.get(BoundingCorner.TopLeft);
translate(
newTopLeft.getX() - oldTopLeft.getX(), // deltaX
0 // deltaY
);
break;
case NorthWest:
newTopLeft = new Point2D.Double(bounds.getMinX(), bounds.getMinY());
oldTopLeft = boundingCorners.get(BoundingCorner.TopLeft);
translate(
newTopLeft.getX() - oldTopLeft.getX(), // deltaX
newTopLeft.getY() - oldTopLeft.getY() // deltaY
);
break;
}
}
public void attach(Hexagon toTranslate, Direction direction)
{
double horSize = size + hOffset;
/**
* To start with, we need to know toTranslate's position RELATIVE to ours
*/
Point2D topLeft = getBoundPoint(BoundingCorner.TopLeft);
Point2D toTranslateTopLeft = toTranslate.getBoundPoint(BoundingCorner.TopLeft);
double deltaX = topLeft.getX() - toTranslateTopLeft.getX();
double deltaY = topLeft.getY() - toTranslateTopLeft.getY();
// that should be enough to line them up exactly... now offset it...
switch(direction)
{
case NorthEast:
deltaX += wOffset;
deltaY -= horSize;
break;
case East:
deltaX += width;
break;
case SouthEast:
deltaX += wOffset;
deltaY += horSize;
break;
case SouthWest:
deltaX -= wOffset;
deltaY += horSize;
break;
case West:
deltaX -= width;
break;
case NorthWest:
deltaX -= wOffset;
deltaY -= horSize;
break;
}
toTranslate.translate(deltaX, deltaY);
}
public static class CreateTile
{
public static void main(String[] args)
{
if(args.length != 2)
{
System.out.println("USAGE: java org.eoti.awt.geom.Hexagon$CreateTile size filename");
System.out.print("Output file can be: ");
for(String name : ImageIO.getWriterFormatNames())
System.out.print(name + ",");
System.out.println();
System.exit(0);
}
try{
int size = Integer.parseInt(args[0]);
Hexagon hex = new Hexagon(0, 0, size);
Rectangle2D newBounds = new Rectangle2D.Double(0, 0, hex.getWidth(), hex.getHeight());
hex.align(newBounds, Direction.NorthWest);
File file = GraphicsUtil.write(args[1], hex);
if(file == null)
System.out.println("Error creating file");
else
System.out.println("Hexagon tile created: " + file.getAbsolutePath());
}catch(Exception e){
System.out.format("Exception: %s", e.getMessage());
e.printStackTrace();
}
}
}
}
class GraphicsUtil
{
protected Graphics g;
protected ImageObserver observer;
public GraphicsUtil(Graphics g, ImageObserver observer)
{
this.g = g;
this.observer = observer;
}
public enum Align
{
North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest, Center
}
public BufferedImage drawCodePoint(char codePoint, int width, int height)
{
return drawCodePoint(codePoint, width, height, g.getFont(), g.getColor());
}
public static BufferedImage drawCodePoint(char codePoint, int width, int height, Font font, Color color)
{
BufferedImage img = createImage(width, height);
Graphics2D g2 = img.createGraphics();
String text = "" + codePoint;
g2.setColor(color);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
GlyphVector gv = font.createGlyphVector(g2.getFontRenderContext(), text);
g2.drawGlyphVector(gv, 0f, (float)gv.getGlyphMetrics(0).getBounds2D().getHeight());
return img;
}
public static BufferedImage createImage(Dimension size)
{
return createImage(size.width, size.height);
}
public static BufferedImage createImage(int width, int height)
{
return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
public void drawImage(BufferedImage img)
{
drawImage(g, img, observer);
}
public static void drawImage(Graphics g, BufferedImage img)
{
drawImage(g, img, (ImageObserver)null);
}
public static void drawImage(Graphics g, BufferedImage img, ImageObserver observer)
{
Graphics2D g2 = (Graphics2D)g;
g2.drawImage(img, 0, 0, img.getWidth(), img.getHeight(), observer);
}
public void drawImage(BufferedImage img, Rectangle2D bounds)
{
drawImage(g, img, bounds, observer);
}
public static void drawImage(Graphics g, BufferedImage img, Rectangle2D bounds)
{
drawImage(g, img, bounds, null);
}
public static void drawImage(Graphics g, BufferedImage img, Rectangle2D bounds, ImageObserver observer)
{
Graphics2D g2 = (Graphics2D)g;
g2.drawImage(
img, // what to draw
(int)bounds.getMinX(), // dest left
(int)bounds.getMinY(), // dest top
(int)bounds.getMaxX(), // dest right
(int)bounds.getMaxY(), // dest bottom
0, // src left
0, // src top
img.getWidth(), // src right
img.getHeight(), // src bottom
observer // to notify of image updates
);
}
public static Rectangle2D contract(RectangularShape rect, double amount)
{
return contract(rect, amount, amount);
}
public static Rectangle2D contract(RectangularShape rect, double amountX, double amountY)
{
return new Rectangle2D.Double(
rect.getX() + amountX,
rect.getY() + amountY,
rect.getWidth() - (2*amountX),
rect.getHeight() - (2*amountY)
);
}
public static Rectangle2D expand(RectangularShape rect, double amount)
{
return expand(rect, amount, amount);
}
public static Rectangle2D expand(RectangularShape rect, double amountX, double amountY)
{
return new Rectangle2D.Double(
rect.getX() - amountX,
rect.getY() - amountY,
rect.getWidth() + (2*amountX),
rect.getHeight() + (2*amountY)
);
}
public static Point2D getPoint(RectangularShape bounds, Align align)
{
double x = 0.0;
double y = 0.0;
switch(align)
{
case North:
x = bounds.getCenterX();
y = bounds.getMinY();
break;
case NorthEast:
x = bounds.getMaxX();
y = bounds.getMinY();
break;
case East:
x = bounds.getMaxX();
y = bounds.getCenterY();
break;
case SouthEast:
x = bounds.getMaxX();
y = bounds.getMaxY();
break;
case South:
x = bounds.getCenterX();
y = bounds.getMaxY();
break;
case SouthWest:
x = bounds.getMinX();
y = bounds.getMaxY();
break;
case West:
x = bounds.getMinX();
y = bounds.getCenterY();
break;
case NorthWest:
x = bounds.getMinX();
y = bounds.getMinY();
break;
case Center:
x = bounds.getCenterX();
y = bounds.getCenterY();
break;
}
return new Point2D.Double(x,y);
}
public void drawString(String text, RectangularShape bounds, Align align)
{
drawString(g, text, bounds, align, 0.0);
}
public void drawString(String text, RectangularShape bounds, Align align, double angle)
{
drawString(g, text, bounds, align, angle);
}
public static void drawString(Graphics g, String text, RectangularShape bounds, Align align)
{
drawString(g, text, bounds, align, 0.0);
}
public static void drawString(Graphics g, String text, RectangularShape bounds, Align align, double angle)
{
Graphics2D g2 = (Graphics2D)g;
Font font = g2.getFont();
if(angle != 0)
g2.setFont(font.deriveFont(AffineTransform.getRotateInstance(Math.toRadians(angle))));
Rectangle2D sSize = g2.getFontMetrics().getStringBounds(text, g2);
Point2D pos = getPoint(bounds, align);
double x = pos.getX();
double y = pos.getY() + sSize.getHeight();
switch(align)
{
case North:
case South:
case Center:
x -= (sSize.getWidth() / 2);
break;
case NorthEast:
case East:
case SouthEast:
x -= (sSize.getWidth());
break;
case SouthWest:
case West:
case NorthWest:
break;
}
g2.drawString(text, (float)x, (float)y);
g2.setFont(font);
}
/*
public static void drawGrid(Graphics g, Rectangle2D bounds, int cols, int rows)
{
Graphics2D g2 = (Graphics2D)g;
double minx = bounds.getMinX();
double miny = bounds.getMinY();
double maxx = bounds.getMaxX();
double maxy = bounds.getMaxY();
double width = bounds.getWidth();
double height = bounds.getHeight();
double xInterval = width / cols;
double yInterval = height / rows;
for(int col=1; col<=cols; col++)
{
for(int row=1; row<=rows; row++)
{
Point2D pos = new Point2D.Double(minx + (xInterval * col), miny + (yInterval * row));
g2.setColor(Color.black);
g2.drawLine(
(int)pos.getX(),
(int)miny,
(int)pos.getX(),
(int)maxy
);
g2.drawLine(
(int)minx,
(int)pos.getY(),
(int)maxx,
(int)pos.getY()
);
Point2D ctr = new Point2D.Double(
minx + (xInterval * col) - (xInterval/2),
miny + (yInterval * row) - (yInterval/2)
);
g2.setColor(Color.green);
g2.drawLine(
(int)ctr.getX(),
(int)miny,
(int)ctr.getX(),
(int)maxy
);
g2.drawLine(
(int)minx,
(int)ctr.getY(),
(int)maxx,
(int)ctr.getY()
);
}
}
g2.setColor(Color.black);
g2.drawRect((int)minx, (int)miny, (int)width, (int)height);
}
*/
public void drawImage(BufferedImage img, Align align)
{
drawImage(g, img, align, img.getWidth(), img.getHeight(), observer);
}
public void drawImage(BufferedImage img, Align align, double newWidth, double newHeight)
{
drawImage(g, img, align, newWidth, newHeight, observer);
}
public static void drawImage(Graphics g, BufferedImage img, Align align)
{
drawImage(g, img, align, img.getWidth(), img.getHeight(), null);
}
public static void drawImage(Graphics g, BufferedImage img, Align align, double newWidth, double newHeight)
{
drawImage(g, img, align, newWidth, newHeight, null);
}
public static void drawImage(Graphics g, BufferedImage img, Align align, double newWidth, double newHeight, ImageObserver observer)
{
// TBD
}
public void drawCentered(BufferedImage img, Point2D location)
{
drawCentered(g, img, location, img.getWidth(), img.getHeight(), observer);
}
public void drawCentered(BufferedImage img, Point2D location, double newWidth, double newHeight)
{
drawCentered(g, img, location, newWidth, newHeight, observer);
}
public static void drawCentered(Graphics g, BufferedImage img, Point2D location)
{
drawCentered(g, img, location, img.getWidth(), img.getHeight(), null);
}
public static void drawCentered(Graphics g, BufferedImage img, Point2D location, ImageObserver observer)
{
drawCentered(g, img, location, img.getWidth(), img.getHeight(), observer);
}
public static void drawCentered(Graphics g, BufferedImage img, Point2D location, double newWidth, double newHeight)
{
drawCentered(g, img, location, newWidth, newHeight, null);
}
public static void drawCentered(Graphics g, BufferedImage img, Point2D location, double newWidth, double newHeight, ImageObserver observer)
{
Graphics2D g2 = (Graphics2D)g;
g2.drawImage(
img, // what to draw
(int)(location.getX() - (newWidth/2)), // dest left
(int)(location.getY() - (newHeight/2)), // dest top
(int)(location.getX() + (newWidth/2)), // dest right
(int)(location.getY() + (newHeight/2)), // dest bottom
0, // src left
0, // src top
img.getWidth(), // src right
img.getHeight(), // src bottom
observer // to notify of image updates
);
}
public static File write(String fileName, Shape shape)
throws IOException
{
Rectangle bounds = shape.getBounds();
BufferedImage img = createImage(bounds.width, bounds.height);
Graphics2D g2 = (Graphics2D)img.createGraphics();
//g2.setColor(WebColor.CornSilk.getColor());
g2.fill(shape);
g2.setColor(Color.black);
g2.draw(shape);
return write(fileName, img);
}
public static File write(String fileName, BufferedImage img)
throws IOException
{
File file = new File(fileName);
if(ImageIO.write(img, "PNG",file))
return file;
return null;
}
// add something like write(fileName, ArrayList<ArrayList<BufferedImage>>)
// or write(fileName, BufferedImage ... images)
// to create a tiled image from multiple source images
}
Related examples in the same category