Hexagon.java Source code

Java tutorial

Introduction

Here is the source code for Hexagon.java

Source

/*
 * @(#)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
}