From 72194870f7e37b9b086cc6abe2f42f01b44363e7 Mon Sep 17 00:00:00 2001 From: Werner Randelshofer Date: Fri, 27 Sep 2024 18:14:29 +0200 Subject: [PATCH] Fix layout algorithm in AbstractElbowLineConnectionWithMarkersFigure. --- .../draw/connector/LocatorConnector.java | 24 -- .../draw/connector/PathConnector.java | 17 +- ...tElbowLineConnectionWithMarkersFigure.java | 219 ++++++++++++------ ...stractPathConnectionWithMarkersFigure.java | 44 ++-- .../ElbowConnectionWithMarkersFigure.java | 6 + .../draw/figure/ElbowableLineFigure.java | 3 +- .../draw/figure/PathMetricsFigure.java | 20 ++ .../draw/key/NullableCssSizeStyleableKey.java | 11 +- .../mini/BezierArcLengthExampleMain.java | 5 +- .../org/jhotdraw8/geom/Angles.java | 2 +- .../geom/CubicCurveCharacteristics.java | 8 +- .../geom/CubicCurveToQuadCurves.java | 2 +- .../org/jhotdraw8/geom/FXGeom.java | 41 +++- .../org/jhotdraw8/geom/Points.java | 18 +- .../org/jhotdraw8/geom/Scalars.java | 25 ++ .../geom/contour/ContourIntersections.java | 7 +- .../jhotdraw8/geom/contour/PlineVertex.java | 5 +- .../org/jhotdraw8/geom/contour/Utils.java | 4 +- .../geom/intersect/IntersectCircleCircle.java | 9 +- .../geom/intersect/IntersectCirclePoint.java | 4 +- .../IntersectCubicCurveCubicCurve.java | 3 +- .../intersect/IntersectCubicCurveLine.java | 6 +- .../IntersectCubicCurvePathIterator.java | 4 +- .../intersect/IntersectLinePathIterator.java | 4 +- .../geom/intersect/IntersectLinePoint.java | 6 +- .../intersect/IntersectPathIteratorPoint.java | 4 +- .../geom/intersect/IntersectPointRay.java | 10 +- .../IntersectQuadCurvePathIterator.java | 4 +- .../geom/intersect/IntersectRayRay.java | 16 +- .../geom/contour/ContourBuilderTest.java | 18 +- 30 files changed, 334 insertions(+), 215 deletions(-) create mode 100755 org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/figure/PathMetricsFigure.java create mode 100644 org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/Scalars.java diff --git a/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/connector/LocatorConnector.java b/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/connector/LocatorConnector.java index 4dc1fe95a..1101d8f38 100755 --- a/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/connector/LocatorConnector.java +++ b/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/connector/LocatorConnector.java @@ -40,28 +40,4 @@ public Locator getLocator() { public PointAndDerivative getPointAndDerivativeInLocal(Figure connection, Figure target) { return new PointAndDerivative(locator.locate(target).getX(), locator.locate(target).getY(), new Point2D(1, 0).getX(), new Point2D(1, 0).getY()); } - - /* - @Override - public IntersectionPointEx chopStart(RenderContext ctx, Figure connection, Figure target, double startX, double startY, double endX, double endY) { - final Bounds b = target.getLayoutBounds(); - Point2D center = new Point2D(b.getMinX() + b.getWidth() * 0.5, b.getMinY() + b.getHeight() * 0.5); - Point2D location = locator.locate(target); - Point2D direction = location.subtract(center); - Point2D derivative1 = new Point2D(direction.getY(), -direction.getX()); - Point2D derivative2 = new Point2D(direction.getX(), direction.getY()); - if (FXGeom.squaredMagnitude(derivative1) < 1e-6) { - derivative1 = new Point2D(1, 0); - derivative2 = new Point2D(0, -1); - } - - Transform localToWorld = target.getLocalToWorld(); - Point2D targetP = target.localToWorld(location); - Point2D t1p = localToWorld == null ? derivative1 : localToWorld.deltaTransform(derivative1); - Point2D t2p = localToWorld == null ? derivative2 : localToWorld.deltaTransform(derivative2); - return new IntersectionPointEx( - targetP.getX(), targetP.getY(), - 0, t1p.getX(), t1p.getY(), - 0, t2p.getX(), t2p.getY()); - }*/ } diff --git a/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/connector/PathConnector.java b/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/connector/PathConnector.java index 0e4cad034..4f21e87f9 100755 --- a/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/connector/PathConnector.java +++ b/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/connector/PathConnector.java @@ -16,9 +16,6 @@ import java.awt.geom.PathIterator; -import static org.jhotdraw8.draw.figure.StrokableFigure.STROKE; -import static org.jhotdraw8.draw.figure.StrokableFigure.STROKE_TYPE; - /** * PathConnector. The target of the connection must implement {@link PathIterableFigure}. * @@ -43,19 +40,7 @@ public IntersectionPointEx intersect(RenderContext ctx, Figure connection, Figur } Point2D s = target.worldToLocal(start); Point2D e = target.worldToLocal(end); - PathIterator pit; - - // FIXME does not take line join into account - if (target.getStyled(STROKE) != null) { - pit = switch (target.getStyledNonNull(STROKE_TYPE)) { - default -> - // FIXME must stroke the path - pif.getPathIterator(ctx, null); - case INSIDE -> pif.getPathIterator(ctx, null); - }; - } else { - pit = pif.getPathIterator(ctx, null); - } + PathIterator pit = pif.getPathIterator(ctx, null); IntersectionResultEx i = IntersectLinePathIterator.intersectLinePathIteratorEx(s.getX(), s.getY(), e.getX(), e.getY(), pit); return i.intersections().peekLast(); diff --git a/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/figure/AbstractElbowLineConnectionWithMarkersFigure.java b/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/figure/AbstractElbowLineConnectionWithMarkersFigure.java index 04bba6e0f..dd2a1d548 100755 --- a/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/figure/AbstractElbowLineConnectionWithMarkersFigure.java +++ b/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/figure/AbstractElbowLineConnectionWithMarkersFigure.java @@ -30,6 +30,7 @@ import org.jhotdraw8.geom.FXGeom; import org.jhotdraw8.geom.FXPreciseRotate; import org.jhotdraw8.geom.PointAndDerivative; +import org.jhotdraw8.geom.intersect.IntersectRayRay; import org.jhotdraw8.geom.intersect.IntersectionPointEx; import org.jhotdraw8.icollection.immutable.ImmutableList; import org.jspecify.annotations.Nullable; @@ -38,8 +39,8 @@ import java.awt.geom.PathIterator; import java.util.List; -import static java.lang.Math.abs; -import static java.lang.Math.signum; +import static org.jhotdraw8.draw.render.RenderContext.UNIT_CONVERTER_KEY; +import static org.jhotdraw8.geom.Scalars.almostEqual; /** * AbstractElbowLineConnectionWithMarkersFigure draws a straight line or an elbow line from start to end. @@ -116,7 +117,7 @@ public void createHandles(HandleType handleType, List list) { * @param node the node */ protected void updateLineNode(RenderContext ctx, Polyline node) { - + // empty } /** @@ -143,51 +144,49 @@ protected void updateEndMarkerNode(RenderContext ctx, Path node) { @Override public void updateNode(RenderContext ctx, Node node) { - Group g = (Group) node; - Polyline lineNode = (Polyline) g.getChildren().get(0); - final Path startMarkerNode = (Path) g.getChildren().get(1); - final Path endMarkerNode = (Path) g.getChildren().get(2); - - Point2D start = getNonNull(START).getConvertedValue(); - Point2D end = getNonNull(END).getConvertedValue(); - - final double startInset = getStrokeCutStart(ctx); - final double endInset = getStrokeCutEnd(ctx); - final ImmutableList startMarkerShape = getMarkerStartShape(); - - ObservableList points = lineNode.getPoints(); - - points.setAll(path.getPoints()); - int size = points.size(); - Point2D p0, p1, p3, p2; - if (size > 4) { - p0 = new Point2D(points.get(0), points.get(1)); - p1 = new Point2D(points.get(2), points.get(3)); - p3 = new Point2D(points.get(size - 2), points.get(size - 1)); - p2 = new Point2D(points.get(size - 4), points.get(size - 3)); - } else { - p2 = p0 = new Point2D(points.get(0), points.get(1)); - p3 = p1 = new Point2D(points.get(2), points.get(3)); - } - updateMarkerNode(ctx, g, startMarkerNode, - new PointAndDerivative(p0.getX(), p0.getY(), p1.getX() - p0.getX(), p1.getY() - p0.getY()), - startMarkerShape, getMarkerStartScaleFactor()); - final ImmutableList endMarkerShape = getMarkerEndShape(); - updateMarkerNode(ctx, g, endMarkerNode, - new PointAndDerivative(p3.getX(), p3.getY(), p2.getX() - p3.getX(), p2.getY() - p3.getY()), - endMarkerShape, getMarkerEndScaleFactor()); - - Point2D dir = end.subtract(start).normalize(); - if (startInset != 0) { - start = start.add(dir.multiply(startInset)); - } - if (endInset != 0) { - end = end.add(dir.multiply(-endInset)); + try { + Group g = (Group) node; + Polyline lineNode = (Polyline) g.getChildren().get(0); + final Path startMarkerNode = (Path) g.getChildren().get(1); + final Path endMarkerNode = (Path) g.getChildren().get(2); + + Point2D start = getNonNull(START).getConvertedValue(); + Point2D end = getNonNull(END).getConvertedValue(); + + final double startInset = getStrokeCutStart(ctx); + final double endInset = getStrokeCutEnd(ctx); + final ImmutableList startMarkerShape = getMarkerStartShape(); + + ObservableList points = lineNode.getPoints(); + + points.setAll(path.getPoints()); + int size = points.size(); + Point2D p0, p1, p3, p2; + if (size > 4) { + p0 = new Point2D(points.get(0), points.get(1)); + p1 = new Point2D(points.get(2), points.get(3)); + p3 = new Point2D(points.get(size - 2), points.get(size - 1)); + p2 = new Point2D(points.get(size - 4), points.get(size - 3)); + } else if (size == 4) { + p2 = p0 = new Point2D(points.get(0), points.get(1)); + p3 = p1 = new Point2D(points.get(2), points.get(3)); + } else { + p2 = p0 = p1 = p3 = new Point2D(0, 0); + } + updateMarkerNode(ctx, g, startMarkerNode, + new PointAndDerivative(p0.getX(), p0.getY(), p1.getX() - p0.getX(), p1.getY() - p0.getY()), + startMarkerShape, getMarkerStartScaleFactor()); + final ImmutableList endMarkerShape = getMarkerEndShape(); + updateMarkerNode(ctx, g, endMarkerNode, + new PointAndDerivative(p3.getX(), p3.getY(), p2.getX() - p3.getX(), p2.getY() - p3.getY()), + endMarkerShape, getMarkerEndScaleFactor()); + + updateLineNode(ctx, lineNode); + updateStartMarkerNode(ctx, startMarkerNode); + updateEndMarkerNode(ctx, endMarkerNode); + } catch (Throwable t) { + t.printStackTrace(); } - - updateLineNode(ctx, lineNode); - updateStartMarkerNode(ctx, startMarkerNode); - updateEndMarkerNode(ctx, endMarkerNode); } protected void updateMarkerNode(RenderContext ctx, Group group, @@ -243,8 +242,6 @@ public void layout(RenderContext ctx) { Connector endConnector = get(END_CONNECTOR); Figure startTarget = get(START_TARGET); Figure endTarget = get(END_TARGET); - CssSize elbowOffset1 = getElbowOffset(); - double elbowOffset = elbowOffset1 == null ? 0.0 : ctx.getNonNull(RenderContext.UNIT_CONVERTER_KEY).convert(elbowOffset1, UnitConverter.DEFAULT); ObservableList points = path.getPoints(); @@ -258,8 +255,10 @@ public void layout(RenderContext ctx) { end = endConnector.getPointAndDerivativeInWorld(this, endTarget).getPoint(Point2D::new); } // Chop start and end points + Point2D startDerivative = null; if (startConnector != null && startTarget != null) { IntersectionPointEx intersectionPointEx = startConnector.chopStart(ctx, this, startTarget, start, end); + startDerivative = new Point2D(intersectionPointEx.getDerivativeB().getX(), intersectionPointEx.getDerivativeB().getY()); start = worldToParent(intersectionPointEx.getX(), intersectionPointEx.getY()); set(START, new CssPoint2D(start)); } @@ -270,32 +269,110 @@ public void layout(RenderContext ctx) { end = worldToParent(intersectionPointEx.getX(), intersectionPointEx.getY()); set(END, new CssPoint2D(end)); } + Point2D lineDerivative = end.subtract(start); + if (startDerivative == null) { + startDerivative = lineDerivative; + } + if (endDerivative == null) { + endDerivative = lineDerivative.multiply(-1); + } - CssSize elbowOffsetSize = getElbowOffset(); - if (elbowOffset == 0 || endDerivative == null || FXGeom.squaredMagnitude(endDerivative) < 1e-7) { - points.addAll(start.getX(), start.getY()); - points.addAll(end.getX(), end.getY()); + + points.add(start.getX()); + points.add(start.getY()); + if (start.getX() == end.getX() || start.getY() == end.getY()) { + // case 1: The line is horizontal or vertical: we draw a straight line + // nothing to do } else { - Point2D endDerivativeNormalized = endDerivative.normalize(); - // Enforce perfect vertical or perfect horizontal line - if (abs(endDerivativeNormalized.getX()) > abs(endDerivativeNormalized.getY())) { - endDerivativeNormalized = new Point2D(signum(endDerivativeNormalized.getX()), 0); - } else { - endDerivativeNormalized = new Point2D(0, signum(endDerivativeNormalized.getY())); + // Compute perpendicular to boundary of start and end target shape + Point2D startPerp = FXGeom.perp(startDerivative).normalize(); + Point2D endPerp = FXGeom.perp(endDerivative).normalize(); + // Flip perpendiculars if necessary, so that each points towards the other shape + Point2D lineDerivativeNormalized = lineDerivative.normalize(); + if (!FXGeom.isSameDirection(startPerp, lineDerivativeNormalized)) { + startPerp = new Point2D(-startPerp.getX(), -startPerp.getY()); } - Point2D dir = new Point2D(endDerivative.getY(), -endDerivative.getX()).normalize(); - Point2D p1; - Point2D p2; - if (UnitConverter.PERCENTAGE.equals(elbowOffsetSize.getUnits())) { - elbowOffset = elbowOffsetSize.getConvertedValue() * abs(dir.dotProduct(end.subtract(start))); + if (FXGeom.isSameDirection(lineDerivativeNormalized, endPerp)) { + endPerp = new Point2D(-endPerp.getX(), -endPerp.getY()); + } + // Round perpendiculars to 90° angles + startPerp = FXGeom.normalizeTo90Degrees(startPerp); + endPerp = FXGeom.normalizeTo90Degrees(endPerp); + + CssSize elbowOffsetSize = getElbowOffset(); + double elbowOffset = elbowOffsetSize == null ? 0 : ctx.getNonNull(UNIT_CONVERTER_KEY).convert(elbowOffsetSize, UnitConverter.DEFAULT); + + double cosine = startPerp.dotProduct(endPerp); + if (almostEqual(cosine, 0)) { + if (elbowOffset <= 0) { + // case 2: the lines meet at a 90 degrees angle: we draw an 'L'-shape + var intersection = IntersectRayRay.intersectRayRayEx( + start.getX(), start.getY(), startPerp.getX(), startPerp.getY(), + end.getX(), end.getY(), endPerp.getX(), endPerp.getY() + ).intersections().getFirst(); + points.add(intersection.getX()); + points.add(intersection.getY()); + } else { + // case 3: the lines miss their meeting point, we draw a '|_|⎺'-shape + Point2D p1 = start.add(startPerp.multiply(elbowOffset)); + double distance2 = -endPerp.dotProduct(end.subtract(p1)); + double actualOffset2 = distance2 * 0.5; + Point2D p2 = p1.add(endPerp.multiply(-actualOffset2)); + Point2D p3 = end.add(endPerp.multiply(distance2 - actualOffset2)); + points.add(p1.getX()); + points.add(p1.getY()); + points.add(p2.getX()); + points.add(p2.getY()); + points.add(p3.getX()); + points.add(p3.getY()); + } + } else { + double distance = startPerp.dotProduct(lineDerivative); + double actualOffset = (elbowOffset <= 0) ? distance * 0.5 : elbowOffset; + if (actualOffset <= distance) { + // case 4: we draw a Z-shape + Point2D p1 = start.add(startPerp.multiply(actualOffset)); + Point2D p2 = end.add(endPerp.multiply(distance - actualOffset)); + points.add(p1.getX()); + points.add(p1.getY()); + points.add(p2.getX()); + points.add(p2.getY()); + + } else { + // case 4: we draw a Z-shape + Point2D p1 = start.add(startPerp.multiply(actualOffset)); + points.add(p1.getX()); + points.add(p1.getY()); + double distance2 = endPerp.dotProduct(end.subtract(p1)); + double actualOffset2 = distance2 * 0.5; + Point2D p3 = end.add(endPerp.multiply(distance2 - actualOffset2)); + connectWithElbowLine(p1, p3, FXGeom.perp(startPerp), points); + points.add(p3.getX()); + points.add(p3.getY()); + } } - p2 = endDerivativeNormalized.multiply(elbowOffset); - p1 = endDerivativeNormalized.multiply(abs(dir.dotProduct(end.subtract(start))) - elbowOffset); - - points.addAll(start.getX(), start.getY()); - points.addAll(start.getX() - p1.getY(), start.getY() + p1.getX()); - points.addAll(end.getX() + p2.getY(), end.getY() - p2.getX()); - points.addAll(end.getX(), end.getY()); } + + points.add(end.getX()); + points.add(end.getY()); + + } + + /** + * Connects two points with a z-shaped elbow line. + * + * @param a the first point + * @param b the second point + * @param dir the direction of the z-shape + * @param points + */ + private void connectWithElbowLine(Point2D a, Point2D b, Point2D dir, ObservableList points) { + Point2D derivative = b.subtract(a); + double distance = dir.dotProduct(derivative); + double offsetDistance = distance * 0.5; + points.add(a.getX() + dir.getX() * offsetDistance); + points.add(a.getY() + dir.getY() * offsetDistance); + points.add(b.getX() - dir.getX() * (distance - offsetDistance)); + points.add(b.getY() - dir.getY() * (distance - offsetDistance)); } } diff --git a/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/figure/AbstractPathConnectionWithMarkersFigure.java b/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/figure/AbstractPathConnectionWithMarkersFigure.java index fb0afb752..55c8aef82 100755 --- a/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/figure/AbstractPathConnectionWithMarkersFigure.java +++ b/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/figure/AbstractPathConnectionWithMarkersFigure.java @@ -58,7 +58,7 @@ * @author Werner Randelshofer */ public abstract class AbstractPathConnectionWithMarkersFigure extends AbstractLineConnectionFigure - implements PathIterableFigure { + implements PathIterableFigure, PathMetricsFigure { public static final NonNullObjectStyleableKey PATH = new NonNullObjectStyleableKey<>("path", BezierPath.class, new BezierPathCssConverter(true), BezierPath.of()); @@ -157,6 +157,17 @@ public PathIterator getPathIterator(RenderContext ctx, @Nullable AffineTransform return path.getPathIterator(tx); } + @Override + public PathMetrics getPathMetrics() { + BezierPath path = get(PATH); + if (path == null || path.isEmpty()) { + Point2D start = getNonNull(START).getConvertedValue(); + Point2D end = getNonNull(END).getConvertedValue(); + return new SimplePathMetrics(new Line2D.Double(start.getX(), start.getY(), end.getX(), end.getY())); + } + return path.getPathMetrics(); + } + public abstract double getStrokeCutEnd(RenderContext ctx); public abstract double getStrokeCutStart(RenderContext ctx); @@ -222,9 +233,8 @@ public void layout(RenderContext ctx) { } @Override - public void transformInLocal(Transform tx) { - set(START, new CssPoint2D(tx.transform(getNonNull(START).getConvertedValue()))); - set(END, new CssPoint2D(tx.transform(getNonNull(END).getConvertedValue()))); + public void reshapeInLocal(Transform tx) { + super.reshapeInLocal(tx); BezierPath path = get(PATH); if (path != null) { for (int i = 0, n = path.size(); i < n; i++) { @@ -236,13 +246,11 @@ public void transformInLocal(Transform tx) { } @Override - public void translateInLocal(CssPoint2D t) { - set(START, getNonNull(START).add(t)); - set(END, getNonNull(END).add(t)); + public void transformInLocal(Transform tx) { + set(START, new CssPoint2D(tx.transform(getNonNull(START).getConvertedValue()))); + set(END, new CssPoint2D(tx.transform(getNonNull(END).getConvertedValue()))); BezierPath path = get(PATH); if (path != null) { - Point2D tc = t.getConvertedValue(); - Translate tx = new Translate(tc.getX(), tc.getY()); for (int i = 0, n = path.size(); i < n; i++) { var node = path.get(i); path = path.set(i, node.transform(tx)); @@ -252,10 +260,13 @@ public void translateInLocal(CssPoint2D t) { } @Override - public void reshapeInLocal(Transform tx) { - super.reshapeInLocal(tx); + public void translateInLocal(CssPoint2D t) { + set(START, getNonNull(START).add(t)); + set(END, getNonNull(END).add(t)); BezierPath path = get(PATH); if (path != null) { + Point2D tc = t.getConvertedValue(); + Translate tx = new Translate(tc.getX(), tc.getY()); for (int i = 0, n = path.size(); i < n; i++) { var node = path.get(i); path = path.set(i, node.transform(tx)); @@ -343,17 +354,6 @@ public void updateNode(RenderContext ctx, Node node) { updateEndMarkerNode(ctx, endMarkerNode); } - public PathMetrics getPathMetrics() { - BezierPath path = get(PATH); - if (path == null || path.isEmpty()) { - Point2D start = getNonNull(START).getConvertedValue(); - Point2D end = getNonNull(END).getConvertedValue(); - return new SimplePathMetrics(new Line2D.Double(start.getX(), start.getY(), end.getX(), end.getY())); - } - return path.getPathMetrics(); - } - - /** * This method can be overridden by a subclass to apply styles to the marker * node. diff --git a/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/figure/ElbowConnectionWithMarkersFigure.java b/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/figure/ElbowConnectionWithMarkersFigure.java index 8c03825b9..c3d95aa7b 100644 --- a/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/figure/ElbowConnectionWithMarkersFigure.java +++ b/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/figure/ElbowConnectionWithMarkersFigure.java @@ -4,6 +4,7 @@ import javafx.scene.paint.Color; import javafx.scene.shape.Path; import javafx.scene.shape.PathElement; +import javafx.scene.shape.Polyline; import org.jhotdraw8.css.value.CssSize; import org.jhotdraw8.draw.css.value.CssColor; import org.jhotdraw8.draw.render.RenderContext; @@ -89,4 +90,9 @@ public double getMarkerEndScaleFactor() { return getStyled(ElbowableLineFigure.ELBOW_OFFSET); } + @Override + protected void updateLineNode(final RenderContext ctx, final Polyline node) { + StrokableFigure.super.applyStrokableFigureProperties(ctx, node); + } + } diff --git a/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/figure/ElbowableLineFigure.java b/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/figure/ElbowableLineFigure.java index a24887311..7b453acdb 100755 --- a/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/figure/ElbowableLineFigure.java +++ b/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/figure/ElbowableLineFigure.java @@ -18,7 +18,8 @@ public interface ElbowableLineFigure extends Figure { /** * The offset of the elbow with respect of the end of the line. */ - @Nullable NullableCssSizeStyleableKey ELBOW_OFFSET = new NullableCssSizeStyleableKey("elbowOffset", CssSize.ZERO); + @Nullable + NullableCssSizeStyleableKey ELBOW_OFFSET = new NullableCssSizeStyleableKey("elbowOffset", null); /** * The offset of the elbow from the end of the line. diff --git a/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/figure/PathMetricsFigure.java b/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/figure/PathMetricsFigure.java new file mode 100755 index 000000000..efa28cffe --- /dev/null +++ b/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/figure/PathMetricsFigure.java @@ -0,0 +1,20 @@ +/* + * @(#)PathIterableFigure.java + * Copyright © 2023 The authors and contributors of JHotDraw. MIT License. + */ + +package org.jhotdraw8.draw.figure; + +import org.jhotdraw8.geom.shape.PathMetrics; + +/** + * PathMetricsFigure. + * + * @author Werner Randelshofer + */ +public interface PathMetricsFigure extends Figure { + /** + * Gets the path metrics of this figure in local coordinates. + */ + PathMetrics getPathMetrics(); +} diff --git a/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/key/NullableCssSizeStyleableKey.java b/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/key/NullableCssSizeStyleableKey.java index 711cc24d9..bd08956fb 100755 --- a/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/key/NullableCssSizeStyleableKey.java +++ b/org.jhotdraw8.draw/src/main/java/org.jhotdraw8.draw/org/jhotdraw8/draw/key/NullableCssSizeStyleableKey.java @@ -24,7 +24,16 @@ public class NullableCssSizeStyleableKey extends AbstractStyleableKey /** - * Creates a new instance with the specified name, mask and default value. + * Creates a new instance with the specified name and default value = null. + * + * @param name The name of the key. + */ + public NullableCssSizeStyleableKey(String name) { + this(name, null); + } + + /** + * Creates a new instance with the specified name and default value. * * @param name The name of the key. * @param defaultValue The default value. diff --git a/org.jhotdraw8.examples/src/main/java/org.jhotdraw8.examples/org/jhotdraw8/examples/mini/BezierArcLengthExampleMain.java b/org.jhotdraw8.examples/src/main/java/org.jhotdraw8.examples/org/jhotdraw8/examples/mini/BezierArcLengthExampleMain.java index d13735bd2..103ec7954 100644 --- a/org.jhotdraw8.examples/src/main/java/org.jhotdraw8.examples/org/jhotdraw8/examples/mini/BezierArcLengthExampleMain.java +++ b/org.jhotdraw8.examples/src/main/java/org.jhotdraw8.examples/org/jhotdraw8/examples/mini/BezierArcLengthExampleMain.java @@ -31,6 +31,7 @@ import org.jhotdraw8.geom.Integrals; import org.jhotdraw8.geom.PointAndDerivative; import org.jhotdraw8.geom.Points; +import org.jhotdraw8.geom.Scalars; import org.jhotdraw8.geom.Solvers; import java.awt.geom.PathIterator; @@ -336,7 +337,7 @@ private void updatePointsOfInterest() { curve.getEndX(), curve.getEndY()); pointsOfInterest.getChildren().clear(); - if (infl.size() == 2 && Points.almostEqual(infl.get(0), infl.get(1), 0.09)) { + if (infl.size() == 2 && Scalars.almostEqual(infl.get(0), infl.get(1), 0.09)) { double cusp = (infl.get(0) + infl.get(1)) / 2; addPoint(cusp, Color.WHITE, Color.BLACK); } else { @@ -463,7 +464,7 @@ public Point2D interpolate(double s) { private void addNonDegeneratedSegment(double moveX, double moveY, double prevX, double prevY, DoubleSummaryStatistics sum, List list) { Segment seg = new Segment(sum.getSum(), moveX, moveY, prevX, prevY); - if (!Points.almostZero(seg.length)) { + if (!Scalars.almostZero(seg.length)) { list.add(seg); sum.accept(seg.length); } diff --git a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/Angles.java b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/Angles.java index 2a78f40ec..058fbed60 100644 --- a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/Angles.java +++ b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/Angles.java @@ -36,7 +36,7 @@ public static double angle(double x1, double y1, double x2, double y2) { * @return atan2 of dy, dx or 0. */ public static double atan2(double dy, double dx) { - return Points.almostZero(dy) && Points.almostZero(dx) ? 0.0 : Math.atan2(dy, dx); + return Scalars.almostZero(dy) && Scalars.almostZero(dx) ? 0.0 : Math.atan2(dy, dx); } /** diff --git a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/CubicCurveCharacteristics.java b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/CubicCurveCharacteristics.java index c5799c179..5162505ea 100644 --- a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/CubicCurveCharacteristics.java +++ b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/CubicCurveCharacteristics.java @@ -13,7 +13,7 @@ import static java.lang.Math.sqrt; import static org.jhotdraw8.geom.Lines.isCollinear; -import static org.jhotdraw8.geom.Points.almostZero; +import static org.jhotdraw8.geom.Scalars.almostZero; public class CubicCurveCharacteristics { @@ -77,7 +77,7 @@ public static Characteristics characteristics(double x0, double y0, if (x <= 0) { double l1 = (-x * x + 3 * x) / 3; - if (Points.almostEqual(y, l1, 0.06)) { + if (Scalars.almostEqual(y, l1, 0.06)) { return Characteristics.LOOP_AT_T_0; } if (l1 < y && y < cusp) { @@ -87,7 +87,7 @@ public static Characteristics characteristics(double x0, double y0, if (0 <= x) { double l0 = (sqrt(3) * sqrt(4 * x - x * x) - x) / 2; - if (Points.almostEqual(y, l0, 0.06)) { + if (Scalars.almostEqual(y, l0, 0.06)) { return Characteristics.LOOP_AT_T_1; } if (l0 < y && y < cusp) { @@ -95,7 +95,7 @@ public static Characteristics characteristics(double x0, double y0, } } - if (Points.almostEqual(y, cusp, 0.06)) { + if (Scalars.almostEqual(y, cusp, 0.06)) { return Characteristics.CUSP; } diff --git a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/CubicCurveToQuadCurves.java b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/CubicCurveToQuadCurves.java index f941c910d..dcce23ef0 100644 --- a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/CubicCurveToQuadCurves.java +++ b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/CubicCurveToQuadCurves.java @@ -45,7 +45,7 @@ private int approximateCubicCurve(double[] p, int offsetP, double[] q, int offse } final double epsilon = 1e-6; for (double t : list) { - if (!Points.almostEqual(t, 0, epsilon) && !Points.almostEqual(t, 1, epsilon)) { + if (!Scalars.almostEqual(t, 0, epsilon) && !Scalars.almostEqual(t, 1, epsilon)) { return approximateCubicCurveSplitCase(p, offsetP, t, q, offsetQ, tolerance, maxDepth); } } diff --git a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/FXGeom.java b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/FXGeom.java index abf0054de..14d3d964d 100644 --- a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/FXGeom.java +++ b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/FXGeom.java @@ -21,6 +21,7 @@ import static java.lang.Math.abs; import static java.lang.Math.cos; +import static java.lang.Math.signum; import static java.lang.Math.sin; import static java.lang.Math.sqrt; @@ -146,6 +147,30 @@ public static boolean isCollinear(Point2D p0, Point2D p1, Point2D p2) { return Lines.isCollinear(p0.getX(), p0.getY(), p1.getX(), p1.getY(), p2.getX(), p2.getY()); } + /** + * Returns true if the two vectors point roughly in the same direction. + * + * @param p0 a vector + * @param p1 a vector + * @return true if the two vectors have a cosine greater than zero + */ + public static boolean isSameDirection(Point2D p0, Point2D p1) { + return p0.dotProduct(p1) > 0; + } + + /** + * Rounds the vector to 90 degrees and normalizes it. + * + * @param p a vector + * @return rounded to 90 degrees + */ + public static Point2D normalizeTo90Degrees(Point2D p) { + if (abs(p.getX()) > abs(p.getY())) { + return new Point2D(signum(p.getX()), 0); + } + return new Point2D(0, signum(p.getY())); + } + /** * Returns a point on the edge of the shape which crosses the line from the * center of the shape to the specified point. If no edge crosses of the @@ -175,14 +200,14 @@ public static Point2D chop(Shape shape, Point2D p) { i.next(); for (; !i.isDone(); i.next()) { switch (i.currentSegment(coords)) { - case PathIterator.SEG_MOVETO: - moveToX = coords[0]; - moveToY = coords[1]; - break; - case PathIterator.SEG_CLOSE: - coords[0] = moveToX; - coords[1] = moveToY; - break; + case PathIterator.SEG_MOVETO: + moveToX = coords[0]; + moveToY = coords[1]; + break; + case PathIterator.SEG_CLOSE: + coords[0] = moveToX; + coords[1] = moveToY; + break; } IntersectionResultEx chop = IntersectLineLine.intersectLineLineEx( prevX, prevY, diff --git a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/Points.java b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/Points.java index 9eadeeecf..1718b54aa 100644 --- a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/Points.java +++ b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/Points.java @@ -6,7 +6,7 @@ package org.jhotdraw8.geom; -import java.awt.*; +import java.awt.Shape; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; @@ -90,22 +90,6 @@ public static boolean almostZero(Point2D.Double v, double epsilon) { return Points2D.magnitudeSq(v) < epsilon * epsilon; } - public static boolean almostEqual(double a, double b) { - return almostEqual(a, b, Rectangles.REAL_THRESHOLD); - } - - public static boolean almostEqual(double a, double b, double epsilon) { - return Math.abs(a - b) < epsilon; - } - - public static boolean almostZero(double a) { - return almostZero(a, Rectangles.REAL_THRESHOLD); - } - - public static boolean almostZero(double a, double epsilon) { - return Math.abs(a) < epsilon; - } - /** * Computes the distance from the given shape to the given point. * diff --git a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/Scalars.java b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/Scalars.java new file mode 100644 index 000000000..62a864634 --- /dev/null +++ b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/Scalars.java @@ -0,0 +1,25 @@ +package org.jhotdraw8.geom; + +public class Scalars { + /** + * Don't let anyone instantiate this class. + */ + private Scalars() { + } + + public static boolean almostEqual(double a, double b) { + return almostEqual(a, b, Rectangles.REAL_THRESHOLD); + } + + public static boolean almostEqual(double a, double b, double epsilon) { + return Math.abs(a - b) < epsilon; + } + + public static boolean almostZero(double a) { + return almostZero(a, Rectangles.REAL_THRESHOLD); + } + + public static boolean almostZero(double a, double epsilon) { + return Math.abs(a) < epsilon; + } +} diff --git a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/contour/ContourIntersections.java b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/contour/ContourIntersections.java index 92e2326fa..0aa5b396c 100755 --- a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/contour/ContourIntersections.java +++ b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/contour/ContourIntersections.java @@ -15,6 +15,7 @@ import org.jhotdraw8.geom.AABB; import org.jhotdraw8.geom.Points; import org.jhotdraw8.geom.Rectangles; +import org.jhotdraw8.geom.Scalars; import org.jhotdraw8.geom.intersect.IntersectCircleCircle; import org.jhotdraw8.geom.intersect.IntersectCircleLine; import org.jhotdraw8.geom.intersect.IntersectLineLine; @@ -242,7 +243,7 @@ public static void localSelfIntersects(final PlinePath pline, final List quadraticSolutions(double a, double b, double // that are very near each other in value. // See: // https://math.stackexchange.com/questions/311382/solving-a-quadratic-equation-with-precision-when-using-floating-point-variables - assert Points.almostEqual(b * b - 4.0 * a * c, discr, Rectangles.REAL_THRESHOLD) : "discriminate is not correct"; + assert Scalars.almostEqual(b * b - 4.0 * a * c, discr, Rectangles.REAL_THRESHOLD) : "discriminate is not correct"; double sqrtDiscr = Math.sqrt(discr); double denom = 2.0 * a; double sol1; diff --git a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectCircleCircle.java b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectCircleCircle.java index 9717331c4..1c87fe441 100644 --- a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectCircleCircle.java +++ b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectCircleCircle.java @@ -6,6 +6,7 @@ import org.jhotdraw8.geom.Angles; import org.jhotdraw8.geom.Points; +import org.jhotdraw8.geom.Scalars; import java.awt.geom.Point2D; import java.util.ArrayList; @@ -65,7 +66,7 @@ public static IntersectionResultEx intersectCircleCircleEx(double c1x, double c1 status = IntersectionStatus.NO_INTERSECTION_OUTSIDE; } else if (c_dist < r_min) { status = r1 < r2 ? IntersectionStatus.NO_INTERSECTION_INSIDE : IntersectionStatus.NO_INTERSECTION_OUTSIDE; - } else if (Points.almostZero(c_dist, epsilon) && Points.almostEqual(r1, r2, epsilon)) { + } else if (Scalars.almostZero(c_dist, epsilon) && Scalars.almostEqual(r1, r2, epsilon)) { status = IntersectionStatus.NO_INTERSECTION_COINCIDENT; } else { status = IntersectionStatus.INTERSECTION; @@ -86,7 +87,7 @@ public static IntersectionResultEx intersectCircleCircleEx(double c1x, double c1 double p2x = p.getX() + b * dy; double p2y = p.getY() - b * dx; - if (!Points.almostEqual(c_dist, r_max, epsilon)) { + if (!Scalars.almostEqual(c_dist, r_max, epsilon)) { result.add(new IntersectionPointEx(new Point2D.Double(p2x, p2y), Angles.atan2(p2y - c1y, p2x - c1x), Angles.perp(p2x - c1x, p2y - c1y), Angles.atan2(p2y - c2y, p2x - c2x), Angles.perp(p2x - c2x, p2y - c2y) @@ -142,7 +143,7 @@ public static IntersectionResult intersectCircleCircle(double c1x, double c1y, d status = IntersectionStatus.NO_INTERSECTION_OUTSIDE; } else if (c_dist < r_min) { status = r1 < r2 ? IntersectionStatus.NO_INTERSECTION_INSIDE : IntersectionStatus.NO_INTERSECTION_OUTSIDE; - } else if (Points.almostZero(c_dist, epsilon) && Points.almostEqual(r1, r2, epsilon)) { + } else if (Scalars.almostZero(c_dist, epsilon) && Scalars.almostEqual(r1, r2, epsilon)) { status = IntersectionStatus.NO_INTERSECTION_COINCIDENT; } else { status = IntersectionStatus.INTERSECTION; @@ -162,7 +163,7 @@ public static IntersectionResult intersectCircleCircle(double c1x, double c1y, d double p2x = p.getX() + b * dy; double p2y = p.getY() - b * dx; - if (!Points.almostEqual(c_dist, r_max, epsilon)) { + if (!Scalars.almostEqual(c_dist, r_max, epsilon)) { result.add(new IntersectionPoint(p2x, p2y, Angles.atan2(p2y - c1y, p2x - c1x) diff --git a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectCirclePoint.java b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectCirclePoint.java index af4882b84..5d19da3e5 100644 --- a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectCirclePoint.java +++ b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectCirclePoint.java @@ -5,8 +5,8 @@ package org.jhotdraw8.geom.intersect; import org.jhotdraw8.geom.Angles; -import org.jhotdraw8.geom.Points; import org.jhotdraw8.geom.Rectangles; +import org.jhotdraw8.geom.Scalars; import java.awt.geom.Point2D; import java.util.ArrayList; @@ -36,7 +36,7 @@ public static IntersectionResult intersectCirclePoint(Point2D cc, double cr, Poi double c_dist = cc.distance(pc); IntersectionStatus status; - if (Points.almostZero(c_dist, epsilon)) { + if (Scalars.almostZero(c_dist, epsilon)) { status = IntersectionStatus.NO_INTERSECTION_INSIDE; } else { diff --git a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectCubicCurveCubicCurve.java b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectCubicCurveCubicCurve.java index 9e19ed269..78e974ded 100644 --- a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectCubicCurveCubicCurve.java +++ b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectCubicCurveCubicCurve.java @@ -11,6 +11,7 @@ import org.jhotdraw8.geom.Points2D; import org.jhotdraw8.geom.Polynomial; import org.jhotdraw8.geom.Rectangles; +import org.jhotdraw8.geom.Scalars; import java.awt.geom.Point2D; import java.util.ArrayList; @@ -351,7 +352,7 @@ public static IntersectionResult intersectCubicCurveCubicCurve(Point2D a0, Point for (double xRoot : xRoots) { if (tMin < xRoot && xRoot <= tMax) { for (double yRoot : yRoots) { - if (Points.almostEqual(xRoot, yRoot, ROOT_X_Y_TOLERANCE)) { + if (Scalars.almostEqual(xRoot, yRoot, ROOT_X_Y_TOLERANCE)) { result.add(new IntersectionPoint( Points2D.sum( Points2D.multiply(c23, s * s * s), diff --git a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectCubicCurveLine.java b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectCubicCurveLine.java index 55f329827..70388fcab 100644 --- a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectCubicCurveLine.java +++ b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectCubicCurveLine.java @@ -6,10 +6,10 @@ import org.jhotdraw8.geom.CubicCurves; import org.jhotdraw8.geom.PointAndDerivative; -import org.jhotdraw8.geom.Points; import org.jhotdraw8.geom.Points2D; import org.jhotdraw8.geom.Polynomial; import org.jhotdraw8.geom.Rectangles; +import org.jhotdraw8.geom.Scalars; import java.awt.geom.Point2D; import java.util.ArrayList; @@ -120,12 +120,12 @@ public static IntersectionResult intersectCubicCurveLine(Point2D p0, Point2D p1, // See if point is on line segment // Had to make special cases for vertical and horizontal lines due // to slight errors in calculation of p10 - if (Points.almostEqual(a0x, a1x, epsilon)) { + if (Scalars.almostEqual(a0x, a1x, epsilon)) { if (topLeft.getY() <= p10.getY() && p10.getY() <= bottomRight.getY()) { status = IntersectionStatus.INTERSECTION; result.add(new IntersectionPoint(p10, t)); } - } else if (Points.almostEqual(a0y, a1y, epsilon)) { + } else if (Scalars.almostEqual(a0y, a1y, epsilon)) { if (topLeft.getX() <= p10.getX() && p10.getX() <= bottomRight.getX()) { status = IntersectionStatus.INTERSECTION; result.add(new IntersectionPoint(p10, t)); diff --git a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectCubicCurvePathIterator.java b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectCubicCurvePathIterator.java index 399eea961..bfa5d4e23 100644 --- a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectCubicCurvePathIterator.java +++ b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectCubicCurvePathIterator.java @@ -4,8 +4,8 @@ */ package org.jhotdraw8.geom.intersect; -import org.jhotdraw8.geom.Points; import org.jhotdraw8.geom.Rectangles; +import org.jhotdraw8.geom.Scalars; import java.awt.geom.PathIterator; import java.util.ArrayList; @@ -113,7 +113,7 @@ public static IntersectionResultEx intersectCubicCurvePathIteratorEx(double a0x, if (rayCheck != null && rayCheck.getStatus() == IntersectionStatus.INTERSECTION) { for (IntersectionPointEx ip : rayCheck.intersections()) { double ty = ip.getDerivativeB().getY(); - if (Points.almostZero(ty)) { + if (Scalars.almostZero(ty)) { // intersection point is tangential to ray - no crossing } else if (ty > 0) { clockwiseCrossings++; diff --git a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectLinePathIterator.java b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectLinePathIterator.java index 42b05e94d..677a7eade 100644 --- a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectLinePathIterator.java +++ b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectLinePathIterator.java @@ -4,8 +4,8 @@ */ package org.jhotdraw8.geom.intersect; -import org.jhotdraw8.geom.Points; import org.jhotdraw8.geom.Rectangles; +import org.jhotdraw8.geom.Scalars; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; @@ -118,7 +118,7 @@ public static IntersectionResultEx intersectLinePathIteratorEx(double a0x, doubl if (rayCheck != null && rayCheck.getStatus() == IntersectionStatus.INTERSECTION) { for (IntersectionPointEx ip : rayCheck.intersections()) { double ty = ip.getDerivativeB().getY(); - if (Points.almostZero(ty)) { + if (Scalars.almostZero(ty)) { // intersection point is tangential to ray - no crossing } else if (ty > 0) { clockwiseCrossings++; diff --git a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectLinePoint.java b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectLinePoint.java index 2e21da9ba..f098f9037 100644 --- a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectLinePoint.java +++ b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectLinePoint.java @@ -4,9 +4,9 @@ */ package org.jhotdraw8.geom.intersect; -import org.jhotdraw8.geom.Points; import org.jhotdraw8.geom.Polynomial; import org.jhotdraw8.geom.Rectangles; +import org.jhotdraw8.geom.Scalars; import java.awt.geom.Point2D; import java.util.ArrayList; @@ -137,10 +137,10 @@ public static boolean lineContainsPoint(double x1, double y1, double a, b, x, y; - if (Points.almostEqual(x1, x2, tolerance)) { + if (Scalars.almostEqual(x1, x2, tolerance)) { return (abs(px - x1) <= tolerance); } - if (Points.almostEqual(y1, y2, tolerance)) { + if (Scalars.almostEqual(y1, y2, tolerance)) { return (abs(py - y1) <= tolerance); } diff --git a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectPathIteratorPoint.java b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectPathIteratorPoint.java index 7c4bde565..2cc3983e1 100644 --- a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectPathIteratorPoint.java +++ b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectPathIteratorPoint.java @@ -4,8 +4,8 @@ */ package org.jhotdraw8.geom.intersect; -import org.jhotdraw8.geom.Points; import org.jhotdraw8.geom.Rectangles; +import org.jhotdraw8.geom.Scalars; import java.awt.geom.PathIterator; import java.util.ArrayList; @@ -113,7 +113,7 @@ public static IntersectionResult intersectPathIteratorPoint(PathIterator pit, do if (rayCheck != null && rayCheck.getStatus() == IntersectionStatus.INTERSECTION) { for (IntersectionPointEx ip : rayCheck.intersections()) { double ty = ip.getDerivativeB().getY(); - if (Points.almostZero(ty)) { + if (Scalars.almostZero(ty)) { // intersection point is tangential to ray - no crossing } else if (ty > 0) { clockwiseCrossings++; diff --git a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectPointRay.java b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectPointRay.java index 1dfc7166e..22df2a439 100644 --- a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectPointRay.java +++ b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectPointRay.java @@ -4,7 +4,7 @@ */ package org.jhotdraw8.geom.intersect; -import org.jhotdraw8.geom.Points; +import org.jhotdraw8.geom.Scalars; import org.jspecify.annotations.Nullable; public class IntersectPointRay { @@ -44,17 +44,17 @@ public static double projectedPointOnRay(double ox, double oy, double dx, double // o + t * d = p // t = (p - o) / d - boolean aIsPoint = Points.almostZero(dx * amax) && Points.almostZero(dy * amax); + boolean aIsPoint = Scalars.almostZero(dx * amax) && Scalars.almostZero(dy * amax); if (aIsPoint) { - return Points.almostEqual(ox, px) && Points.almostEqual(oy, py) ? 0.0 : null; + return Scalars.almostEqual(ox, px) && Scalars.almostEqual(oy, py) ? 0.0 : null; } double t; - if (Points.almostZero(dx)) { + if (Scalars.almostZero(dx)) { t = (py - oy) / dy; } else { t = (px - ox) / dx; } - return -tolerance < t && t <= amax && Points.almostEqual(ox + t * dx, px) && Points.almostEqual(oy + t * dy, py) + return -tolerance < t && t <= amax && Scalars.almostEqual(ox + t * dx, px) && Scalars.almostEqual(oy + t * dy, py) ? t : null; } } diff --git a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectQuadCurvePathIterator.java b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectQuadCurvePathIterator.java index 7eada80ac..d25d0db3d 100644 --- a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectQuadCurvePathIterator.java +++ b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectQuadCurvePathIterator.java @@ -4,8 +4,8 @@ */ package org.jhotdraw8.geom.intersect; -import org.jhotdraw8.geom.Points; import org.jhotdraw8.geom.Rectangles; +import org.jhotdraw8.geom.Scalars; import java.awt.geom.PathIterator; import java.util.ArrayList; @@ -111,7 +111,7 @@ public static IntersectionResultEx intersectQuadCurvePathIteratorEx(double a0x, if (rayCheck != null && rayCheck.getStatus() == IntersectionStatus.INTERSECTION) { for (IntersectionPointEx ip : rayCheck.intersections()) { double ty = ip.getDerivativeB().getY(); - if (Points.almostZero(ty)) { + if (Scalars.almostZero(ty)) { // intersection point is tangential to ray - no crossing } else if (ty > 0) { clockwiseCrossings++; diff --git a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectRayRay.java b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectRayRay.java index f2a897375..16e0892c3 100644 --- a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectRayRay.java +++ b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectRayRay.java @@ -4,7 +4,7 @@ */ package org.jhotdraw8.geom.intersect; -import org.jhotdraw8.geom.Points; +import org.jhotdraw8.geom.Scalars; import java.awt.geom.Point2D; import java.util.ArrayList; @@ -123,7 +123,7 @@ public static IntersectionResultEx intersectRayRayEx( double ub_t = adx * doy - ady * dox; double u_b = bdy * adx - bdx * ady; - if (!Points.almostZero(u_b, epsilon)) { + if (!Scalars.almostZero(u_b, epsilon)) { double ua = ua_t / u_b; double ub = ub_t / u_b; @@ -143,13 +143,13 @@ public static IntersectionResultEx intersectRayRayEx( )); } } else { - if (Points.almostZero(ua_t) || Points.almostZero(ub_t)) { + if (Scalars.almostZero(ua_t) || Scalars.almostZero(ub_t)) { // either collinear or degenerate (segments are single points) - boolean aIsPoint = Points.almostZero(amax * adx, epsilon) && Points.almostZero(amax * ady, epsilon); - boolean bIsPoint = Points.almostZero(bmax * bdx, epsilon) && Points.almostZero(bmax * bdy, epsilon); + boolean aIsPoint = Scalars.almostZero(amax * adx, epsilon) && Scalars.almostZero(amax * ady, epsilon); + boolean bIsPoint = Scalars.almostZero(bmax * bdx, epsilon) && Scalars.almostZero(bmax * bdy, epsilon); if (aIsPoint && bIsPoint) { // both segments are just points - if (Points.almostEqual(aox, box) && Points.almostEqual(aoy, boy)) { + if (Scalars.almostEqual(aox, box) && Scalars.almostEqual(aoy, boy)) { // same point status = IntersectionStatus.INTERSECTION; result.add(new IntersectionPointEx( @@ -187,7 +187,7 @@ public static IntersectionResultEx intersectRayRayEx( } else { // neither segment is a point, check if they overlap double at0, at1; - if (Points.almostZero(adx)) { + if (Scalars.almostZero(adx)) { at0 = (boy - aoy) / ady; at1 = (bdy + boy - aoy) / ady; } else { @@ -205,7 +205,7 @@ public static IntersectionResultEx intersectRayRayEx( at0 = Math.clamp(at0, 0.0, amax); at1 = Math.clamp(at1, 0.0, bmax); double bt0, bt1; - if (Points.almostZero(bdx)) { + if (Scalars.almostZero(bdx)) { bt0 = (aoy + at0 * ady - boy) / bdy; bt1 = (aoy + at1 * ady - boy) / bdy; } else { diff --git a/org.jhotdraw8.geom/src/test/java/org.jhotdraw8.geom/org/jhotdraw8/geom/contour/ContourBuilderTest.java b/org.jhotdraw8.geom/src/test/java/org.jhotdraw8.geom/org/jhotdraw8/geom/contour/ContourBuilderTest.java index caf2085c1..8630cbdaa 100755 --- a/org.jhotdraw8.geom/src/test/java/org.jhotdraw8.geom/org/jhotdraw8/geom/contour/ContourBuilderTest.java +++ b/org.jhotdraw8.geom/src/test/java/org.jhotdraw8.geom/org/jhotdraw8/geom/contour/ContourBuilderTest.java @@ -7,12 +7,18 @@ import org.jhotdraw8.geom.AwtPathBuilder; import org.jhotdraw8.geom.AwtShapes; -import org.jhotdraw8.geom.Points; +import org.jhotdraw8.geom.Scalars; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; -import javax.swing.*; -import java.awt.*; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.SwingUtilities; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; import java.util.Arrays; import java.util.List; @@ -150,9 +156,9 @@ private void testOffsetPath(PlinePath input, double offset, List expe for (int j = 0; j < e.size(); j++) { PlineVertex ev = e.get(j); PlineVertex av = a.get(j); - isEqual &= Points.almostEqual(ev.bulge(), av.bulge(), 1e-5); - isEqual &= Points.almostEqual(ev.getX(), av.getX(), 1e-5); - isEqual &= Points.almostEqual(ev.getY(), av.getY(), 1e-5); + isEqual &= Scalars.almostEqual(ev.bulge(), av.bulge(), 1e-5); + isEqual &= Scalars.almostEqual(ev.getX(), av.getX(), 1e-5); + isEqual &= Scalars.almostEqual(ev.getY(), av.getY(), 1e-5); if (!isEqual) { System.err.println("expected: " + ev); System.err.println("actual: " + av);