/* * Copyright 2016 Nathan Howard * * This file is part of OpenGrave * * OpenGrave is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenGrave 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenGrave. If not, see <http://www.gnu.org/licenses/>. */ package com.opengrave.common.pathing; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; public class PathingArea extends Polygon { int layer = 0; public class InsetTooSmallException extends Exception { private static final long serialVersionUID = 8762987646102778932L; } private HashMap<Double, Polygon> innerPolygon = new HashMap<Double, Polygon>(); private NavigationMesh mesh; public PathingArea(NavigationMesh mesh, ArrayList<Point> points) { super(points); this.mesh = mesh; } public Polygon getInnerPoly(double size) { if (innerPolygon.containsKey(size)) { // return innerPolygon.get(size); } ArrayList<PathingEdge> edges = mesh.getEdges(this); // Get all real // edges ArrayList<Point> newPoints = new ArrayList<Point>(); Point a, b = points.get(points.size() - 1), c = points.get(0); Polygon pgon = null; try { for (int i = 0; i <= points.size(); i++) { a = b; b = c; if (i < points.size() - 1) { c = points.get(i + 1); } else { c = points.get((i + 1) - points.size()); } Line l1 = new Line(new Point(a), new Point(b), true); Line l2 = new Line(new Point(b), new Point(c), true); boolean edge1 = false, edge2 = false; for (PathingEdge edge : edges) { if (edge.contains(l1)) { edge1 = true; } if (edge.contains(l2)) { edge2 = true; } } insetCorner(newPoints, new Point(a), new Point(b), new Point(c), size, edge1, edge2); } pgon = new Polygon(newPoints); if (!pgon.checkSimple()) { // Insetting broke it pgon = new Polygon(); } } catch (InsetTooSmallException e) { pgon = new Polygon(); } // newPoints.add(insetCorner(new Point(b), new Point(c), new // Point(start), size, true,false)); innerPolygon.put(size, pgon); return pgon; } /** * Inset a corner with toggles to not inset either or both edges. If both * edges are toggled then an extra vertex is added to avoid the corner. Not * perfect but a damn sight better than previous versions * * @param newPoints * @param a * @param b * @param c * @param size * @param side1 * @param side2 */ private void insetCorner(ArrayList<Point> newPoints, Point a, Point b, Point c, double size, boolean side1, boolean side2) throws PathingArea.InsetTooSmallException { Point c1 = new Point(c), a1 = new Point(a); double bx1 = b.x, bx2 = b.x, by1 = b.y, by2 = b.y, dx1, dy1, dist1, dx2, dy2, dist2, insetX, insetY; dx1 = b.x - a.x; dy1 = b.y - a.y; dist1 = Math.sqrt(dx1 * dx1 + dy1 * dy1); dx2 = c.x - b.x; dy2 = c.y - b.y; dist2 = Math.sqrt(dx2 * dx2 + dy2 * dy2); if (dist1 < size * 2 || dist2 < size * 2) { side1 = false; side2 = false; // throw new PathingArea.InsetTooSmallException(); } if (!side1 || (side1 && side2)) { insetX = dy1 / dist1 * size; a1.x += insetX; bx1 += insetX; insetY = -dx1 / dist1 * size; a1.y += insetY; by1 += insetY; } if (!side2 || (side1 && side2)) { insetX = dy2 / dist2 * size; c1.x += insetX; bx2 += insetX; insetY = -dx2 / dist2 * size; c1.y += insetY; by2 += insetY; } if (side1 && side2) { newPoints.add(intersect(a, b.x, b.y, bx2, by2, c1, b.getZ())); newPoints.add(intersect(a1, bx1, by1, b.x, b.y, c, b.getZ())); return; } if (bx1 == bx2 && by1 == by2) { newPoints.add(new Point(bx1, by1, b.getZ())); } Point p = intersect(a1, bx1, by1, bx2, by2, c1, b.getZ()); if (!isPointInside(p)) { throw new PathingArea.InsetTooSmallException(); } newPoints.add(p); } private Point intersect(Point a, double bx1, double by1, double bx2, double by2, Point c, int layer) { double distAB, theCos, theSin, newX, ABpos; if (a.x == bx1 && a.y == by1 || bx2 == c.x && by2 == c.y) return null; bx1 -= a.x; by1 -= a.y; bx2 -= a.x; by2 -= a.y; c.x -= a.x; c.y -= a.y; // Discover the length of segment A-B. distAB = Math.sqrt(bx1 * bx1 + by1 * by1); // (2) Rotate the system so that point B is on the positive X axis. theCos = bx1 / distAB; theSin = by1 / distAB; newX = bx2 * theCos + by2 * theSin; by2 = by2 * theCos - bx2 * theSin; bx2 = newX; newX = c.x * theCos + c.y * theSin; c.y = c.y * theCos - c.x * theSin; c.x = newX; // Fail if the lines are parallel. if (by1 == c.y) return null; // (3) Discover the position of the intersection point along line A-B. ABpos = c.x + (bx2 - c.x) * c.y / (c.y - by2); // (4) Apply the discovered position to line A-B in the original // coordinate system. return new Point(a.x + ABpos * theCos, a.y + ABpos * theSin, layer); } public Collection<Polygon> getAllInnerPoly() { return innerPolygon.values(); } public int getLayer() { return layer; } public void setLayer(int layer) { this.layer = layer; } }