From 749b7e129a4a6169049afb149474ea59aea4ec90 Mon Sep 17 00:00:00 2001 From: divyesh000 Date: Mon, 22 Jul 2024 20:31:28 +0400 Subject: [PATCH 1/2] improve regular polygon drawing algorithm --- .../canvas/drawing/DrawRegularPolygon.java | 59 +++++++------ .../model/calculator/PolygonCalculator.java | 87 ++++++++++++++----- 2 files changed, 97 insertions(+), 49 deletions(-) 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..03a9610e 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,50 @@ 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); + if (preview != null && firstVertex != null && secondVertex != null) { + Polygon polygon = calculator.getPolygonFromTwoPoints(firstVertex, secondVertex, numSides); + preview.setShape(polygon); canvas.repaint(); } } @Override protected void handleMousePressed(Point2D polySpaceMousePosition) { - if (preview == null) { - // center of polygon has been input - - // initialize shape preview - preview = new ShapeWrapper(canvasModel.getShapeColor(), - canvasModel.getLineType(), - canvasModel.getLineThickness()); - preview.getPlottedPoints().add(polySpaceMousePosition); - - // save preview - canvasModel.getShapeManager().setShapePreview(preview); + if (firstVertex == null) { + firstVertex = polySpaceMousePosition; + return; + } + if (secondVertex == null) { + secondVertex = polySpaceMousePosition; // ask user to enter number of sides numSides = inputVertices(); if (numSides < 3) { - // invalid input => cancel operation + // invalid input => reset state and cancel operation + firstVertex = null; + secondVertex = null; disposePreview(); + return; } - return; - } - // second time clicked - // add preview as actual shape - canvasModel.getShapeManager().addShape(preview); + // initialize shape preview after getting valid input + preview = new ShapeWrapper(canvasModel.getShapeColor(), + canvasModel.getLineType(), + canvasModel.getLineThickness()); + preview.getPlottedPoints().add(firstVertex); + preview.getPlottedPoints().add(secondVertex); - disposePreview(); + // save preview + canvasModel.getShapeManager().setShapePreview(preview); + + Polygon polygon = calculator.getPolygonFromTwoPoints(firstVertex, secondVertex, numSides); + preview.setShape(polygon); + canvas.repaint(); + } else { + canvasModel.getShapeManager().addShape(preview); + disposePreview(); + } } @Override @@ -76,7 +81,7 @@ 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); 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..8eaec066 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)); - } /** @@ -38,25 +37,25 @@ 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 + * @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,45 @@ 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 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 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. + */ + private Polygon getRegularPolygon(Point2D pointA, Point2D pointB, int sidesCount) { + double length = pointA.distance(pointB); + double angle = Math.atan2(pointB.getY() - pointA.getY(), pointB.getX() - pointA.getX()); + + 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()); + + final double rotationAngleInRad = Math.toRadians(360.0 / sidesCount); + + for (int i = 2; i < sidesCount; i++) { + points[i] = (Point2D.Double) rotatePointAboutPivot(points[i - 1], points[i - 2], rotationAngleInRad); + } + + 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. + * @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 +148,23 @@ public int[][] getOrderedPoints(int sidesCount, int length, int centerX, int cen } /** - * Transforms a given Polygon using the specified AffineTransform. + * 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 getPolygonFromTwoPoints(Point2D pointA, Point2D pointB, int sidesCount) { + return getRegularPolygon(pointA, pointB, sidesCount); + } + + /** + * 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); From e232d5d895168a139fd0fc35cafffec53c0fac41 Mon Sep 17 00:00:00 2001 From: creme332 <65414576+creme332@users.noreply.github.com> Date: Wed, 31 Jul 2024 17:36:46 +0400 Subject: [PATCH 2/2] rework tilted regular polygon --- .../canvas/drawing/DrawRegularPolygon.java | 56 ++++++++++++------- .../model/calculator/PolygonCalculator.java | 45 ++++++++------- 2 files changed, 58 insertions(+), 43 deletions(-) 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 03a9610e..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 @@ -26,48 +26,54 @@ public DrawRegularPolygon(AppState app, Canvas canvas) { @Override protected void handleMouseMoved(Point2D polySpaceMousePosition) { - if (preview != null && firstVertex != null && secondVertex != null) { - Polygon polygon = calculator.getPolygonFromTwoPoints(firstVertex, secondVertex, numSides); - preview.setShape(polygon); - canvas.repaint(); - } + // do nothing } @Override protected void handleMousePressed(Point2D polySpaceMousePosition) { 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); + + // 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 => reset state and cancel operation - firstVertex = null; - secondVertex = null; disposePreview(); return; } - // initialize shape preview after getting valid input - preview = new ShapeWrapper(canvasModel.getShapeColor(), - canvasModel.getLineType(), - canvasModel.getLineThickness()); - preview.getPlottedPoints().add(firstVertex); - preview.getPlottedPoints().add(secondVertex); + // generate new polygon + Polygon polygon = calculator.getRegularPolygon(firstVertex, secondVertex, numSides); + preview.setShape(polygon); - // save preview - canvasModel.getShapeManager().setShapePreview(preview); + // save shape + canvasModel.getShapeManager().addShape(preview); - Polygon polygon = calculator.getPolygonFromTwoPoints(firstVertex, secondVertex, numSides); - preview.setShape(polygon); canvas.repaint(); - } else { - canvasModel.getShapeManager().addShape(preview); + + // discard preview disposePreview(); } } @@ -81,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 The number of vertices entered by the user or -1 if invalid or cancelled. + * @return The number of vertices entered by the user or -1 if invalid or + * cancelled. */ private int inputVertices() { JTextField numSidesField = new JTextField(5); @@ -104,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 8eaec066..c7038884 100644 --- a/src/main/java/com/github/creme332/model/calculator/PolygonCalculator.java +++ b/src/main/java/com/github/creme332/model/calculator/PolygonCalculator.java @@ -36,7 +36,7 @@ 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. + * @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) { @@ -89,27 +89,38 @@ private Shape getRegularPolygon(int sidesCount, int length) { } /** - * Calculates a regular polygon given two adjacent vertices and the number of sides. + * 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 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. */ - private Polygon getRegularPolygon(Point2D pointA, Point2D pointB, int sidesCount) { - double length = pointA.distance(pointB); - double angle = Math.atan2(pointB.getY() - pointA.getY(), pointB.getX() - pointA.getX()); - - Point2D.Double[] points = new Point2D.Double[sidesCount]; + 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()); - final double rotationAngleInRad = Math.toRadians(360.0 / sidesCount); + /** + * 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++) { - points[i] = (Point2D.Double) rotatePointAboutPivot(points[i - 1], points[i - 2], rotationAngleInRad); + 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++) { @@ -147,18 +158,6 @@ public int[][] getOrderedPoints(int sidesCount, int length, int centerX, int cen return new int[][] { xOrdered, yOrdered }; } - /** - * 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 getPolygonFromTwoPoints(Point2D pointA, Point2D pointB, int sidesCount) { - return getRegularPolygon(pointA, pointB, sidesCount); - } - /** * Transforms a polygon using the given affine transformation. *