diff --git a/org.jhotdraw8.fxcollection/src/main/java/org.jhotdraw8.fxcollection/org/jhotdraw8/fxcollection/typesafekey/CompositeMapAccessor.java b/org.jhotdraw8.fxcollection/src/main/java/org.jhotdraw8.fxcollection/org/jhotdraw8/fxcollection/typesafekey/CompositeMapAccessor.java index da744e494..e52c27d2d 100755 --- a/org.jhotdraw8.fxcollection/src/main/java/org.jhotdraw8.fxcollection/org/jhotdraw8/fxcollection/typesafekey/CompositeMapAccessor.java +++ b/org.jhotdraw8.fxcollection/src/main/java/org.jhotdraw8.fxcollection/org/jhotdraw8/fxcollection/typesafekey/CompositeMapAccessor.java @@ -7,6 +7,7 @@ import org.jhotdraw8.annotation.NonNull; import org.jhotdraw8.icollection.immutable.ImmutableSequencedSet; +import java.io.Serial; import java.util.Map; /** @@ -17,6 +18,10 @@ */ public interface CompositeMapAccessor extends MapAccessor { + /** + * Serial version UID: + */ + @Serial long serialVersionUID = 1L; @Override @@ -29,7 +34,10 @@ default boolean containsKey(Map, Object> map) { return true; } - + /** + * Gets all {@link MapAccessor}s that this accessor is composing. + * @return the sub-accessors + */ @NonNull ImmutableSequencedSet> getSubAccessors(); } diff --git a/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectLineCubicCurve.java b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectLineCubicCurve.java new file mode 100644 index 000000000..8034886a3 --- /dev/null +++ b/org.jhotdraw8.geom/src/main/java/org.jhotdraw8.geom/org/jhotdraw8/geom/intersect/IntersectLineCubicCurve.java @@ -0,0 +1,176 @@ +/* + * @(#)IntersectCubicCurveLine.java + * Copyright © 2023 The authors and contributors of JHotDraw. MIT License. + */ +package org.jhotdraw8.geom.intersect; + +import org.jhotdraw8.annotation.NonNull; +import org.jhotdraw8.geom.CubicCurves; +import org.jhotdraw8.geom.PointAndDerivative; +import org.jhotdraw8.geom.Points2D; +import org.jhotdraw8.geom.Polynomial; +import org.jhotdraw8.geom.Rectangles; + +import java.awt.geom.Point2D; +import java.util.ArrayList; +import java.util.List; + +import static org.jhotdraw8.geom.Lines.lerp; +import static org.jhotdraw8.geom.intersect.IntersectCubicCurveLine.intersectCubicCurveLine; +import static org.jhotdraw8.geom.intersect.IntersectLinePoint.argumentOnLine; + +public class IntersectLineCubicCurve { + private IntersectLineCubicCurve() { + } + + + /** + * Computes the intersection between cubic bezier curve 'p' and the line + * 'a'. + * + * @param p0x control point P0 of 'p' + * @param p1x control point P1 of 'p' + * @param p2x control point P2 of 'p' + * @param p3x control point P3 of 'p' + * @param a0x point 1 of 'a' + * @param a1x point 2 of 'a' + * @param p0y control point P0 of 'p' + * @param p1y control point P1 of 'p' + * @param p2y control point P2 of 'p' + * @param p3y control point P3 of 'p' + * @param a0y point 1 of 'a' + * @param a1y point 2 of 'a' + * @return the computed intersection + */ + public static @NonNull IntersectionResult intersectLineCubicCurve( + double a0x, double a0y, double a1x, double a1y, + double p0x, double p0y, double p1x, double p1y, double p2x, double p2y, double p3x, double p3y, + double epsilon) { + + Point2D.Double a0 = new Point2D.Double(a0x, a0y); + Point2D.Double a1 = new Point2D.Double(a1x, a1y); + Point2D.Double p0 = new Point2D.Double(p0x, p0y); + Point2D.Double p1 = new Point2D.Double(p1x, p1y); + Point2D.Double p2 = new Point2D.Double(p2x, p2y); + Point2D.Double p3 = new Point2D.Double(p3x, p3y); + return intersectLineCubicCurve(a0, a1, p0, p1, p2, p3, Rectangles.REAL_THRESHOLD); + } + + /** + * Computes the intersection between cubic bezier curve 'p' and the line + * 'a'. + * + * @param a0 point 1 of 'a' + * @param a1 point 2 of 'a' + * @param p0 control point P0 of 'p' + * @param p1 control point P1 of 'p' + * @param p2 control point P2 of 'p' + * @param p3 control point P3 of 'p' + * @param epsilon + * @return the computed intersection + */ + public static @NonNull IntersectionResult intersectLineCubicCurve(@NonNull Point2D a0, @NonNull Point2D a1, @NonNull Point2D p0, @NonNull Point2D p1, @NonNull Point2D p2, @NonNull Point2D p3, double epsilon) { + final double a0x, a0y, a1x, a1y; + a0x = a0.getX(); + a0y = a0.getY(); + a1x = a1.getX(); + a1y = a1.getY(); + + final Point2D.Double amin = Intersections.topLeft(a0, a1); // used to determine if point is on line segment + final Point2D.Double amax = Intersections.bottomRight(a0, a1); // used to determine if point is on line segment + List result = new ArrayList<>(); + + // Start with Bezier using Bernstein polynomials for weighting functions: + // (1-t^3)P0 + 3t(1-t)^2P1 + 3t^2(1-t)P2 + t^3P3 + // + // Expand and collect terms to form linear combinations of original Bezier + // controls. This ends up with a vector cubic in t: + // (-P0+3P1-3P2+P3)t^3 + (3P0-6P1+3P2)t^2 + (-3P0+3P1)t + P0 + // /\ /\ /\ /\ + // || || || || + // c3 c2 c1 c0 + // Calculate the coefficients + final Point2D c3, c2, c1, c0; // coefficients of cubic + c3 = Points2D.sum(Points2D.multiply(p0, -1), Points2D.multiply(p1, 3), Points2D.multiply(p2, -3), p3); + c2 = Points2D.sum(Points2D.multiply(p0, 3), Points2D.multiply(p1, -6), Points2D.multiply(p2, 3)); + c1 = Points2D.add(Points2D.multiply(p0, -3), Points2D.multiply(p1, 3)); + c0 = p0; + + // Convert line to normal form: ax + by + c = 0 + // Find normal to line: negative inverse of original line's slope + final Point2D.Double n; // normal for normal form of line + n = new Point2D.Double(a0y - a1y, a1x - a0x); + + // Determine new c coefficient + final double cl; // c coefficient for normal form of line + cl = a0x * a1y - a1x * a0y; + + // Rotate each cubic coefficient using line for new coordinate system + // Find roots of rotated cubic + final Polynomial polynomial = new Polynomial( + Points2D.dotProduct(n, c3), + Points2D.dotProduct(n, c2), + Points2D.dotProduct(n, c1), + Points2D.dotProduct(n, c0) + cl + ); + double[] roots = polynomial.getRoots(); + + // Any roots in closed interval [0,1] are intersections on Bezier, but + // might not be on the line segment. + // Find intersections and calculate point coordinates + IntersectionStatus status = IntersectionStatus.NO_INTERSECTION; + for (double t : roots) { + if (-epsilon <= t && t <= 1 + epsilon) { + // We're within the Bezier curve + // Find point on Bezier + final Point2D.Double p5, p6, p7, p8, p9, p10; + p5 = lerp(p0, p1, t); + p6 = lerp(p1, p2, t); + p7 = lerp(p2, p3, t); + + p8 = lerp(p5, p6, t); + p9 = lerp(p6, p7, t); + + p10 = lerp(p8, p9, t); + + // 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 + double t1 = argumentOnLine(a0x, a0y, a1x, a1y, p10.getX(), p10.getY()); + if (t1 > -epsilon && t1 < 1 + epsilon) { + status = IntersectionStatus.INTERSECTION; + result.add(new IntersectionPoint(p10, t1)); + } + } + } + + return new IntersectionResult(status, result); + } + + public static IntersectionResultEx intersectLineCubicCurveEx(double a0x, double a0y, double a1x, double a1y, + double p0x, double p0y, double p1x, double p1y, double p2x, double p2y, double p3x, double p3y, + double epsilon) { + IntersectionResult result = intersectCubicCurveLine( + p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, + a0x, a0y, a1x, a1y, + epsilon); + ArrayList list = new ArrayList<>(); + for (IntersectionPoint ip : result.intersections()) { + double x = ip.getX(); + double y = ip.getY(); + PointAndDerivative pdA = CubicCurves.eval(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, ip.argumentA()); + list.add(new IntersectionPointEx( + x, y, + IntersectLinePoint.argumentOnLine(a0x, a0y, a1x, a1y, x, y), a1x - a0x, a1y - a0y, + ip.argumentA(), pdA.dx(), pdA.dy() + )); + } + + return new IntersectionResultEx(result.getStatus(), list); + } + + public static IntersectionResultEx intersectLineCubicCurveEx(double a0x, double a0y, double a1x, double a1y, double lastx, double lasty, double v, double v1, double v2, double v3, double x, double y) { + return intersectLineCubicCurveEx(a0x, a0y, a1x, a1y, lastx, lasty, v, v1, v2, v3, x, y, Rectangles.REAL_THRESHOLD); + } + +}