diff --git a/src/main/java/com/github/creme332/controller/canvas/drawing/DrawEllipse.java b/src/main/java/com/github/creme332/controller/canvas/drawing/DrawEllipse.java index aa9983b2..1152cc14 100644 --- a/src/main/java/com/github/creme332/controller/canvas/drawing/DrawEllipse.java +++ b/src/main/java/com/github/creme332/controller/canvas/drawing/DrawEllipse.java @@ -2,6 +2,10 @@ import java.awt.Polygon; import java.awt.geom.Point2D; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JOptionPane; +import javax.swing.JTextField; import com.github.creme332.model.AppState; import com.github.creme332.model.Mode; @@ -35,39 +39,112 @@ public void handleMouseMoved(Point2D polySpaceMousePosition) { @Override public void handleMousePressed(Point2D polySpaceMousePosition) { - if (preview == null) { - // center of ellipse has been selected + if (getCanvasMode() == Mode.DRAW_ELLIPSE) { + if (preview == null) { + // First focus has been selected + + // Create a shape wrapper + preview = new ShapeWrapper(canvasModel.getShapeColor(), + canvasModel.getLineType(), + canvasModel.getLineThickness()); + preview.getPlottedPoints().add(polySpaceMousePosition); + + // Save preview + canvasModel.getShapeManager().setShapePreview(preview); + return; + } - // create a shape wrapper - preview = new ShapeWrapper(canvasModel.getShapeColor(), - canvasModel.getLineType(), - canvasModel.getLineThickness()); - preview.getPlottedPoints().add(polySpaceMousePosition); + if (preview.getPlottedPoints().size() == 1) { + // Second focus has now been selected + preview.getPlottedPoints().add(polySpaceMousePosition); + } else { + // Third point has been selected + preview.getPlottedPoints().add(polySpaceMousePosition); - // save preview - canvasModel.getShapeManager().setShapePreview(preview); + // Save preview as an actual shape + canvasModel.getShapeManager().addShape(preview); - return; - } + disposePreview(); + } + } else if (getCanvasMode() == Mode.DRAW_ELLIPSE_FIXED) { + if (preview == null) { + // First focus has been selected + + // Create preview and plot the first focus + preview = new ShapeWrapper(canvasModel.getShapeColor(), + canvasModel.getLineType(), + canvasModel.getLineThickness()); + preview.getPlottedPoints().add(polySpaceMousePosition); + + // Save preview + canvasModel.getShapeManager().setShapePreview(preview); + return; + } - if (preview.getPlottedPoints().size() == 1) { - // second point has now been selected - preview.getPlottedPoints().add(polySpaceMousePosition); - } else { - // third point has been selected - preview.getPlottedPoints().add(polySpaceMousePosition); + if (preview.getPlottedPoints().size() == 1) { + // Second focus has been selected + preview.getPlottedPoints().add(polySpaceMousePosition); - // save preview as an actual shape - canvasModel.getShapeManager().addShape(preview); + // Ask user for the radii + int[] radii = inputRadii(); - disposePreview(); - } + if (radii == null || radii[0] <= 0 || radii[1] <= 0) { + disposePreview(); + return; + } + + final Point2D firstFocus = preview.getPlottedPoints().get(0); + final Point2D secondFocus = preview.getPlottedPoints().get(1); + int[][] coordinates = ellipseCalculator.getOrderedPointsWithRadius(firstFocus, secondFocus, radii[0], radii[1]); + + if (coordinates.length == 2) { + Polygon ellipse = new Polygon(coordinates[0], coordinates[1], coordinates[0].length); + preview.setShape(ellipse); + // Save preview as an actual shape + canvasModel.getShapeManager().addShape(preview); + + disposePreview(); + } + } + } } @Override public boolean shouldDraw() { - return getCanvasMode() == Mode.DRAW_ELLIPSE; + return getCanvasMode() == Mode.DRAW_ELLIPSE || getCanvasMode() == Mode.DRAW_ELLIPSE_FIXED; } + /** + * Asks user to enter the radii for the ellipse. If input values are invalid + * or if the operation is cancelled, null is returned. + * + * @return array with radii [rx, ry] + */ + private int[] inputRadii() { + JTextField rxField = new JTextField(5); + JTextField ryField = new JTextField(5); + JPanel panel = new JPanel(); + panel.add(new JLabel("Radius X:")); + panel.add(rxField); + panel.add(new JLabel("Radius Y:")); + panel.add(ryField); + + int result = JOptionPane.showConfirmDialog(null, panel, "Ellipse: Foci & Radius", JOptionPane.OK_CANCEL_OPTION, + JOptionPane.PLAIN_MESSAGE); + + // Request focus again otherwise keyboard shortcuts will not work + canvas.getTopLevelAncestor().requestFocus(); + + if (result == JOptionPane.OK_OPTION) { + try { + int rx = Integer.parseInt(rxField.getText()); + int ry = Integer.parseInt(ryField.getText()); + return new int[]{rx, ry}; + } catch (NumberFormatException e) { + return null; + } + } + return null; + } } diff --git a/src/main/java/com/github/creme332/model/calculator/EllipseCalculator.java b/src/main/java/com/github/creme332/model/calculator/EllipseCalculator.java index fe205a1b..9211596b 100644 --- a/src/main/java/com/github/creme332/model/calculator/EllipseCalculator.java +++ b/src/main/java/com/github/creme332/model/calculator/EllipseCalculator.java @@ -50,8 +50,9 @@ public static int[] transformPoint(int x, int y, int destinationQuadrant) { * * @param centerX x-coordinate of circle center * @param centerY y-coordinate of circle center - * @param radius radius of circle - * @return A list of 2 arrays where tThe first array is the list of + * @param rx radius along the x-axis + * @param ry radius along the y-axis + * @return A list of 2 arrays where the first array is the list of * x-coordinates and the second array is a list of y-coordinates. */ public int[][] getOrderedPoints(int centerX, int centerY, int rx, int ry) { @@ -92,10 +93,9 @@ public int[][] getOrderedPoints(int centerX, int centerY, int rx, int ry) { * Calculates coordinates of circle starting from top and moving clockwise. * Points are ordered clockwise. Duplicate points may occur at x==0 and x==y. * - * @param centerX x-coordinate of circle center - * @param centerY y-coordinate of circle center - * @param radius radius of circle - * @return A list of 2 arrays where tThe first array is the list of + * @param rx radius along the x-axis + * @param ry radius along the y-axis + * @return A list of 2 arrays where the first array is the list of * x-coordinates and the second array is a list of y-coordinates. */ public List> getFirstQuadrantPoints(int rx, int ry) { @@ -327,4 +327,46 @@ public int[][] getOrderedPoints(Point2D firstFocus, Point2D secondFocus, Point2D return points; } + + /** + * Calculates integer pixel coordinates of an ellipse given its foci and fixed + * radii. + * + * @param firstFocus Coordinates of the first focus of the ellipse + * @param secondFocus Coordinates of the second focus of the ellipse + * @param rx Radius along the x-axis (horizontal radius) + * @param ry Radius along the y-axis (vertical radius) + * @return A list of 2 arrays where the first array is the list of x-coordinates + * and the second array is a list of y-coordinates. + */ + public int[][] getOrderedPointsWithRadius(Point2D firstFocus, Point2D secondFocus, int rx, int ry) { + /** + * Coordinates of the center of the ellipse. + */ + final Point2D center = new Point2D.Double((firstFocus.getX() + secondFocus.getX()) / 2, + (firstFocus.getY() + secondFocus.getY()) / 2); + + if (rx <= 0 || ry <= 0) { + throw new IllegalArgumentException("Radii must be positive values."); + } + + int[][] points = getOrderedPoints((int) center.getX(), (int) center.getY(), rx, ry); + + /** + * Angle which the semi-major axis makes with the horizontal. + */ + final double inclinationAngle = Math.atan2(secondFocus.getY() - firstFocus.getY(), + secondFocus.getX() - firstFocus.getX()); + + // Rotate calculated points based on inclination + for (int i = 0; i < points[0].length; i++) { + Point2D vector = new Point2D.Double(points[0][i] - center.getX(), points[1][i] - center.getY()); + vector = PolygonCalculator.rotateVector(vector, inclinationAngle); + points[0][i] = (int) (vector.getX() + center.getX()); + points[1][i] = (int) (vector.getY() + center.getY()); + } + + return points; + } + }