diff --git a/src/main/java/com/github/creme332/controller/canvas/drawing/DrawRegularPolygon.java b/src/main/java/com/github/creme332/controller/canvas/drawing/DrawRegularPolygon.java index cd2281de..8541d52b 100644 --- a/src/main/java/com/github/creme332/controller/canvas/drawing/DrawRegularPolygon.java +++ b/src/main/java/com/github/creme332/controller/canvas/drawing/DrawRegularPolygon.java @@ -17,6 +17,8 @@ public class DrawRegularPolygon extends AbstractDrawer { PolygonCalculator calculator = new PolygonCalculator(); private int numSides = 5; // default to 5 sides (pentagon) + private Point2D firstVertex = null; + private Point2D secondVertex = null; public DrawRegularPolygon(AppState app, Canvas canvas) { super(app, canvas); @@ -24,47 +26,56 @@ public DrawRegularPolygon(AppState app, Canvas canvas) { @Override protected void handleMouseMoved(Point2D polySpaceMousePosition) { - if (preview != null) { - // calculate side length of polygon - Point2D center = preview.getPlottedPoints().get(0); - int sideLength = (int) Math.abs(center.distance(polySpaceMousePosition)); - - int[][] res = calculator.getOrderedPoints(numSides, sideLength, (int) center.getX(), (int) center.getY()); - Polygon p = new Polygon(res[0], res[1], res[0].length); - preview.setShape(p); - canvas.repaint(); - } + // do nothing } @Override protected void handleMousePressed(Point2D polySpaceMousePosition) { - if (preview == null) { - // center of polygon has been input + if (firstVertex == null) { + // user entered first vertex of polygon + firstVertex = polySpaceMousePosition; // initialize shape preview preview = new ShapeWrapper(canvasModel.getShapeColor(), canvasModel.getLineType(), canvasModel.getLineThickness()); + + // save first plotted point preview.getPlottedPoints().add(polySpaceMousePosition); - // save preview + // update preview on canvas canvasModel.getShapeManager().setShapePreview(preview); + return; + } + + if (secondVertex == null) { + // user entered second vertex of polygon + secondVertex = polySpaceMousePosition; + + // save second plotted point + preview.getPlottedPoints().add(polySpaceMousePosition); // ask user to enter number of sides numSides = inputVertices(); if (numSides < 3) { - // invalid input => cancel operation + // invalid input => reset state and cancel operation disposePreview(); + return; } - return; - } - // second time clicked - // add preview as actual shape - canvasModel.getShapeManager().addShape(preview); + // generate new polygon + Polygon polygon = calculator.getRegularPolygon(firstVertex, secondVertex, numSides); + preview.setShape(polygon); + + // save shape + canvasModel.getShapeManager().addShape(preview); + + canvas.repaint(); - disposePreview(); + // discard preview + disposePreview(); + } } @Override @@ -76,7 +87,8 @@ protected boolean shouldDraw() { * Asks user to enter number of vertices for polygon. If input value is invalid * or if operation is cancelled, -1 is returned. * - * @return + * @return The number of vertices entered by the user or -1 if invalid or + * cancelled. */ private int inputVertices() { JTextField numSidesField = new JTextField(5); @@ -99,4 +111,13 @@ private int inputVertices() { } return -1; } + + @Override + public void disposePreview() { + // delete any preview shape + firstVertex = null; + secondVertex = null; + preview = null; + canvasModel.getShapeManager().setShapePreview(preview); + } } diff --git a/src/main/java/com/github/creme332/model/calculator/PolygonCalculator.java b/src/main/java/com/github/creme332/model/calculator/PolygonCalculator.java index 2ddc482a..c7038884 100644 --- a/src/main/java/com/github/creme332/model/calculator/PolygonCalculator.java +++ b/src/main/java/com/github/creme332/model/calculator/PolygonCalculator.java @@ -12,7 +12,7 @@ public class PolygonCalculator { /** - * Caches coordinates regular polygons previously calculated. + * Caches coordinates of regular polygons previously calculated. */ private final Map, Shape> polygonCache = new HashMap<>(); @@ -22,13 +22,12 @@ public class PolygonCalculator { * * @param vector A point in the x-y plane. * @param radAngle Rotation angle in radians. - * @return + * @return The rotated vector as a Point2D. */ public static Point2D rotateVector(Point2D vector, double radAngle) { return new Point2D.Double( vector.getX() * Math.cos(radAngle) - vector.getY() * Math.sin(radAngle), vector.getX() * Math.sin(radAngle) + vector.getY() * Math.cos(radAngle)); - } /** @@ -37,26 +36,26 @@ public static Point2D rotateVector(Point2D vector, double radAngle) { * * @param point Point to be rotated. * @param pivot Point about which rotation takes place. - * @param radAngle Rotation angle in radians. - * @return + * @param radAngle Anti-clockwise rotation angle in radians. + * @return The rotated point as a Point2D. */ public static Point2D rotatePointAboutPivot(Point2D point, Point2D pivot, double radAngle) { - // calculate translation vector from pivot to point + // Calculate translation vector from pivot to point Point2D translationVector = new Point2D.Double(point.getX() - pivot.getX(), point.getY() - pivot.getY()); - // rotate translation vector + // Rotate translation vector translationVector = rotateVector(translationVector, radAngle); - // return new position of point + // Return new position of point return new Point2D.Double(translationVector.getX() + pivot.getX(), translationVector.getY() + pivot.getY()); } /** - * Calculates coordinates of a regular polygon centered at origin. + * Calculates coordinates of a regular polygon centered at the origin. * - * @param sidesCount Number of sides in polygon - * @param length Side length of polygon - * @return + * @param sidesCount Number of sides in polygon. + * @param length Side length of polygon. + * @return The shape representing the polygon. */ private Shape getRegularPolygon(int sidesCount, int length) { if (polygonCache.containsKey(Map.entry(sidesCount, length))) { @@ -71,7 +70,7 @@ private Shape getRegularPolygon(int sidesCount, int length) { length * Math.cos(rotationAngleInRad / 2)); for (int i = 1; i < sidesCount; i++) { - // rotate the previous point by the required angle + // Rotate the previous point by the required angle Point2D.Double vector = new Point2D.Double(points[i - 1].x, points[i - 1].y); Point2D rotatedVector = rotateVector(vector, rotationAngleInRad); points[i] = new Point2D.Double(rotatedVector.getX(), rotatedVector.getY()); @@ -90,13 +89,56 @@ private Shape getRegularPolygon(int sidesCount, int length) { } /** - * Calculates integer coordinates of a regular polygon. + * Calculates a regular polygon given two adjacent vertices and the number of + * sides. + * + * @param pointA First vertex. + * @param pointB Second vertex (adjacent to the first). + * @param sidesCount Number of sides in polygon. + * @return The Polygon object representing the regular polygon. + */ + public Polygon getRegularPolygon(Point2D pointA, Point2D pointB, int sidesCount) { + final Point2D.Double[] points = new Point2D.Double[sidesCount]; + points[0] = new Point2D.Double(pointA.getX(), pointA.getY()); + points[1] = new Point2D.Double(pointB.getX(), pointB.getY()); + + /** + * Size of 1 interior angle of polygon. + */ + final double interiorAngle = (180 * (sidesCount - 2)) / (double) sidesCount; + + /** + * Anticlockwise rotation angle mapping one edge to another. + */ + final double rotationAngleInRad = Math.toRadians(360 - interiorAngle); + + for (int i = 2; i < sidesCount; i++) { + Point2D pivot = points[i - 1]; + Point2D vectorStart = points[i - 2]; + + // rotate vector about pivot + points[i] = (Point2D.Double) rotatePointAboutPivot(vectorStart, pivot, rotationAngleInRad); + } + + /// round off pixel coordinates to the nearest integer + int[] x = new int[sidesCount]; + int[] y = new int[sidesCount]; + for (int i = 0; i < sidesCount; i++) { + x[i] = (int) Math.round(points[i].x); + y[i] = (int) Math.round(points[i].y); + } + + return new Polygon(x, y, sidesCount); + } + + /** + * Retrieves the ordered points of a regular polygon with specified parameters. * - * @param sidesCount number of sides - * @param length side length of polygon - * @param centerX x-coordinate of center of polygon - * @param centerY y-coordinate of center of polygon - * @return + * @param sidesCount Number of sides. + * @param length Side length. + * @param centerX X-coordinate of the polygon center. + * @param centerY Y-coordinate of the polygon center. + * @return A 2D array containing ordered x and y points. */ public int[][] getOrderedPoints(int sidesCount, int length, int centerX, int centerY) { Shape polygon = getRegularPolygon(sidesCount, length); @@ -117,11 +159,11 @@ public int[][] getOrderedPoints(int sidesCount, int length, int centerX, int cen } /** - * Transforms a given Polygon using the specified AffineTransform. + * Transforms a polygon using the given affine transformation. * - * @param polygon the Polygon to be transformed - * @param transform the AffineTransform to be applied - * @return a new Polygon representing the transformed shape + * @param polygon The polygon to be transformed. + * @param transform The affine transformation to apply. + * @return The transformed polygon. */ public static Polygon transformPolygon(Polygon polygon, AffineTransform transform) { Shape transformedShape = transform.createTransformedShape(polygon);