diff --git a/src/main/java/net/rptools/maptool/model/Grid.java b/src/main/java/net/rptools/maptool/model/Grid.java index 145b861ca2..d319130f53 100644 --- a/src/main/java/net/rptools/maptool/model/Grid.java +++ b/src/main/java/net/rptools/maptool/model/Grid.java @@ -411,9 +411,12 @@ public void setSize(int size) { } int visionDistance = zone.getTokenVisionInPixels(); double visionRange = (range == 0) ? visionDistance : range * getSize() / zone.getUnitsPerCell(); + /* Token facing as an angle. 0° points to the right and clockwise is positive. */ + int tokenFacingAngle = token.getFacingInDegrees() + 90; + Rectangle footprint = token.getFootprint(this).getBounds(this); if (scaleWithToken) { - double footprintWidth = token.getFootprint(this).getBounds(this).getWidth() / 2; + double footprintWidth = footprint.getWidth() / 2; // Test for gridless maps var cellShape = getCellShape(); @@ -423,85 +426,76 @@ public void setSize(int size) { } else { // For grids, this will be the same, but for Hex's we'll use the smaller side depending on // which Hex type you choose - double footprintHeight = token.getFootprint(this).getBounds(this).getHeight() / 2; + double footprintHeight = footprint.getHeight() / 2; visionRange += Math.min(footprintWidth, footprintHeight); } } - Area visibleArea = new Area(); + Area visibleArea; switch (shape) { - case CIRCLE: + case CIRCLE -> { visibleArea = GraphicsUtil.createLineSegmentEllipse( -visionRange, -visionRange, visionRange, visionRange, CIRCLE_SEGMENTS); - break; - case GRID: + } + case GRID -> { visibleArea = getGridArea(token, range, scaleWithToken, visionRange); - break; - case SQUARE: + } + case SQUARE -> { visibleArea = new Area( new Rectangle2D.Double( -visionRange, -visionRange, visionRange * 2, visionRange * 2)); - break; - case BEAM: + } + case BEAM -> { // Make at least 1 pixel on each side, so it's at least visible at 100% zoom. var pixelWidth = Math.max(2, width * getSize() / zone.getUnitsPerCell()); Shape lineShape = new Rectangle2D.Double(0, -pixelWidth / 2, visionRange, pixelWidth); - Shape visibleShape = new GeneralPath(lineShape); visibleArea = new Area( - AffineTransform.getRotateInstance( - Math.toRadians(offsetAngle) - Math.toRadians(token.getFacing())) - .createTransformedShape(visibleShape)); - break; - case CONE: + AffineTransform.getRotateInstance(Math.toRadians(offsetAngle + tokenFacingAngle)) + .createTransformedShape(lineShape)); + } + case CONE -> { Arc2D cone = new Arc2D.Double( -visionRange, -visionRange, visionRange * 2, visionRange * 2, - 360.0 - (arcAngle / 2.0) + (offsetAngle * 1.0), + (offsetAngle - tokenFacingAngle) - arcAngle / 2., arcAngle, Arc2D.PIE); // Flatten the cone to remove 'curves' GeneralPath path = new GeneralPath(); path.append(cone.getPathIterator(null, 1), false); - Area tempvisibleArea = new Area(path); - - // Rotate - tempvisibleArea = - tempvisibleArea.createTransformedArea( - AffineTransform.getRotateInstance(-Math.toRadians(token.getFacing()))); - - Rectangle footprint = token.getFootprint(this).getBounds(this); - footprint.x = -footprint.width / 2; - footprint.y = -footprint.height / 2; - - visibleArea.add(new Area(footprint)); - visibleArea.add(tempvisibleArea); - break; - case HEX: - footprint = token.getFootprint(this).getBounds(this); + visibleArea = new Area(path); + + var footprintPart = new Rectangle(footprint); + footprintPart.x = -footprintPart.width / 2; + footprintPart.y = -footprintPart.height / 2; + visibleArea.add(new Area(footprintPart)); + } + case HEX -> { double x = footprint.getCenterX(); double y = footprint.getCenterY(); - double footprintWidth = token.getFootprint(this).getBounds(this).getWidth(); - double footprintHeight = token.getFootprint(this).getBounds(this).getHeight(); + double footprintWidth = footprint.getWidth(); + double footprintHeight = footprint.getHeight(); double adjustment = Math.min(footprintWidth, footprintHeight); x -= adjustment / 2; y -= adjustment / 2; visibleArea = createHex(x, y, visionRange, 0); - break; - default: + } + default -> { + log.error("Unhandled shape {}; treating as a circle", shape); visibleArea = GraphicsUtil.createLineSegmentEllipse( -visionRange, -visionRange, visionRange * 2, visionRange * 2, CIRCLE_SEGMENTS); - break; + } } return visibleArea; diff --git a/src/main/java/net/rptools/maptool/model/IsometricGrid.java b/src/main/java/net/rptools/maptool/model/IsometricGrid.java index 2e9d174227..319e6bb571 100644 --- a/src/main/java/net/rptools/maptool/model/IsometricGrid.java +++ b/src/main/java/net/rptools/maptool/model/IsometricGrid.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.annotation.Nonnull; import javax.swing.Action; import javax.swing.KeyStroke; import net.rptools.maptool.client.AppPreferences; @@ -267,7 +268,7 @@ public void uninstallMovementKeys(Map actionMap) { } @Override - public Area getShapedArea( + public @Nonnull Area getShapedArea( ShapeType shape, Token token, double range, @@ -281,37 +282,32 @@ public Area getShapedArea( int visionDistance = getZone().getTokenVisionInPixels(); double visionRange = (range == 0) ? visionDistance : range * getSize() / getZone().getUnitsPerCell(); + int tokenFacingAngle = token.getFacingInDegrees() + 90; + Rectangle footprint = token.getFootprint(this).getBounds(this); if (scaleWithToken) { - Rectangle footprint = token.getFootprint(this).getBounds(this); visionRange += footprint.getHeight() / 2; - System.out.println(token.getName() + " footprint.getWidth() " + footprint.getWidth()); - System.out.println(token.getName() + " footprint.getHeight() " + footprint.getHeight()); } - // System.out.println("this.getDefaultFootprint() " + this.getDefaultFootprint()); - // System.out.println("token.getWidth() " + token.getWidth()); Area visibleArea = new Area(); switch (shape) { - case CIRCLE: + case CIRCLE -> { visionRange = (float) Math.sin(Math.toRadians(45)) * visionRange; - // visibleArea = new Area(new Ellipse2D.Double(-visionRange * 2, -visionRange, visionRange * - // 4, visionRange * 2)); visibleArea = GraphicsUtil.createLineSegmentEllipse( -visionRange * 2, -visionRange, visionRange * 2, visionRange, CIRCLE_SEGMENTS); - break; - case SQUARE: - int x[] = {0, (int) visionRange * 2, 0, (int) -visionRange * 2}; - int y[] = {(int) -visionRange, 0, (int) visionRange, 0}; + } + case SQUARE -> { + int[] x = {0, (int) visionRange * 2, 0, (int) -visionRange * 2}; + int[] y = {(int) -visionRange, 0, (int) visionRange, 0}; visibleArea = new Area(new Polygon(x, y, 4)); - break; - case BEAM: + } + case BEAM -> { var pixelWidth = Math.max(2, width * getSize() / getZone().getUnitsPerCell()); Shape visibleShape = new Rectangle2D.Double(0, -pixelWidth / 2, visionRange, pixelWidth); // new angle, corrected for isometric view - double theta = Math.toRadians(offsetAngle) + Math.toRadians(token.getFacing()); + double theta = Math.toRadians(offsetAngle - tokenFacingAngle); Point2D angleVector = new Point2D.Double(Math.cos(theta), Math.sin(theta)); AffineTransform at = new AffineTransform(); at.rotate(Math.PI / 4); @@ -324,35 +320,25 @@ public Area getShapedArea( new Area( AffineTransform.getRotateInstance(theta + Math.toRadians(45)) .createTransformedShape(visibleShape)); - - break; - case CONE: + } + case CONE -> { // Rotate the vision range by 45 degrees for isometric view visionRange = (float) Math.sin(Math.toRadians(45)) * visionRange; // Get the cone, use degreesFromIso to convert the facing from isometric to plan - // Area tempvisibleArea = new Area(new Arc2D.Double(-visionRange * 2, -visionRange, - // visionRange * 4, visionRange * 2, token.getFacing() - (arcAngle / 2.0) + (offsetAngle * - // 1.0), arcAngle, - // Arc2D.PIE)); Arc2D cone = new Arc2D.Double( -visionRange * 2, -visionRange, visionRange * 4, visionRange * 2, - token.getFacing() - (arcAngle / 2.0) + (offsetAngle * 1.0), + (offsetAngle - tokenFacingAngle) - (arcAngle / 2.0), arcAngle, Arc2D.PIE); GeneralPath path = new GeneralPath(); path.append(cone.getPathIterator(null, 1), false); // Flatten the cone to remove 'curves' Area tempvisibleArea = new Area(path); - // Get the cell footprint - Rectangle footprint = - token.getFootprint(getZone().getGrid()).getBounds(getZone().getGrid()); - footprint.x = -footprint.width / 2; - footprint.y = -footprint.height / 2; // convert the cell footprint to an area Area cellShape = createCellShape(footprint.height); // convert the area to isometric view @@ -362,14 +348,14 @@ public Area getShapedArea( // join cell footprint and cone to create viewable area visibleArea.add(cellShape); visibleArea.add(tempvisibleArea); - break; - default: - // visibleArea = new Area(new Ellipse2D.Double(-visionRange, -visionRange, visionRange * 2, - // visionRange * 2)); + } + default -> { + log.error("Unhandled shape {}; treating as a circle", shape); + visionRange = (float) Math.sin(Math.toRadians(45)) * visionRange; visibleArea = GraphicsUtil.createLineSegmentEllipse( - -visionRange, -visionRange, visionRange, visionRange, CIRCLE_SEGMENTS); - break; + -visionRange * 2, -visionRange, visionRange * 2, visionRange, CIRCLE_SEGMENTS); + } } return visibleArea; } diff --git a/src/main/java/net/rptools/maptool/model/Token.java b/src/main/java/net/rptools/maptool/model/Token.java index 0252943ddd..7b9b94cab5 100644 --- a/src/main/java/net/rptools/maptool/model/Token.java +++ b/src/main/java/net/rptools/maptool/model/Token.java @@ -875,7 +875,9 @@ public int getFacing() { /** * This returns the rotation of the facing of the token from the default facing of down or -90. - * Positive for CW and negative for CCW. + * + *

Positive for CW and negative for CCW. The range is currently from -270° (inclusive) to +90° + * (exclusive), but callers should not rely on this. * * @return angle in degrees */