package com.austinv11.collectiveframework.utils.math.geometry; import com.austinv11.collectiveframework.utils.ArrayUtils; import com.austinv11.collectiveframework.utils.math.MathUtils; import com.austinv11.collectiveframework.utils.math.TwoDimensionalVector; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Class which represents a 2D shape with an indefinite amount of sides (until instantiation) */ public class Variable2DShape { private Line[] sides; /** * Constructor for a 2D shape * @param vectors The vectors to form the shape with * @throws IncompatibleDimensionsException */ public Variable2DShape(TwoDimensionalVector... vectors) throws IncompatibleDimensionsException { if (vectors.length < 3) throw new IncompatibleDimensionsException("Not enough vectors!"); Arrays.sort(vectors); Line[] lines = new Line[vectors.length]; for (int i = 0; i < vectors.length; i++) lines[i] = new Line(vectors[i], ArrayUtils.wrappedRetrieve(vectors, i+1)); sides = lines; init(); } /** * Constructor for a 2D shape * @param lines The lines to form the shape with * @throws IncompatibleDimensionsException */ public Variable2DShape(Line... lines) throws IncompatibleDimensionsException { if (lines.length < 3) throw new IncompatibleDimensionsException("Not enough sides!"); sides = lines; init(); } private void init() throws IncompatibleDimensionsException { //This ensures that a proper polygon was actually formed int numOfSides = getNumberOfSides(); TwoDimensionalVector[] nonRepeatingVectors = getVertices(); if (!(nonRepeatingVectors.length == numOfSides)) throw new IncompatibleDimensionsException("The dimensions do not connect to form a polygon!"); } /** * Gets the number of sides for this shape * @return The number of sides */ public int getNumberOfSides() { return sides.length; } /** * Retrieves all points representing vertices on the shape * @return The vertices */ public TwoDimensionalVector[] getVertices() { List<TwoDimensionalVector> vectors = new ArrayList<TwoDimensionalVector>(); for (Line line : sides) { vectors.add(line.get2DStart()); vectors.add(line.get2DEnd()); } TwoDimensionalVector[] vectorArray = vectors.toArray(new TwoDimensionalVector[vectors.size()]); return ArrayUtils.removeRepeats(vectorArray); } /** * Gets the perimeter of the shape * @return The perimeter */ public double getPerimeter() { double perimeter = 0; for (Line line : sides) perimeter += line.getLength(); return perimeter; } /** * Calculates the area of the shape * @return The area */ public double getArea() { double area = 0; TwoDimensionalVector[] vertices = getVertices(); for (int i = 0; i < vertices.length; i++) { TwoDimensionalVector vertex1 = ArrayUtils.wrappedRetrieve(vertices, i); TwoDimensionalVector vertex2 = ArrayUtils.wrappedRetrieve(vertices, i+1); area += (vertex1.getX() * vertex2.getY()) - (vertex1.getY() * vertex2.getX()); } area = Math.abs(area / 2); return area; } /** * Creates an array of sides from the shape where the side touches the given coord * This should only ever return 0-2 lines * @param coord The coordinate point * @return The sides */ public Line[] findSidesWithPoint(TwoDimensionalVector coord) { List<Line> lines = new ArrayList<Line>(); for (Line line : sides) if (line.isPointValid(coord)) lines.add(line); return lines.toArray(new Line[lines.size()]); } /** * Finds the (internal) angle of the given vertex * @param vertex The vertex * @return The angle * @throws IncompatibleDimensionsException */ public float getAngleForVertex(TwoDimensionalVector vertex) throws IncompatibleDimensionsException { Line[] sides = findSidesWithPoint(vertex); if (sides.length != 2) throw new IncompatibleDimensionsException(vertex.toString()+" is not a valid vertex!"); double slope1 = Double.isNaN(sides[0].get2DSlope()) ? Double.MAX_VALUE : sides[0].get2DSlope(); double slope2 = Double.isNaN(sides[1].get2DSlope()) ? Double.MAX_VALUE : sides[1].get2DSlope(); return (float) Math.atan(Math.abs((slope1-slope2)/(1+(slope1*slope2)))); } /** * Gets the lines representing the sides of the shape * @return The sides */ public Line[] getSides() { return sides; } /** * Calculates the approximate center of the polygon * @return The center coordinates */ public TwoDimensionalVector getCentroid() { TwoDimensionalVector[] vertices = getVertices(); double centerX = 0; double centerY = 0; for (TwoDimensionalVector vertex : vertices) { centerX += vertex.getX(); centerY += vertex.getY(); } centerX /= getNumberOfSides(); centerY /= getNumberOfSides(); return new TwoDimensionalVector(centerX, centerY); } /** * Translates the whole shape to make a new shape with a matching centroid * @param centroid New centroid * @return Modified shape */ public Variable2DShape setCentroid(TwoDimensionalVector centroid) { TwoDimensionalVector oldCentroid = getCentroid(); double xDiff = centroid.getX() - oldCentroid.getX(); double yDiff = centroid.getY() - oldCentroid.getY(); TwoDimensionalVector toAdd = new TwoDimensionalVector(xDiff, yDiff); TwoDimensionalVector[] vertices = getVertices(); List<TwoDimensionalVector> newVertices = new ArrayList<TwoDimensionalVector>(); for (TwoDimensionalVector vertex : vertices) newVertices.add(vertex.add(toAdd)); try { return new Variable2DShape(newVertices.toArray(new TwoDimensionalVector[vertices.length])); } catch (IncompatibleDimensionsException e) { e.printStackTrace(); } return null; //This should never be reached } /** * Rotates the shape around a point * @param point The point to rotate around * @param angle The angle to rotate by * @return The new shape */ public Variable2DShape rotate(TwoDimensionalVector point, double angle) { TwoDimensionalVector[] vertices = getVertices(); List<TwoDimensionalVector> newVertices = new ArrayList<TwoDimensionalVector>(); for (TwoDimensionalVector vertex : vertices) newVertices.add(vertex.rotate(point, angle)); try { return new Variable2DShape(newVertices.toArray(new TwoDimensionalVector[vertices.length])); } catch (IncompatibleDimensionsException e) { e.printStackTrace(); } return null; //This should never be reached } /** * Rotates the shape about its centroid * @param angle The angle to rotate by * @return The new shape */ public Variable2DShape rotate(double angle) { return rotate(getCentroid(), angle); } /** * Checks if the provided point is on the shape's perimeter * @param coord The point * @return If the point is on the perimeter */ public boolean isPointOnPerimeter(TwoDimensionalVector coord) { for (Line l : sides) if (l.isPointValid(coord)) return true; return false; } /** * Checks if a point is inside the shape. * DO NOT TRUST THIS- it is not always accurate, it is only 100% accurate with rectangles * @param coord The point * @return If the point is in the shape */ @Deprecated public boolean isPointValid(TwoDimensionalVector coord) { //FIXME if (isPointOnPerimeter(coord)) return true; TwoDimensionalVector[] vertices = getVertices(); double[] xVals = new double[vertices.length]; double[] yVals = new double[vertices.length]; for (int i = 0; i < vertices.length; i++) { xVals[i] = vertices[i].getX(); yVals[i] = vertices[i].getY(); } double minX, maxX, minY, maxY; minX = MathUtils.getMin(xVals); maxX = MathUtils.getMax(xVals); minY = MathUtils.getMin(yVals); maxY = MathUtils.getMax(yVals); return MathUtils.isBetween(minX, maxX, coord.getX(), true) && MathUtils.isBetween(minY, maxY, coord.getY(), true); } /** * Gets all the points in the shape (with whole number coords) * WARNING- Deprecated because it uses isPointValid * @return The points */ @Deprecated public TwoDimensionalVector[] getAllPoints() { List<TwoDimensionalVector> points = new ArrayList<TwoDimensionalVector>(); TwoDimensionalVector[] vertices = getVertices(); double[] xVals = new double[vertices.length]; double[] yVals = new double[vertices.length]; for (int i = 0; i < vertices.length; i++) { xVals[i] = vertices[i].getX(); yVals[i] = vertices[i].getY(); } double minX, maxX, minY, maxY; minX = MathUtils.getMin(xVals); maxX = MathUtils.getMax(xVals); minY = MathUtils.getMin(yVals); maxY = MathUtils.getMax(yVals); for (int x = (int) minX; x <= maxX; x++) for (int y = (int)minY; y <= maxY; y++) if (isPointValid(new TwoDimensionalVector(x, y))) points.add(new TwoDimensionalVector(x, y)); return points.toArray(new TwoDimensionalVector[points.size()]); } @Override public boolean equals(Object other) { if (other instanceof Variable2DShape) return ((Variable2DShape) other).getNumberOfSides() == getNumberOfSides() && ((Variable2DShape) other).getArea() == getArea() && ((Variable2DShape) other).getPerimeter() == getPerimeter(); return false; } @Override public String toString() { return "Variable2DShape(Sides:"+getNumberOfSides()+")"; } }