/* * $Id$ * * Copyright (c) 2000-2003 by Rodney Kinney * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License (LGPL) as published by the Free Software Foundation. * * 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, copies are available * at http://www.opensource.org. */ package VASSAL.build.module.map.boardPicker.board; import static java.lang.Math.PI; import static java.lang.Math.abs; import static java.lang.Math.atan2; import static java.lang.Math.ceil; import static java.lang.Math.cos; import static java.lang.Math.floor; import static java.lang.Math.max; import static java.lang.Math.min; import static java.lang.Math.round; import static java.lang.Math.sqrt; import java.awt.Color; import java.awt.Container; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Polygon; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.util.HashMap; import java.util.Map; import javax.swing.JButton; import VASSAL.build.AbstractConfigurable; import VASSAL.build.Buildable; import VASSAL.build.module.documentation.HelpFile; import VASSAL.build.module.map.boardPicker.board.mapgrid.GridContainer; import VASSAL.build.module.map.boardPicker.board.mapgrid.GridNumbering; import VASSAL.build.module.map.boardPicker.board.mapgrid.HexGridNumbering; import VASSAL.configure.AutoConfigurer; import VASSAL.configure.ColorConfigurer; import VASSAL.configure.Configurer; import VASSAL.configure.VisibilityCondition; import VASSAL.i18n.Resources; /** * A Hexgrid is a map grid composed of hexes. */ public class HexGrid extends AbstractConfigurable implements GeometricGrid, GridEditor.EditableGrid { protected Point origin = new Point(0, 32); protected double dx; protected double dy; protected int snapScale = 0; protected GridContainer container; protected GridNumbering numbering; protected boolean visible = false; protected boolean dotsVisible = false; protected boolean edgesLegal = false; protected boolean cornersLegal = false; protected Color color = Color.black; protected boolean sideways = false; protected boolean snapTo = true; protected Map<Integer,Area> shapeCache = new HashMap<Integer,Area>(); protected HexGridEditor gridEditor; public static final String X0 = "x0"; //$NON-NLS-1$ public static final String Y0 = "y0"; //$NON-NLS-1$ public static final String DY = "dy"; //$NON-NLS-1$ public static final String DX = "dx"; //$NON-NLS-1$ public static final String VISIBLE = "visible"; //$NON-NLS-1$ public static final String DOTS_VISIBLE = "dotsVisible"; //$NON-NLS-1$ public static final String CORNERS = "cornersLegal"; //$NON-NLS-1$ public static final String EDGES = "edgesLegal"; //$NON-NLS-1$ public static final String SIDEWAYS = "sideways"; //$NON-NLS-1$ public static final String COLOR = "color"; //$NON-NLS-1$ public static final String SNAP_SCALE = "snapscale"; //$NON-NLS-1$ public static final String SNAP_TO = "snapTo"; //$NON-NLS-1$ protected static final double sqrt3_2 = sqrt(3) / 2.; public String[] getAttributeNames() { return new String[] { SIDEWAYS, X0, Y0, DY, DX, SNAP_TO, EDGES, CORNERS, VISIBLE, DOTS_VISIBLE, COLOR }; } public String[] getAttributeDescriptions() { return new String[]{ Resources.getString("Editor.HexGrid.sideways"), //$NON-NLS-1$ Resources.getString("Editor.Grid.x_offset"), //$NON-NLS-1$ Resources.getString("Editor.Grid.y_offset"), //$NON-NLS-1$ Resources.getString("Editor.HexGrid.hex_height"), //$NON-NLS-1$ Resources.getString("Editor.HexGrid.hex_width"), //$NON-NLS-1$ Resources.getString("Editor.Grid.snap"), //$NON-NLS-1$ Resources.getString("Editor.Grid.edges"), //$NON-NLS-1$ Resources.getString("Editor.HexGrid.vertices"), //$NON-NLS-1$ Resources.getString("Editor.Grid.show_grid"), //$NON-NLS-1$ Resources.getString("Editor.Grid.center_dots"), //$NON-NLS-1$ Resources.getString(Resources.COLOR_LABEL), }; } public Class<?>[] getAttributeTypes() { return new Class<?>[]{ Boolean.class, Integer.class, Integer.class, Double.class, Double.class, Boolean.class, Boolean.class, Boolean.class, Boolean.class, Boolean.class, Color.class }; } public VisibilityCondition getAttributeVisibility(String name) { if (COLOR.equals(name)) { return new VisibilityCondition() { public boolean shouldBeVisible() { return visible; } }; } else if (EDGES.equals(name) || CORNERS.equals(name)) { return new VisibilityCondition() { public boolean shouldBeVisible() { return snapTo; } }; } else { return super.getAttributeVisibility(name); } } public Configurer getConfigurer() { boolean buttonExists = config != null; AutoConfigurer c = (AutoConfigurer) super.getConfigurer(); final Configurer dxConfig = c.getConfigurer(DX); c.getConfigurer(DY).addPropertyChangeListener(new java.beans.PropertyChangeListener() { public void propertyChange(java.beans.PropertyChangeEvent evt) { if (evt.getNewValue() != null) { double hgt = ((Double) evt.getNewValue()).doubleValue(); dxConfig.setValue(Double.valueOf(sqrt3_2 * hgt).toString()); } } }); if (!buttonExists) { JButton b = new JButton(Resources.getString("Editor.Grid.edit_grid")); //$NON-NLS-1$ b.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { editGrid(); } }); ((Container) c.getControls()).add(b); } return config; } protected boolean alternate = false;// true if hex B1 is above A1 public HexGrid(double height, double width, boolean alt) { dy = height; dx = width; alternate = alt; } public HexGrid(double size, boolean alt) { this(size, sqrt3_2 * size, alt); } public HexGrid() { this(64.0, false); } public boolean isVisible() { return visible == true || (numbering != null && numbering.isVisible()); } public boolean isEdgesLegal() { return edgesLegal; } public boolean isCornersLegal() { return cornersLegal; } public void setVisible(boolean legal) { visible = legal; } public void setEdgesLegal(boolean legal) { edgesLegal = legal; } public boolean isSideways() { return sideways; } public void setSideways(boolean b) { sideways = b; } public void setCornersLegal(boolean legal) { cornersLegal = legal; } public void setHexSize(double size) { dy = size; dx = sqrt3_2 * size; shapeCache.clear(); } public double getHexSize() { return dy; } public double getHexWidth() { return dx; } public void setHexWidth(double w) { dx = w; } public double getDx() { return getHexWidth(); } public void setDx(double d) { setHexWidth(d); } public double getDy() { return getHexSize(); } public void setDy(double d) { dy = d; // DO NOT call setHexSize() so that dx is not reset } public GridContainer getContainer() { return container; } public void addTo(Buildable b) { container = (GridContainer) b; container.setGrid(this); } public void removeFrom(Buildable b) { ((GridContainer) b).removeGrid(this); } public static String getConfigureTypeName() { return Resources.getString("Editor.HexGrid.component_type"); //$NON-NLS-1$ } public String getGridName() { return getConfigureTypeName(); } public String getConfigureName() { return null; } public VASSAL.build.module.documentation.HelpFile getHelpFile() { return HelpFile.getReferenceManualPage("HexGrid.htm"); //$NON-NLS-1$ } public String getAttributeValueString(String key) { if (X0.equals(key)) { return String.valueOf(origin.x); } else if (Y0.equals(key)) { return String.valueOf(origin.y); } else if (DY.equals(key)) { return String.valueOf(dy); } else if (DX.equals(key)) { return String.valueOf(dx); } else if (SNAP_TO.equals(key)) { return String.valueOf(snapTo); } else if (CORNERS.equals(key)) { return String.valueOf(cornersLegal); } else if (EDGES.equals(key)) { return String.valueOf(edgesLegal); } else if (SIDEWAYS.equals(key)) { return String.valueOf(sideways); } else if (VISIBLE.equals(key)) { return String.valueOf(visible); } else if (DOTS_VISIBLE.equals(key)) { return String.valueOf(dotsVisible); } else if (COLOR.equals(key)) { return ColorConfigurer.colorToString(color); } return null; } public void setAttribute(String key, Object val) { if (X0.equals(key)) { if (val instanceof String) { val = Integer.valueOf((String) val); } origin.x = ((Integer) val).intValue(); } else if (Y0.equals(key)) { if (val instanceof String) { val = Integer.valueOf((String) val); } origin.y = ((Integer) val).intValue(); } else if (DY.equals(key)) { if (val instanceof String) { val = Double.valueOf((String) val); } dy = ((Double) val).doubleValue(); if (dx == sqrt3_2 * 64.0) { dx = sqrt3_2 * dy; } } else if (DX.equals(key)) { if (val instanceof String) { val = Double.valueOf((String) val); } dx = ((Double) val).doubleValue(); } else if (SNAP_TO.equals(key)) { if (val instanceof String) { val = Boolean.valueOf((String) val); } snapTo = ((Boolean) val).booleanValue(); } else if (CORNERS.equals(key)) { if (val instanceof String) { val = Boolean.valueOf((String) val); } cornersLegal = ((Boolean) val).booleanValue(); } else if (EDGES.equals(key)) { if (val instanceof String) { val = Boolean.valueOf((String) val); } edgesLegal = ((Boolean) val).booleanValue(); } else if (SIDEWAYS.equals(key)) { if (val instanceof String) { val = Boolean.valueOf((String) val); } sideways = ((Boolean) val).booleanValue(); } else if (VISIBLE.equals(key)) { if (val instanceof String) { val = Boolean.valueOf((String) val); } visible = ((Boolean) val).booleanValue(); } else if (DOTS_VISIBLE.equals(key)) { if (val instanceof String) { val = Boolean.valueOf((String) val); } dotsVisible = ((Boolean) val).booleanValue(); } else if (COLOR.equals(key)) { if (val instanceof String) { val = ColorConfigurer.stringToColor((String) val); } color = (Color) val; } else if (SNAP_SCALE.equals(key)) { if (val instanceof String) { val = Integer.valueOf((String)val); } snapScale = (Integer)val; } shapeCache.clear(); } public Class<?>[] getAllowableConfigureComponents() { return new Class[]{HexGridNumbering.class}; } public String locationName(Point p) { return numbering == null ? null : numbering.locationName(p); } public String localizedLocationName(Point p) { return numbering == null ? null : numbering.localizedLocationName(p); } public Point getLocation(String location) throws BadCoords { if (numbering == null) throw new BadCoords(); else return numbering.getLocation(location); } public Point snapTo(Point p) { if (! snapTo) { return p; } Point center = snapToHex(p); if (edgesLegal && cornersLegal) { Point edge = snapToHexSide(p); Point vertex = snapToHexVertex(p); if ((p.x - edge.x) * (p.x - edge.x) + (p.y - edge.y) * (p.y - edge.y) < (p.x - vertex.x) * (p.x - vertex.x) + (p.y - vertex.y) * (p.y - vertex.y)) { return checkCenter(center, edge); } else { return checkCenter(center, vertex); } } else if (edgesLegal) { return checkCenter(center, snapToHexSide(p)); } else if (cornersLegal) { return checkCenter(center, snapToHexVertex(p)); } else { return snapToHex(p); } } // FIXME: snapToHexVertex() does not always return the correct X co-ordinate // if the point is close to the center of the Hex. Workaround by returning // the real hex center if it is within 1 pixel x/y protected Point checkCenter(Point center, Point target) { if ((center.x - target.x) * (center.x - target.x) + (center.y - target.y) * (center.y - target.y) <= 2) { return center; } else { return target; } } public boolean isLocationRestricted(Point p) { return snapTo; } /** * @return the nearest hex center */ public Point snapToHex(Point p) { p = new Point(p); rotateIfSideways(p); p.setLocation(hexX(p.x, p.y), hexY(p.x, p.y)); rotateIfSideways(p); return p; } /** * @return the nearest hex center or hexside */ public Point snapToHexSide(Point p) { p = new Point(p); rotateIfSideways(p); int x = sideX(p.x, p.y); int y = sideY(p.x, p.y); if (snapScale > 0) { int hexX = hexX(p.x,p.y); int hexY = hexY(p.x,p.y); if (abs(p.x-hexX) + abs(p.y-hexY) <= abs(p.x-x)+abs(p.y-y)) { x = hexX; y = hexY; } } p.setLocation(x, y); rotateIfSideways(p); return p; } /** * @return the nearest hex center or vertex */ public Point snapToHexVertex(Point p) { p = new Point(p); rotateIfSideways(p); int x = vertexX(p.x, p.y); int y = vertexY(p.x, p.y); if (snapScale > 0) { int hexX = hexX(p.x,p.y); int hexY = hexY(p.x,p.y); if (abs(p.x-hexX) + abs(p.y-hexY) <= abs(p.x-x)+abs(p.y-y)) { x = hexX; y = hexY; } } p.setLocation(x, y); rotateIfSideways(p); return p; } public void rotate(Point p) { int swap = p.x; p.x = p.y; p.y = swap; } public void rotateIfSideways(Point p) { if (sideways) { rotate(p); } } public Area getGridShape(Point center, int range) { Area shape = shapeCache.get(range); if (shape == null) { //Choose a starting point Point origin = new Point(0, 0); shape = getSingleHexShape(origin.x, origin.y, false); for (int i = -range; i <= range; i++) { int x = origin.x + (int) (i * dx); int length = range * 2 + 1 - abs(i); int startY = 0; if (length % 2 == 1) { startY = origin.y - (int) (dy * (length - 1) / 2); } else { startY = origin.y - (int) (dy * (0.5 + (length - 2) / 2)); } int y = startY; for (int j = 0; j < length; j++) { Point p = new Point(x, y); rotateIfSideways(p); shape.add(getSingleHexShape(p.x, p.y, false)); y += dy; } } rotateIfSideways(origin); shape.transform( AffineTransform.getTranslateInstance(0 - origin.x, 0 - origin.y)); shapeCache.put(range, shape); } shape = new Area(AffineTransform.getTranslateInstance(center.x, center.y).createTransformedShape(shape)); return shape; } /** * Return the Shape of a single hex * @param centerX X co-ord of hex centre * @param centerY Y co-ord of hex centre * @return Hex Shape */ protected Area getSingleHexShape(int centerX, int centerY, boolean reversed) { Polygon poly = new Polygon(); float x = (float) (sideways ? centerY : centerX); float y = (float) (sideways ? centerX : centerY); float x1,y1, x2,y2, x3,y3, x4, y4, x5, y5, x6, y6; float deltaX = (float) (this.dx); float deltaY = (float) (this.dy); float r = 2.F * deltaX / 3.F; Point p1 = new Point(); Point p2 = new Point(); Point p3 = new Point(); Point p4 = new Point(); Point p5 = new Point(); Point p6 = new Point(); x1 = x - r; y1 = y; p1.setLocation(round(x1), round(y1)); x2 = x - 0.5F * r; y2 = reversed ? y + 0.5F * deltaY : y - 0.5F * deltaY; p2.setLocation(round(x2), round(y2)); x3 = x + 0.5F * r; y3 = y2; p3.setLocation(round(x3) + 1, round(y3)); x4 = x + r; y4 = y; p4.setLocation(round(x4) + 1, round(y4)); x5 = x3; y5 = reversed ? y - 0.5F * deltaY : y + 0.5F * deltaY; p5.setLocation(round(x5) + 1, round(y5) + 1); x6 = x2; y6 = y5; p6.setLocation(round(x6), round(y6) + 1); if (sideways) { rotate(p1); rotate(p2); rotate(p3); rotate(p4); rotate(p5); rotate(p6); } poly.addPoint(p1.x, p1.y); poly.addPoint(p2.x, p2.y); poly.addPoint(p3.x, p3.y); poly.addPoint(p4.x, p4.y); poly.addPoint(p5.x, p5.y); poly.addPoint(p6.x, p6.y); poly.addPoint(p1.x, p1.y); return new Area(poly); } public int range(Point p1, Point p2) { p1 = new Point(p1); rotateIfSideways(p1); p2 = new Point(p2); rotateIfSideways(p2); int x = p2.x - p1.x; int y = p2.y - p1.y; double theta = atan2((double) (-x), (double) (-y)) + PI; while (theta > PI / 3.) theta -= PI / 3.; theta = PI / 6. - theta; double r = sqrt((double) (x * x + y * y)); r *= cos(theta); return (int) (r / (dy * sqrt3_2) + 0.5); } protected int hexX(int x, int y) { int loc = ((int) (dx * (int) floor((x - origin.x + dx / 2) / dx) + origin.x)); if (snapScale > 0) { int delta = x - loc; delta = (int)round(delta/(0.5*dx/snapScale)); delta = max(delta,1-snapScale); delta = min(delta,snapScale-1); delta = (int)round(delta*0.5*dx/snapScale); loc += delta; } return loc; } protected int hexY(int x, int y) { int nx = (int) floor((x - origin.x + dx / 2) / dx); int loc; if (nx % 2 == 0) loc = ((int) (dy * (int) floor((y - origin.y + dy / 2) / dy) + origin.y)); else loc = ((int) (dy * (int) floor((y - origin.y) / dy) + (int) (dy / 2) + origin.y)); if (snapScale > 0) { int delta = y - loc; delta = (int)round(delta/(0.5*dy/snapScale)); delta = max(delta,1-snapScale); delta = min(delta,snapScale-1); delta = (int)round(delta*0.5*dy/snapScale); loc += delta; } return loc; } protected int sideX(int x, int y) { return ((int) (dx / 2 * (int) floor((x - origin.x + dx / 4) * 2 / dx) + origin.x)); } protected int sideY(int x, int y) { int nx = (int) floor((x - origin.x + dx / 4) * 2 / dx); if (nx % 2 == 0) { return ((int) (dy / 2 * (int) floor((y - origin.y + dy / 4) * 2 / dy) + origin.y)); } else { return ((int) ((dy / 2) * (int) floor((y - origin.y) * 2 / dy) + (int) (dy / 4) + origin.y)); } } // FIXME: vertexX does not always return the same value as HexX for hex // centres, it is sometimes 1 pixel off. The values returned for the // vertices are fine, so snapTo() has been changed to work around this. // There is a rounding error in here if someone else wants to track it down. protected int vertexX(int x, int y) { int ny = (int) floor((y - origin.y + dy / 4) * 2 / dy); if (ny % 2 == 0) { return ((int) (2 * dx / 3 * (int) (floor(x - origin.x + dx / 3) * 3 / (2 * dx)) + origin.x)); } else { return ((int) (2 * dx / 3 * (int) (floor(x - origin.x + dx / 3 + dx / 3) * 3 / (2 * dx)) - (int) (dx / 3) + origin.x)); } } protected int vertexY(int x, int y) { return ((int) (dy / 2 * (int) floor((y - origin.y + dy / 4) * 2 / dy) + origin.y)); } /** Draw the grid, if visible, and the accompanying numbering */ public void draw(Graphics g, Rectangle bounds, Rectangle visibleRect, double zoom, boolean reversed) { if (visible) { forceDraw(g, bounds, visibleRect, zoom, reversed); } if (numbering != null) { numbering.draw(g, bounds, visibleRect, zoom, reversed); } } /** Draw the grid even if set to be not visible */ public void forceDraw(Graphics g, Rectangle bounds, Rectangle visibleRect, double zoom, boolean reversed) { if (!bounds.intersects(visibleRect) || color == null) { return; } Graphics2D g2d = (Graphics2D) g; g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setColor(color); float x1,y1, x2,y2, x3,y3, x4, y4; float deltaX = (float) (this.dx * zoom); float deltaY = (float) (this.dy * zoom); float r = 2.F * deltaX / 3.F; Rectangle region = bounds.intersection(visibleRect); Shape oldClip = g2d.getClip(); if (oldClip != null) { Area clipArea = new Area(oldClip); clipArea.intersect(new Area(region)); g2d.setClip(clipArea); } if (sideways) { bounds = new Rectangle(bounds.y, bounds.x, bounds.height, bounds.width); region = new Rectangle(region.y, region.x, region.height, region.width); } float xmin = reversed ? bounds.x + (float) zoom * origin.x + bounds.width - 2 * deltaX * (float) ceil((bounds.x + zoom * origin.x + bounds.width - region.x) / (2 * deltaX)) : bounds.x + (float) zoom * origin.x + 2 * deltaX * (float) floor((region.x - bounds.x - zoom * origin.x) / (2 * deltaX)); float xmax = region.x + region.width + 2 * deltaX; float ymin = reversed ? bounds.y + (float) zoom * origin.y + bounds.height - deltaY * (float) ceil((bounds.y + zoom * origin.y + bounds.height - region.y) / deltaY) : bounds.y + (float) zoom * origin.y + deltaY * (float) floor((region.y - bounds.y - zoom * origin.y) / deltaY); float ymax = region.y + region.height + deltaY; Point center = new Point(); Point p1 = new Point(); Point p2 = new Point(); Point p3 = new Point(); Point p4 = new Point(); // x,y is the center of a hex for (float x = xmin; x < xmax; x += zoom * 2 * dx) { for (float y = ymin; y < ymax; y += zoom * dy) { x1 = x - r; y1 = y; p1.setLocation(round(x1), round(y1)); x2 = x - 0.5F * r; y2 = reversed ? y + 0.5F * deltaY : y - 0.5F * deltaY; p2.setLocation(round(x2), round(y2)); x3 = x + 0.5F * r; y3 = y2; p3.setLocation(round(x3), round(y3)); x4 = x + r; y4 = y; p4.setLocation(round(x4), round(y4)); if (sideways) { rotate(p1); rotate(p2); rotate(p3); rotate(p4); } g2d.drawLine(p1.x, p1.y, p2.x, p2.y); g2d.drawLine(p2.x, p2.y, p3.x, p3.y); g2d.drawLine(p3.x, p3.y, p4.x, p4.y); if (dotsVisible) { center.setLocation(round(x), round(y)); rotateIfSideways(center); g2d.fillRect(center.x, center.y, 2, 2); center.setLocation(round(x + deltaX), round(y + deltaY / 2)); rotateIfSideways(center); g2d.fillRect(center.x, center.y, 2, 2); } x1 += deltaX; x2 += deltaX; x3 += deltaX; x4 += deltaX; y1 += 0.5F * deltaY; y2 += 0.5F * deltaY; y3 += 0.5F * deltaY; y4 += 0.5F * deltaY; p1.setLocation(round(x1), round(y1)); p2.setLocation(round(x2), round(y2)); p3.setLocation(round(x3), round(y3)); p4.setLocation(round(x4), round(y4)); if (sideways) { rotate(p1); rotate(p2); rotate(p3); rotate(p4); } g2d.drawLine(p1.x, p1.y, p2.x, p2.y); g2d.drawLine(p2.x, p2.y, p3.x, p3.y); g2d.drawLine(p3.x, p3.y, p4.x, p4.y); if (x == xmin) { p1.setLocation(round(x - r), round(y)); p2.setLocation(round(x - r / 2), round(y + deltaY / 2)); if (sideways) { rotate(p1); rotate(p2); } g2d.drawLine(p1.x, p1.y, p2.x, p2.y); } } } g2d.setClip(oldClip); } public void setGridNumbering(GridNumbering numbering) { this.numbering = numbering; } public GridNumbering getGridNumbering() { return numbering; } public Point getOrigin() { return new Point(origin); } public void setOrigin(Point p) { origin.x = p.x; origin.y = p.y; } public void editGrid() { gridEditor = new HexGridEditor((GridEditor.EditableGrid) this); gridEditor.setVisible(true); // Local variables may have been updated by GridEditor so refresh // configurers. Setting the Dy configurer will auto-recalculate dx double origDx = dx; AutoConfigurer cfg = (AutoConfigurer) getConfigurer(); cfg.getConfigurer(DY).setValue(String.valueOf(dy)); dx = origDx; cfg.getConfigurer(DX).setValue(String.valueOf(dx)); cfg.getConfigurer(X0).setValue(String.valueOf(origin.x)); cfg.getConfigurer(Y0).setValue(String.valueOf(origin.y)); cfg.getConfigurer(SIDEWAYS).setValue(String.valueOf(sideways)); } public static class HexGridEditor extends GridEditor { private static final long serialVersionUID = 1L; public HexGridEditor(EditableGrid grid) { super(grid); } /* * Calculate approximate grid metrics based on the three adjacent points * picked out by the user. */ public void calculate() { /* * Two of the points must lie on the same horizontal or vertical line (be perpendicular to). * The third point must not be perpendicular to either of the first two. First step is to work out * which is which as we can't be sure what order they picked out the points in. */ if (isPerpendicular(hp1, hp2)) { calculate_step2(hp1, hp2, hp3); } else if (isPerpendicular(hp1, hp3)) { calculate_step2(hp1, hp3, hp2); } else if (isPerpendicular(hp2, hp3)) { calculate_step2(hp2, hp3, hp1); } else { reportShapeError(); } } /* * Step 2. Check third point is not perpendicular to either of * the first two, then call appropriate calculation routine * depending on location relative to the first two. */ protected void calculate_step2(Point p1, Point p2, Point p3) { if (!isPerpendicular(p1, p3) && !isPerpendicular(p2, p3)) { if (isHorizontal(p1, p2)) { if ((p3.x < p1.x && p3.x < p2.x) ||(p3.x > p1.x && p3.x > p2.x)) { check(false, p1, p2, p3); } else { checkEnd(true, p1, p2, p3); } } else { if ((p3.y < p1.y && p3.y < p2.y) ||(p3.y > p1.y && p3.y > p2.y)) { check(true, reverse(p1), reverse(p2), reverse(p3)); } else { checkEnd(false, reverse(p1), reverse(p2), reverse(p3)); } } } else { reportShapeError(); } } protected Point reverse (Point p) { return new Point(p.y, p.x); } protected void check (boolean sideways, Point p1, Point p2, Point p3) { int r = abs(p1.x - p2.x); int width = r * 3 / 2; if (width < 1) { reportShapeError(); return; } int height = abs(p3.y - p2.y) * 2; int Xoff = min(p1.x, p2.x) % width + r/2; int col = min(p1.x, p2.x) / width; int Yoff = min(p1.y, p2.y) % height - (col % 2 == 1 ? 0 : height/2); if (Yoff < 0) Yoff += height; setMetrics(width, height, Xoff, Yoff, sideways); } protected void checkEnd (boolean sideways, Point p1, Point p2, Point p3) { if (abs((p1.x + p2.x) / 2 - p3.x) > ERROR_MARGIN) { reportShapeError(); return; } int r = abs(p3.y - p1.y) * 2; int width = r * 3 / 2; int height = abs(p3.x - p2.x) * 2; int xOrigin = p1.y - (p3.y < p1.y ? 0 : r); int Xoff = xOrigin % width + r/2; int col = xOrigin / width; int Yoff = min(p1.x, p2.x) % height - (col % 2 == 1 ? 0 : height/2); setMetrics(width, height, Xoff, Yoff, sideways); } protected void setMetrics(int width, int height, int xoff, int yoff, boolean b) { grid.setDx(width); grid.setDy(height); grid.setOrigin(new Point(xoff, yoff)); grid.setSideways(b); } } public int getSnapScale() { return snapScale; } public void setSnapScale(int snapScale) { this.snapScale = snapScale; } }