From 520167978ebe30eb9ae35614224013463956bb34 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Fri, 10 Jun 2011 15:56:13 -0700 Subject: [PATCH] Another copy of the classes, specialized to work on double. Double the maintenance! (But not worth the PITA of programmatically generating the code, IMO.) --- src/main/java/pythagoras/d/AbstractArc.java | 357 +++++ .../java/pythagoras/d/AbstractCubicCurve.java | 174 +++ .../java/pythagoras/d/AbstractDimension.java | 41 + .../java/pythagoras/d/AbstractEllipse.java | 128 ++ src/main/java/pythagoras/d/AbstractLine.java | 211 +++ src/main/java/pythagoras/d/AbstractPoint.java | 61 + .../java/pythagoras/d/AbstractQuadCurve.java | 163 +++ .../java/pythagoras/d/AbstractRectangle.java | 238 ++++ .../pythagoras/d/AbstractRoundRectangle.java | 158 +++ .../java/pythagoras/d/AffineTransform.java | 489 +++++++ src/main/java/pythagoras/d/Arc.java | 241 ++++ src/main/java/pythagoras/d/Area.java | 1244 +++++++++++++++++ src/main/java/pythagoras/d/Crossing.java | 861 ++++++++++++ .../java/pythagoras/d/CrossingHelper.java | 291 ++++ src/main/java/pythagoras/d/CubicCurve.java | 143 ++ src/main/java/pythagoras/d/CubicCurves.java | 101 ++ .../pythagoras/d/CurveCrossingHelper.java | 273 ++++ src/main/java/pythagoras/d/Dimension.java | 65 + src/main/java/pythagoras/d/Dimensions.java | 18 + src/main/java/pythagoras/d/Ellipse.java | 66 + .../pythagoras/d/FlatteningPathIterator.java | 233 +++ src/main/java/pythagoras/d/GeometryUtil.java | 483 +++++++ src/main/java/pythagoras/d/IArc.java | 56 + src/main/java/pythagoras/d/ICubicCurve.java | 61 + src/main/java/pythagoras/d/IDimension.java | 26 + src/main/java/pythagoras/d/IEllipse.java | 14 + src/main/java/pythagoras/d/ILine.java | 70 + src/main/java/pythagoras/d/IPoint.java | 32 + src/main/java/pythagoras/d/IQuadCurve.java | 52 + src/main/java/pythagoras/d/IRectangle.java | 68 + .../java/pythagoras/d/IRectangularShape.java | 50 + .../java/pythagoras/d/IRoundRectangle.java | 20 + src/main/java/pythagoras/d/IShape.java | 56 + .../d/IllegalPathStateException.java | 20 + .../java/pythagoras/d/IntersectPoint.java | 107 ++ src/main/java/pythagoras/d/Line.java | 82 ++ src/main/java/pythagoras/d/Lines.java | 155 ++ .../d/NoninvertibleTransformException.java | 16 + src/main/java/pythagoras/d/Path.java | 374 +++++ src/main/java/pythagoras/d/PathIterator.java | 61 + src/main/java/pythagoras/d/Point.java | 79 ++ src/main/java/pythagoras/d/Points.java | 40 + src/main/java/pythagoras/d/QuadCurve.java | 122 ++ src/main/java/pythagoras/d/QuadCurves.java | 94 ++ src/main/java/pythagoras/d/Rectangle.java | 188 +++ src/main/java/pythagoras/d/Rectangles.java | 33 + .../java/pythagoras/d/RectangularShape.java | 158 +++ .../java/pythagoras/d/RoundRectangle.java | 102 ++ 48 files changed, 8175 insertions(+) create mode 100644 src/main/java/pythagoras/d/AbstractArc.java create mode 100644 src/main/java/pythagoras/d/AbstractCubicCurve.java create mode 100644 src/main/java/pythagoras/d/AbstractDimension.java create mode 100644 src/main/java/pythagoras/d/AbstractEllipse.java create mode 100644 src/main/java/pythagoras/d/AbstractLine.java create mode 100644 src/main/java/pythagoras/d/AbstractPoint.java create mode 100644 src/main/java/pythagoras/d/AbstractQuadCurve.java create mode 100644 src/main/java/pythagoras/d/AbstractRectangle.java create mode 100644 src/main/java/pythagoras/d/AbstractRoundRectangle.java create mode 100644 src/main/java/pythagoras/d/AffineTransform.java create mode 100644 src/main/java/pythagoras/d/Arc.java create mode 100644 src/main/java/pythagoras/d/Area.java create mode 100644 src/main/java/pythagoras/d/Crossing.java create mode 100644 src/main/java/pythagoras/d/CrossingHelper.java create mode 100644 src/main/java/pythagoras/d/CubicCurve.java create mode 100644 src/main/java/pythagoras/d/CubicCurves.java create mode 100644 src/main/java/pythagoras/d/CurveCrossingHelper.java create mode 100644 src/main/java/pythagoras/d/Dimension.java create mode 100644 src/main/java/pythagoras/d/Dimensions.java create mode 100644 src/main/java/pythagoras/d/Ellipse.java create mode 100644 src/main/java/pythagoras/d/FlatteningPathIterator.java create mode 100644 src/main/java/pythagoras/d/GeometryUtil.java create mode 100644 src/main/java/pythagoras/d/IArc.java create mode 100644 src/main/java/pythagoras/d/ICubicCurve.java create mode 100644 src/main/java/pythagoras/d/IDimension.java create mode 100644 src/main/java/pythagoras/d/IEllipse.java create mode 100644 src/main/java/pythagoras/d/ILine.java create mode 100644 src/main/java/pythagoras/d/IPoint.java create mode 100644 src/main/java/pythagoras/d/IQuadCurve.java create mode 100644 src/main/java/pythagoras/d/IRectangle.java create mode 100644 src/main/java/pythagoras/d/IRectangularShape.java create mode 100644 src/main/java/pythagoras/d/IRoundRectangle.java create mode 100644 src/main/java/pythagoras/d/IShape.java create mode 100644 src/main/java/pythagoras/d/IllegalPathStateException.java create mode 100644 src/main/java/pythagoras/d/IntersectPoint.java create mode 100644 src/main/java/pythagoras/d/Line.java create mode 100644 src/main/java/pythagoras/d/Lines.java create mode 100644 src/main/java/pythagoras/d/NoninvertibleTransformException.java create mode 100644 src/main/java/pythagoras/d/Path.java create mode 100644 src/main/java/pythagoras/d/PathIterator.java create mode 100644 src/main/java/pythagoras/d/Point.java create mode 100644 src/main/java/pythagoras/d/Points.java create mode 100644 src/main/java/pythagoras/d/QuadCurve.java create mode 100644 src/main/java/pythagoras/d/QuadCurves.java create mode 100644 src/main/java/pythagoras/d/Rectangle.java create mode 100644 src/main/java/pythagoras/d/Rectangles.java create mode 100644 src/main/java/pythagoras/d/RectangularShape.java create mode 100644 src/main/java/pythagoras/d/RoundRectangle.java diff --git a/src/main/java/pythagoras/d/AbstractArc.java b/src/main/java/pythagoras/d/AbstractArc.java new file mode 100644 index 0000000..bf94943 --- /dev/null +++ b/src/main/java/pythagoras/d/AbstractArc.java @@ -0,0 +1,357 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +import java.util.NoSuchElementException; + +/** + * Provides most of the implementation of {@link IArc}, obtaining only the frame and other metrics + * from the derived class. + */ +public abstract class AbstractArc extends RectangularShape implements IArc +{ + @Override // from interface IArc + public Point getStartPoint () { + return getStartPoint(new Point()); + } + + @Override // from interface IArc + public Point getStartPoint (Point target) { + double a = Math.toRadians(getAngleStart()); + target.setLocation(getX() + (1f + Math.cos(a)) * getWidth() / 2f, + getY() + (1f - Math.sin(a)) * getHeight() / 2f); + return target; + } + + @Override // from interface IArc + public Point getEndPoint () { + return getEndPoint(new Point()); + } + + @Override // from interface IArc + public Point getEndPoint (Point target) { + double a = Math.toRadians(getAngleStart() + getAngleExtent()); + target.setLocation(getX() + (1f + Math.cos(a)) * getWidth() / 2f, + getY() + (1f - Math.sin(a)) * getHeight() / 2f); + return target; + } + + @Override // from interface IArc + public boolean containsAngle (double angle) { + double extent = getAngleExtent(); + if (extent >= 360f) { + return true; + } + angle = getNormAngle(angle); + double a1 = getNormAngle(getAngleStart()); + double a2 = a1 + extent; + if (a2 > 360f) { + return angle >= a1 || angle <= a2 - 360f; + } + if (a2 < 0f) { + return angle >= a2 + 360f || angle <= a1; + } + return (extent > 0f) ? a1 <= angle && angle <= a2 : a2 <= angle && angle <= a1; + } + + @Override // from interface IArc + public Arc clone () { + return new Arc(getX(), getY(), getWidth(), getHeight(), getAngleStart(), getAngleExtent(), + getArcType()); + } + + @Override // from RectangularShape + public boolean isEmpty () { + return getArcType() == OPEN || super.isEmpty(); + } + + @Override // from RectangularShape + public boolean contains (double px, double py) { + // normalize point + double nx = (px - getX()) / getWidth() - 0.5f; + double ny = (py - getY()) / getHeight() - 0.5f; + if ((nx * nx + ny * ny) > 0.25) { + return false; + } + + double extent = getAngleExtent(); + double absExtent = Math.abs(extent); + if (absExtent >= 360f) { + return true; + } + + boolean containsAngle = containsAngle(Math.toDegrees(-Math.atan2(ny, nx))); + if (getArcType() == PIE) { + return containsAngle; + } + if (absExtent <= 180f && !containsAngle) { + return false; + } + + Line l = new Line(getStartPoint(), getEndPoint()); + int ccw1 = l.relativeCCW(px, py); + int ccw2 = l.relativeCCW(getCenterX(), getCenterY()); + return ccw1 == 0 || ccw2 == 0 || ((ccw1 + ccw2) == 0 ^ absExtent > 180f); + } + + @Override // from RectangularShape + public boolean contains (double rx, double ry, double rw, double rh) { + if (!(contains(rx, ry) && contains(rx + rw, ry) && + contains(rx + rw, ry + rh) && contains(rx, ry + rh))) { + return false; + } + + double absExtent = Math.abs(getAngleExtent()); + if (getArcType() != PIE || absExtent <= 180f || absExtent >= 360f) { + return true; + } + + Rectangle r = new Rectangle(rx, ry, rw, rh); + double cx = getCenterX(), cy = getCenterY(); + if (r.contains(cx, cy)) { + return false; + } + + Point p1 = getStartPoint(), p2 = getEndPoint(); + return !r.intersectsLine(cx, cy, p1.getX(), p1.getY()) && + !r.intersectsLine(cx, cy, p2.getX(), p2.getY()); + } + + @Override // from RectangularShape + public boolean intersects (double rx, double ry, double rw, double rh) { + if (isEmpty() || rw <= 0f || rh <= 0f) { + return false; + } + + // check: does arc contain rectangle's points + if (contains(rx, ry) || contains(rx + rw, ry) || + contains(rx, ry + rh) || contains(rx + rw, ry + rh)) { + return true; + } + + double cx = getCenterX(), cy = getCenterY(); + Point p1 = getStartPoint(), p2 = getEndPoint(); + + // check: does rectangle contain arc's points + Rectangle r = new Rectangle(rx, ry, rw, rh); + if (r.contains(p1) || r.contains(p2) || (getArcType() == PIE && r.contains(cx, cy))) { + return true; + } + + if (getArcType() == PIE) { + if (r.intersectsLine(p1.getX(), p1.getY(), cx, cy) || + r.intersectsLine(p2.getX(), p2.getY(), cx, cy)) { + return true; + } + } else { + if (r.intersectsLine(p1.getX(), p1.getY(), p2.getX(), p2.getY())) { + return true; + } + } + + // nearest rectangle point + double nx = cx < rx ? rx : (cx > rx + rw ? rx + rw : cx); + double ny = cy < ry ? ry : (cy > ry + rh ? ry + rh : cy); + return contains(nx, ny); + } + + @Override // from RectangularShape + public Rectangle getBounds (Rectangle target) { + if (isEmpty()) { + target.setBounds(getX(), getY(), getWidth(), getHeight()); + return target; + } + + double rx1 = getX(); + double ry1 = getY(); + double rx2 = rx1 + getWidth(); + double ry2 = ry1 + getHeight(); + + Point p1 = getStartPoint(), p2 = getEndPoint(); + + double bx1 = containsAngle(180f) ? rx1 : Math.min(p1.getX(), p2.getX()); + double by1 = containsAngle(90f) ? ry1 : Math.min(p1.getY(), p2.getY()); + double bx2 = containsAngle(0f) ? rx2 : Math.max(p1.getX(), p2.getX()); + double by2 = containsAngle(270f) ? ry2 : Math.max(p1.getY(), p2.getY()); + + if (getArcType() == PIE) { + double cx = getCenterX(); + double cy = getCenterY(); + bx1 = Math.min(bx1, cx); + by1 = Math.min(by1, cy); + bx2 = Math.max(bx2, cx); + by2 = Math.max(by2, cy); + } + target.setBounds(bx1, by1, bx2 - bx1, by2 - by1); + return target; + } + + @Override // from interface IShape + public PathIterator getPathIterator (AffineTransform at) { + return new Iterator(this, at); + } + + /** Returns a normalized angle (bound between 0 and 360 degrees). */ + protected double getNormAngle (double angle) { + return angle - Math.floor(angle / 360f) * 360f; + } + + /** An iterator over an {@link IArc}. */ + protected static class Iterator implements PathIterator + { + /** The x coordinate of left-upper corner of the arc rectangle bounds */ + private double x; + + /** The y coordinate of left-upper corner of the arc rectangle bounds */ + private double y; + + /** The width of the arc rectangle bounds */ + private double width; + + /** The height of the arc rectangle bounds */ + private double height; + + /** The start angle of the arc in degrees */ + private double angle; + + /** The angle extent in degrees */ + private double extent; + + /** The closure type of the arc */ + private int type; + + /** The path iterator transformation */ + private AffineTransform t; + + /** The current segmenet index */ + private int index; + + /** The number of arc segments the source arc subdivided to be approximated by Bezier + * curves. Depends on extent value. */ + private int arcCount; + + /** The number of line segments. Depends on closure type. */ + private int lineCount; + + /** The step to calculate next arc subdivision point */ + private double step; + + /** The tempopary value of cosinus of the current angle */ + private double cos; + + /** The tempopary value of sinus of the current angle */ + private double sin; + + /** The coefficient to calculate control points of Bezier curves */ + private double k; + + /** The tempopary value of x coordinate of the Bezier curve control vector */ + private double kx; + + /** The tempopary value of y coordinate of the Bezier curve control vector */ + private double ky; + + /** The x coordinate of the first path point (MOVE_TO) */ + private double mx; + + /** The y coordinate of the first path point (MOVE_TO) */ + private double my; + + Iterator (IArc a, AffineTransform t) { + this.width = a.getWidth() / 2f; + this.height = a.getHeight() / 2f; + this.x = a.getX() + width; + this.y = a.getY() + height; + this.angle = -Math.toRadians(a.getAngleStart()); + this.extent = -a.getAngleExtent(); + this.type = a.getArcType(); + this.t = t; + + if (width < 0 || height < 0) { + arcCount = 0; + lineCount = 0; + index = 1; + return; + } + + if (Math.abs(extent) >= 360f) { + arcCount = 4; + k = 4f / 3f * (Math.sqrt(2f) - 1f); + step = Math.PI / 2f; + if (extent < 0f) { + step = -step; + k = -k; + } + } else { + arcCount = (int)Math.rint(Math.abs(extent) / 90f); + step = Math.toRadians(extent / arcCount); + k = 4f / 3f * (1f - Math.cos(step / 2f)) / Math.sin(step / 2f); + } + + lineCount = 0; + if (type == CHORD) { + lineCount++; + } else if (type == PIE) { + lineCount += 2; + } + } + + @Override public int getWindingRule () { + return WIND_NON_ZERO; + } + + @Override public boolean isDone () { + return index > arcCount + lineCount; + } + + @Override public void next () { + index++; + } + + @Override public int currentSegment (double[] coords) { + if (isDone()) { + throw new NoSuchElementException("Iterator out of bounds"); + } + int type; + int count; + if (index == 0) { + type = SEG_MOVETO; + count = 1; + cos = Math.cos(angle); + sin = Math.sin(angle); + kx = k * width * sin; + ky = k * height * cos; + coords[0] = mx = x + cos * width; + coords[1] = my = y + sin * height; + } else if (index <= arcCount) { + type = SEG_CUBICTO; + count = 3; + coords[0] = mx - kx; + coords[1] = my + ky; + angle += step; + cos = Math.cos(angle); + sin = Math.sin(angle); + kx = k * width * sin; + ky = k * height * cos; + coords[4] = mx = x + cos * width; + coords[5] = my = y + sin * height; + coords[2] = mx + kx; + coords[3] = my - ky; + } else if (index == arcCount + lineCount) { + type = SEG_CLOSE; + count = 0; + } else { + type = SEG_LINETO; + count = 1; + coords[0] = x; + coords[1] = y; + } + if (t != null) { + t.transform(coords, 0, coords, 0, count); + } + return type; + } + } +} diff --git a/src/main/java/pythagoras/d/AbstractCubicCurve.java b/src/main/java/pythagoras/d/AbstractCubicCurve.java new file mode 100644 index 0000000..ab9ff64 --- /dev/null +++ b/src/main/java/pythagoras/d/AbstractCubicCurve.java @@ -0,0 +1,174 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +import java.util.NoSuchElementException; + +/** + * Provides most of the implementation of {@link ICubicCurve}, obtaining only the start, end and + * control points from the derived class. + */ +public abstract class AbstractCubicCurve implements ICubicCurve +{ + @Override // from interface ICubicCurve + public Point getP1 () { + return new Point(getX1(), getY1()); + } + + @Override // from interface ICubicCurve + public Point getCtrlP1 () { + return new Point(getCtrlX1(), getCtrlY1()); + } + + @Override // from interface ICubicCurve + public Point getCtrlP2 () { + return new Point(getCtrlX2(), getCtrlY2()); + } + + @Override // from interface ICubicCurve + public Point getP2 () { + return new Point(getX2(), getY2()); + } + + @Override // from interface ICubicCurve + public double getFlatnessSq () { + return CubicCurves.getFlatnessSq(getX1(), getY1(), getCtrlX1(), getCtrlY1(), + getCtrlX2(), getCtrlY2(), getX2(), getY2()); + } + + @Override // from interface ICubicCurve + public double getFlatness () { + return CubicCurves.getFlatness(getX1(), getY1(), getCtrlX1(), getCtrlY1(), + getCtrlX2(), getCtrlY2(), getX2(), getY2()); + } + + @Override // from interface ICubicCurve + public void subdivide (CubicCurve left, CubicCurve right) { + CubicCurves.subdivide(this, left, right); + } + + @Override // from interface ICubicCurve + public CubicCurve clone () { + return new CubicCurve(getX1(), getY1(), getCtrlX1(), getCtrlY1(), + getCtrlX2(), getCtrlY2(), getX2(), getY2()); + } + + @Override // from interface IShape + public boolean isEmpty () { + return true; // curves contain no space + } + + @Override // from interface IShape + public boolean contains (double px, double py) { + return Crossing.isInsideEvenOdd(Crossing.crossShape(this, px, py)); + } + + @Override // from interface IShape + public boolean contains (double rx, double ry, double rw, double rh) { + int cross = Crossing.intersectShape(this, rx, ry, rw, rh); + return (cross != Crossing.CROSSING) && Crossing.isInsideEvenOdd(cross); + } + + @Override // from interface IShape + public boolean contains (IPoint p) { + return contains(p.getX(), p.getY()); + } + + @Override // from interface IShape + public boolean contains (IRectangle r) { + return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + @Override // from interface IShape + public boolean intersects (double rx, double ry, double rw, double rh) { + int cross = Crossing.intersectShape(this, rx, ry, rw, rh); + return (cross == Crossing.CROSSING) || Crossing.isInsideEvenOdd(cross); + } + + @Override // from interface IShape + public boolean intersects (IRectangle r) { + return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + @Override // from interface IShape + public Rectangle getBounds () { + return getBounds(new Rectangle()); + } + + @Override // from interface IShape + public Rectangle getBounds (Rectangle target) { + double x1 = getX1(), y1 = getY1(), x2 = getX2(), y2 = getY2(); + double ctrlx1 = getCtrlX1(), ctrly1 = getCtrlY1(); + double ctrlx2 = getCtrlX2(), ctrly2 = getCtrlY2(); + double rx1 = Math.min(Math.min(x1, x2), Math.min(ctrlx1, ctrlx2)); + double ry1 = Math.min(Math.min(y1, y2), Math.min(ctrly1, ctrly2)); + double rx2 = Math.max(Math.max(x1, x2), Math.max(ctrlx1, ctrlx2)); + double ry2 = Math.max(Math.max(y1, y2), Math.max(ctrly1, ctrly2)); + target.setBounds(rx1, ry1, rx2 - rx1, ry2 - ry1); + return target; + } + + @Override // from interface IShape + public PathIterator getPathIterator (AffineTransform t) { + return new Iterator(this, t); + } + + @Override // from interface IShape + public PathIterator getPathIterator (AffineTransform at, double flatness) { + return new FlatteningPathIterator(getPathIterator(at), flatness); + } + + /** An iterator over an {@link ICubicCurve}. */ + protected static class Iterator implements PathIterator + { + private ICubicCurve c; + private AffineTransform t; + private int index; + + Iterator (ICubicCurve c, AffineTransform t) { + this.c = c; + this.t = t; + } + + @Override public int getWindingRule () { + return WIND_NON_ZERO; + } + + @Override public boolean isDone () { + return index > 1; + } + + @Override public void next () { + index++; + } + + @Override public int currentSegment (double[] coords) { + if (isDone()) { + throw new NoSuchElementException("Iterator out of bounds"); + } + int type; + int count; + if (index == 0) { + type = SEG_MOVETO; + coords[0] = c.getX1(); + coords[1] = c.getY1(); + count = 1; + } else { + type = SEG_CUBICTO; + coords[0] = c.getCtrlX1(); + coords[1] = c.getCtrlY1(); + coords[2] = c.getCtrlX2(); + coords[3] = c.getCtrlY2(); + coords[4] = c.getX2(); + coords[5] = c.getY2(); + count = 3; + } + if (t != null) { + t.transform(coords, 0, coords, 0, count); + } + return type; + } + } +} diff --git a/src/main/java/pythagoras/d/AbstractDimension.java b/src/main/java/pythagoras/d/AbstractDimension.java new file mode 100644 index 0000000..c0ff06e --- /dev/null +++ b/src/main/java/pythagoras/d/AbstractDimension.java @@ -0,0 +1,41 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * Provides most of the implementation of {@link IDimension}, obtaining only width and height from + * the derived class. + */ +public abstract class AbstractDimension implements IDimension +{ + @Override // from interface IDimension + public Dimension clone () { + return new Dimension(this); + } + + @Override + public int hashCode () { + long bits = Double.doubleToLongBits(getWidth()); + bits += Double.doubleToLongBits(getHeight()) * 37; + return (((int) bits) ^ ((int) (bits >> 32))); + } + + @Override + public boolean equals (Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof AbstractDimension) { + AbstractDimension d = (AbstractDimension)obj; + return (d.getWidth() == getWidth() && d.getHeight() == getHeight()); + } + return false; + } + + @Override + public String toString () { + return Dimensions.dimenToString(getWidth(), getHeight()); + } +} diff --git a/src/main/java/pythagoras/d/AbstractEllipse.java b/src/main/java/pythagoras/d/AbstractEllipse.java new file mode 100644 index 0000000..bb3cb93 --- /dev/null +++ b/src/main/java/pythagoras/d/AbstractEllipse.java @@ -0,0 +1,128 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +import java.util.NoSuchElementException; + +/** + * Provides most of the implementation of {@link IEllipse}, obtaining the framing rectangle from + * the derived class. + */ +public abstract class AbstractEllipse extends RectangularShape implements IEllipse +{ + @Override // from IEllipse + public Ellipse clone () { + return new Ellipse(getX(), getY(), getWidth(), getHeight()); + } + + @Override // from interface IShape + public boolean contains (double px, double py) { + if (isEmpty()) return false; + double a = (px - getX()) / getWidth() - 0.5f; + double b = (py - getY()) / getHeight() - 0.5f; + return a * a + b * b < 0.25f; + } + + @Override // from interface IShape + public boolean contains (double rx, double ry, double rw, double rh) { + if (isEmpty() || rw <= 0f || rh <= 0f) return false; + double rx1 = rx, ry1 = ry, rx2 = rx + rw, ry2 = ry + rh; + return contains(rx1, ry1) && contains(rx2, ry1) && contains(rx2, ry2) && contains(rx1, ry2); + } + + @Override // from interface IShape + public boolean intersects (double rx, double ry, double rw, double rh) { + if (isEmpty() || rw <= 0f || rh <= 0f) return false; + double cx = getX() + getWidth() / 2f; + double cy = getY() + getHeight() / 2f; + double rx1 = rx, ry1 = ry, rx2 = rx + rw, ry2 = ry + rh; + double nx = cx < rx1 ? rx1 : (cx > rx2 ? rx2 : cx); + double ny = cy < ry1 ? ry1 : (cy > ry2 ? ry2 : cy); + return contains(nx, ny); + } + + @Override // from interface IShape + public PathIterator getPathIterator (AffineTransform at) { + return new Iterator(this, at); + } + + /** An iterator over an {@link IEllipse}. */ + protected static class Iterator implements PathIterator + { + private final double x, y, width, height; + private final AffineTransform t; + private int index; + + Iterator (IEllipse e, AffineTransform t) { + this.x = e.getX(); + this.y = e.getY(); + this.width = e.getWidth(); + this.height = e.getHeight(); + this.t = t; + if (width < 0f || height < 0f) { + index = 6; + } + } + + @Override public int getWindingRule () { + return WIND_NON_ZERO; + } + + @Override public boolean isDone () { + return index > 5; + } + + @Override public void next () { + index++; + } + + @Override public int currentSegment (double[] coords) { + if (isDone()) { + throw new NoSuchElementException("Iterator out of bounds"); + } + if (index == 5) { + return SEG_CLOSE; + } + int type; + int count; + if (index == 0) { + type = SEG_MOVETO; + count = 1; + double[] p = POINTS[3]; + coords[0] = x + p[4] * width; + coords[1] = y + p[5] * height; + } else { + type = SEG_CUBICTO; + count = 3; + double[] p = POINTS[index - 1]; + int j = 0; + for (int i = 0; i < 3; i++) { + coords[j] = x + p[j++] * width; + coords[j] = y + p[j++] * height; + } + } + if (t != null) { + t.transform(coords, 0, coords, 0, count); + } + return type; + } + } + + // An ellipse is subdivided into four quarters by x and y axis. Each part is approximated by a + // cubic Bezier curve. The arc in the first quarter starts in (a, 0) and finishes in (0, b) + // points. Control points for the cubic curve are (a, 0), (a, m), (n, b) and (0, b) where n and + // m are calculated based on the requirement that the Bezier curve in point 0.5 should lay on + // the arc. + + /** The coefficient to calculate control points of Bezier curves. */ + private static final double U = 2f / 3f * (Math.sqrt(2) - 1f); + + /** The points coordinates calculation table. */ + private static final double[][] POINTS = { + { 1f, 0.5f + U, 0.5f + U, 1f, 0.5f, 1f }, + { 0.5f - U, 1f, 0f, 0.5f + U, 0f, 0.5f }, + { 0f, 0.5f - U, 0.5f - U, 0f, 0.5f, 0f }, + { 0.5f + U, 0f, 1f, 0.5f - U, 1f, 0.5f } }; +} diff --git a/src/main/java/pythagoras/d/AbstractLine.java b/src/main/java/pythagoras/d/AbstractLine.java new file mode 100644 index 0000000..1bb50db --- /dev/null +++ b/src/main/java/pythagoras/d/AbstractLine.java @@ -0,0 +1,211 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +import java.util.NoSuchElementException; + +/** + * Provides most of the implementation of {@link ILine}, obtaining only the start and end points + * from the derived class. + */ +public abstract class AbstractLine implements ILine +{ + @Override // from interface ILine + public Point getP1 () { + return getP1(new Point()); + } + + @Override // from interface ILine + public Point getP1 (Point target) { + target.setLocation(getX1(), getY1()); + return target; + } + + @Override // from interface ILine + public Point getP2 () { + return getP2(new Point()); + } + + @Override // from interface ILine + public Point getP2 (Point target) { + target.setLocation(getX2(), getY2()); + return target; + } + + @Override // from interface ILine + public double pointLineDistSq (double px, double py) { + return Lines.pointLineDistSq(px, py, getX1(), getY1(), getX2(), getY2()); + } + + @Override // from interface ILine + public double pointLineDistSq (IPoint p) { + return Lines.pointLineDistSq(p.getX(), p.getY(), getX1(), getY1(), getX2(), getY2()); + } + + @Override // from interface ILine + public double pointLineDist (double px, double py) { + return Lines.pointLineDist(px, py, getX1(), getY1(), getX2(), getY2()); + } + + @Override // from interface ILine + public double pointLineDist (IPoint p) { + return Lines.pointLineDist(p.getX(), p.getY(), getX1(), getY1(), getX2(), getY2()); + } + + @Override // from interface ILine + public double pointSegDistSq (double px, double py) { + return Lines.pointSegDistSq(px, py, getX1(), getY1(), getX2(), getY2()); + } + + @Override // from interface ILine + public double pointSegDistSq (IPoint p) { + return Lines.pointSegDistSq(p.getX(), p.getY(), getX1(), getY1(), getX2(), getY2()); + } + + @Override // from interface ILine + public double pointSegDist (double px, double py) { + return Lines.pointSegDist(px, py, getX1(), getY1(), getX2(), getY2()); + } + + @Override // from interface ILine + public double pointSegDist (IPoint p) { + return Lines.pointSegDist(p.getX(), p.getY(), getX1(), getY1(), getX2(), getY2()); + } + + @Override // from interface ILine + public int relativeCCW (double px, double py) { + return Lines.relativeCCW(px, py, getX1(), getY1(), getX2(), getY2()); + } + + @Override // from interface ILine + public int relativeCCW (IPoint p) { + return Lines.relativeCCW(p.getX(), p.getY(), getX1(), getY1(), getX2(), getY2()); + } + + @Override // from interface ILine + public Line clone () { + return new Line(getX1(), getY1(), getX2(), getY2()); + } + + @Override // from interface IShape + public boolean isEmpty () { + return false; + } + + @Override // from interface IShape + public boolean contains (double x, double y) { + return false; + } + + @Override // from interface IShape + public boolean contains (IPoint point) { + return false; + } + + @Override // from interface IShape + public boolean contains (double x, double y, double w, double h) { + return false; + } + + @Override // from interface IShape + public boolean contains (IRectangle r) { + return false; + } + + @Override // from interface IShape + public boolean intersects (double rx, double ry, double rw, double rh) { + return Lines.lineIntersectsRect(getX1(), getY1(), getX2(), getY2(), rx, ry, rw, rh); + } + + @Override // from interface IShape + public boolean intersects (IRectangle r) { + return r.intersectsLine(this); + } + + @Override // from interface IShape + public Rectangle getBounds () { + return getBounds(new Rectangle()); + } + + @Override // from interface IShape + public Rectangle getBounds (Rectangle target) { + double x1 = getX1(), x2 = getX2(), y1 = getY1(), y2 = getY2(); + double rx, ry, rw, rh; + if (x1 < x2) { + rx = x1; + rw = x2 - x1; + } else { + rx = x2; + rw = x1 - x2; + } + if (y1 < y2) { + ry = y1; + rh = y2 - y1; + } else { + ry = y2; + rh = y1 - y2; + } + target.setBounds(rx, ry, rw, rh); + return target; + } + + @Override // from interface IShape + public PathIterator getPathIterator (AffineTransform at) { + return new Iterator(this, at); + } + + @Override // from interface IShape + public PathIterator getPathIterator (AffineTransform at, double flatness) { + return new Iterator(this, at); + } + + /** An iterator over an {@link ILine}. */ + protected static class Iterator implements PathIterator + { + private double x1, y1, x2, y2; + private AffineTransform t; + private int index; + + Iterator (ILine l, AffineTransform at) { + this.x1 = l.getX1(); + this.y1 = l.getY1(); + this.x2 = l.getX2(); + this.y2 = l.getY2(); + this.t = at; + } + + @Override public int getWindingRule () { + return WIND_NON_ZERO; + } + + @Override public boolean isDone () { + return index > 1; + } + + @Override public void next () { + index++; + } + + @Override public int currentSegment (double[] coords) { + if (isDone()) { + throw new NoSuchElementException("Iterator out of bounds"); + } + int type; + if (index == 0) { + type = SEG_MOVETO; + coords[0] = x1; + coords[1] = y1; + } else { + type = SEG_LINETO; + coords[0] = x2; + coords[1] = y2; + } + if (t != null) { + t.transform(coords, 0, coords, 0, 1); + } + return type; + } + } +} diff --git a/src/main/java/pythagoras/d/AbstractPoint.java b/src/main/java/pythagoras/d/AbstractPoint.java new file mode 100644 index 0000000..ab7a71c --- /dev/null +++ b/src/main/java/pythagoras/d/AbstractPoint.java @@ -0,0 +1,61 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * Provides most of the implementation of {@link IPoint}, obtaining only the location from the + * derived class. + */ +public abstract class AbstractPoint implements IPoint +{ + @Override // from interface IPoint + public double distanceSq (double px, double py) { + return Points.distanceSq(getX(), getY(), px, py); + } + + @Override // from interface IPoint + public double distanceSq (IPoint p) { + return Points.distanceSq(getX(), getY(), p.getX(), p.getY()); + } + + @Override // from interface IPoint + public double distance (double px, double py) { + return Points.distance(getX(), getY(), px, py); + } + + @Override // from interface IPoint + public double distance (IPoint p) { + return Points.distance(getX(), getY(), p.getX(), p.getY()); + } + + @Override // from interface IPoint + public Point clone () { + return new Point(this); + } + + @Override + public boolean equals (Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof AbstractPoint) { + AbstractPoint p = (AbstractPoint)obj; + return getX() == p.getX() && getY() == p.getY(); + } + return false; + } + + @Override + public int hashCode () { + long bits = Double.doubleToLongBits(getX()); + bits += Double.doubleToLongBits(getY()) * 37; + return (((int) bits) ^ ((int) (bits >> 32))); + } + + @Override + public String toString () { + return Points.pointToString(getX(), getY()); + } +} diff --git a/src/main/java/pythagoras/d/AbstractQuadCurve.java b/src/main/java/pythagoras/d/AbstractQuadCurve.java new file mode 100644 index 0000000..0769d84 --- /dev/null +++ b/src/main/java/pythagoras/d/AbstractQuadCurve.java @@ -0,0 +1,163 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +import java.util.NoSuchElementException; + +/** + * Provides most of the implementation of {@link IQuadCurve}, obtaining only the start, end and + * control point from the derived class. + */ +public abstract class AbstractQuadCurve implements IQuadCurve +{ + @Override // from interface IQuadCurve + public Point getP1 () { + return new Point(getX1(), getY1()); + } + + @Override // from interface IQuadCurve + public Point getCtrlP () { + return new Point(getCtrlX(), getCtrlY()); + } + + @Override // from interface IQuadCurve + public Point getP2 () { + return new Point(getX2(), getY2()); + } + + @Override // from interface IQuadCurve + public double getFlatnessSq () { + return Lines.pointSegDistSq(getCtrlX(), getCtrlY(), getX1(), getY1(), getX2(), getY2()); + } + + @Override // from interface IQuadCurve + public double getFlatness () { + return Lines.pointSegDist(getCtrlX(), getCtrlY(), getX1(), getY1(), getX2(), getY2()); + } + + @Override // from interface IQuadCurve + public void subdivide (QuadCurve left, QuadCurve right) { + QuadCurves.subdivide(this, left, right); + } + + @Override // from interface IQuadCurve + public QuadCurve clone () { + return new QuadCurve(getX1(), getY1(), getCtrlX(), getCtrlY(), getX2(), getY2()); + } + + @Override // from interface IShape + public boolean isEmpty () { + return true; // curves contain no space + } + + @Override // from interface IShape + public boolean contains (double px, double py) { + return Crossing.isInsideEvenOdd(Crossing.crossShape(this, px, py)); + } + + @Override // from interface IShape + public boolean contains (double rx, double ry, double rw, double rh) { + int cross = Crossing.intersectShape(this, rx, ry, rw, rh); + return cross != Crossing.CROSSING && Crossing.isInsideEvenOdd(cross); + } + + @Override // from interface IShape + public boolean contains (IPoint p) { + return contains(p.getX(), p.getY()); + } + + @Override // from interface IShape + public boolean contains (IRectangle r) { + return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + @Override // from interface IShape + public boolean intersects (double rx, double ry, double rw, double rh) { + int cross = Crossing.intersectShape(this, rx, ry, rw, rh); + return cross == Crossing.CROSSING || Crossing.isInsideEvenOdd(cross); + } + + @Override // from interface IShape + public boolean intersects (IRectangle r) { + return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + @Override // from interface IShape + public Rectangle getBounds () { + return getBounds(new Rectangle()); + } + + @Override // from interface IShape + public Rectangle getBounds (Rectangle target) { + double x1 = getX1(), y1 = getY1(), x2 = getX2(), y2 = getY2(); + double ctrlx = getCtrlX(), ctrly = getCtrlY(); + double rx0 = Math.min(Math.min(x1, x2), ctrlx); + double ry0 = Math.min(Math.min(y1, y2), ctrly); + double rx1 = Math.max(Math.max(x1, x2), ctrlx); + double ry1 = Math.max(Math.max(y1, y2), ctrly); + target.setBounds(rx0, ry0, rx1 - rx0, ry1 - ry0); + return target; + } + + @Override // from interface IShape + public PathIterator getPathIterator (AffineTransform t) { + return new Iterator(this, t); + } + + @Override // from interface IShape + public PathIterator getPathIterator (AffineTransform t, double flatness) { + return new FlatteningPathIterator(getPathIterator(t), flatness); + } + + /** An iterator over an {@link IQuadCurve}. */ + protected static class Iterator implements PathIterator + { + private IQuadCurve c; + private AffineTransform t; + private int index; + + Iterator (IQuadCurve q, AffineTransform t) { + this.c = q; + this.t = t; + } + + @Override public int getWindingRule () { + return WIND_NON_ZERO; + } + + @Override public boolean isDone () { + return (index > 1); + } + + @Override public void next () { + index++; + } + + @Override public int currentSegment (double[] coords) { + if (isDone()) { + throw new NoSuchElementException("Iterator out of bounds"); + } + int type; + int count; + if (index == 0) { + type = SEG_MOVETO; + coords[0] = c.getX1(); + coords[1] = c.getY1(); + count = 1; + } else { + type = SEG_QUADTO; + coords[0] = c.getCtrlX(); + coords[1] = c.getCtrlY(); + coords[2] = c.getX2(); + coords[3] = c.getY2(); + count = 2; + } + if (t != null) { + t.transform(coords, 0, coords, 0, count); + } + return type; + } + } +} diff --git a/src/main/java/pythagoras/d/AbstractRectangle.java b/src/main/java/pythagoras/d/AbstractRectangle.java new file mode 100644 index 0000000..8c35c26 --- /dev/null +++ b/src/main/java/pythagoras/d/AbstractRectangle.java @@ -0,0 +1,238 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +import java.util.NoSuchElementException; + +/** + * Provides most of the implementation of {@link IRectangle}, obtaining only the location and + * dimensions from the derived class. + */ +public abstract class AbstractRectangle extends RectangularShape implements IRectangle +{ + @Override // from interface IRectangle + public Point getLocation () { + return getLocation(new Point()); + } + + @Override // from interface IRectangle + public Point getLocation (Point target) { + target.setLocation(getX(), getY()); + return target; + } + + @Override // from interface IRectangle + public Dimension getSize () { + return getSize(new Dimension()); + } + + @Override // from interface IRectangle + public Dimension getSize (Dimension target) { + target.setSize(getWidth(), getHeight()); + return target; + } + + @Override // from interface IRectangle + public Rectangle intersection (double rx, double ry, double rw, double rh) { + double x1 = Math.max(getX(), rx); + double y1 = Math.max(getY(), ry); + double x2 = Math.min(getMaxX(), rx + rw); + double y2 = Math.min(getMaxY(), ry + rh); + return new Rectangle(x1, y1, x2 - x1, y2 - y1); + } + + @Override // from interface IRectangle + public Rectangle intersection (IRectangle r) { + return intersection(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + @Override // from interface IRectangle + public Rectangle union (IRectangle r) { + Rectangle rect = new Rectangle(this); + rect.add(r); + return rect; + } + + @Override // from interface IRectangle + public boolean intersectsLine (double x1, double y1, double x2, double y2) { + return Lines.lineIntersectsRect(x1, y1, x2, y2, getX(), getY(), getWidth(), getHeight()); + } + + @Override // from interface IRectangle + public boolean intersectsLine (ILine l) { + return intersectsLine(l.getX1(), l.getY1(), l.getX2(), l.getY2()); + } + + @Override // from interface IRectangle + public int outcode (double px, double py) { + int code = 0; + + if (getWidth() <= 0) { + code |= OUT_LEFT | OUT_RIGHT; + } else if (px < getX()) { + code |= OUT_LEFT; + } else if (px > getMaxX()) { + code |= OUT_RIGHT; + } + + if (getHeight() <= 0) { + code |= OUT_TOP | OUT_BOTTOM; + } else if (py < getY()) { + code |= OUT_TOP; + } else if (py > getMaxY()) { + code |= OUT_BOTTOM; + } + + return code; + } + + @Override // from interface IRectangle + public int outcode (IPoint p) { + return outcode(p.getX(), p.getY()); + } + + @Override // from interface IRectangle + public Rectangle clone () { + return new Rectangle(this); + } + + @Override // from interface IShape + public boolean contains (double px, double py) { + if (isEmpty()) return false; + + double x = getX(), y = getY(); + if (px < x || py < y) return false; + + px -= x; + py -= y; + return px < getWidth() && py < getHeight(); + } + + @Override // from interface IShape + public boolean contains (double rx, double ry, double rw, double rh) { + if (isEmpty()) return false; + + double x1 = getX(), y1 = getY(), x2 = x1 + getWidth(), y2 = y1 + getHeight(); + return (x1 <= rx) && (rx + rw <= x2) && (y1 <= ry) && (ry + rh <= y2); + } + + @Override // from interface IShape + public boolean intersects (double rx, double ry, double rw, double rh) { + if (isEmpty()) return false; + + double x1 = getX(), y1 = getY(), x2 = x1 + getWidth(), y2 = y1 + getHeight(); + return (rx + rw > x1) && (rx < x2) && (ry + rh > y1) && (ry < y2); + } + + @Override // from interface IShape + public PathIterator getPathIterator (AffineTransform t) { + return new Iterator(this, t); + } + + @Override // from interface IShape + public PathIterator getPathIterator (AffineTransform t, double flatness) { + return new Iterator(this, t); + } + + @Override // from Object + public boolean equals (Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof AbstractRectangle) { + AbstractRectangle r = (AbstractRectangle)obj; + return r.getX() == getX() && r.getY() == getY() && + r.getWidth() == getWidth() && r.getHeight() == getHeight(); + } + return false; + } + + @Override // from Object + public int hashCode () { + long bits = Double.doubleToLongBits(getX()); + bits += Double.doubleToLongBits(getY()) * 37; + bits += Double.doubleToLongBits(getWidth()) * 43; + bits += Double.doubleToLongBits(getHeight()) * 47; + return (((int) bits) ^ ((int) (bits >> 32))); + } + + @Override // from Object + public String toString () { + return Dimensions.dimenToString(getWidth(), getHeight()) + + Points.pointToString(getX(), getY()); + } + + /** An iterator over an {@link IRectangle}. */ + protected static class Iterator implements PathIterator + { + private double x, y, width, height; + private AffineTransform t; + + /** The current segmenet index. */ + private int index; + + Iterator (IRectangle r, AffineTransform at) { + this.x = r.getX(); + this.y = r.getY(); + this.width = r.getWidth(); + this.height = r.getHeight(); + this.t = at; + if (width < 0f || height < 0f) { + index = 6; + } + } + + @Override public int getWindingRule () { + return WIND_NON_ZERO; + } + + @Override public boolean isDone () { + return index > 5; + } + + @Override public void next () { + index++; + } + + @Override public int currentSegment (double[] coords) { + if (isDone()) { + throw new NoSuchElementException("Iterator out of bounds"); + } + if (index == 5) { + return SEG_CLOSE; + } + int type; + if (index == 0) { + type = SEG_MOVETO; + coords[0] = x; + coords[1] = y; + } else { + type = SEG_LINETO; + switch (index) { + case 1: + coords[0] = x + width; + coords[1] = y; + break; + case 2: + coords[0] = x + width; + coords[1] = y + height; + break; + case 3: + coords[0] = x; + coords[1] = y + height; + break; + case 4: + coords[0] = x; + coords[1] = y; + break; + } + } + if (t != null) { + t.transform(coords, 0, coords, 0, 1); + } + return type; + } + } +} diff --git a/src/main/java/pythagoras/d/AbstractRoundRectangle.java b/src/main/java/pythagoras/d/AbstractRoundRectangle.java new file mode 100644 index 0000000..9bcb48c --- /dev/null +++ b/src/main/java/pythagoras/d/AbstractRoundRectangle.java @@ -0,0 +1,158 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +import java.util.NoSuchElementException; + +/** + * Provides most of the implementation of {@link IRoundRectangle}, obtaining the framing rectangle + * from the derived class. + */ +public abstract class AbstractRoundRectangle extends RectangularShape implements IRoundRectangle +{ + @Override // from interface IRoundRectangle + public RoundRectangle clone () { + return new RoundRectangle(getX(), getY(), getWidth(), getHeight(), + getArcWidth(), getArcHeight()); + } + + @Override // from interface IShape + public boolean contains (double px, double py) { + if (isEmpty()) return false; + + double rx1 = getX(), ry1 = getY(); + double rx2 = rx1 + getWidth(), ry2 = ry1 + getHeight(); + if (px < rx1 || px >= rx2 || py < ry1 || py >= ry2) { + return false; + } + + double aw = getArcWidth() / 2f, ah = getArcHeight() / 2f; + double cx, cy; + if (px < rx1 + aw) { + cx = rx1 + aw; + } else if (px > rx2 - aw) { + cx = rx2 - aw; + } else { + return true; + } + + if (py < ry1 + ah) { + cy = ry1 + ah; + } else if (py > ry2 - ah) { + cy = ry2 - ah; + } else { + return true; + } + + px = (px - cx) / aw; + py = (py - cy) / ah; + return px * px + py * py <= 1f; + } + + @Override // from interface IShape + public boolean contains (double rx, double ry, double rw, double rh) { + if (isEmpty() || rw <= 0f || rh <= 0f) return false; + double rx1 = rx, ry1 = ry, rx2 = rx + rw, ry2 = ry + rh; + return contains(rx1, ry1) && contains(rx2, ry1) && contains(rx2, ry2) && contains(rx1, ry2); + } + + @Override // from interface IShape + public boolean intersects (double rx, double ry, double rw, double rh) { + if (isEmpty() || rw <= 0f || rh <= 0f) return false; + + double x1 = getX(), y1 = getY(), x2 = x1 + getWidth(), y2 = y1 + getHeight(); + double rx1 = rx, ry1 = ry, rx2 = rx + rw, ry2 = ry + rh; + if (rx2 < x1 || x2 < rx1 || ry2 < y1 || y2 < ry1) { + return false; + } + + double cx = (x1 + x2) / 2f, cy = (y1 + y2) / 2f; + double nx = cx < rx1 ? rx1 : (cx > rx2 ? rx2 : cx); + double ny = cy < ry1 ? ry1 : (cy > ry2 ? ry2 : cy); + return contains(nx, ny); + } + + @Override // from interface IShape + public PathIterator getPathIterator (AffineTransform at) { + return new Iterator(this, at); + } + + /** Provides an iterator over an {@link IRoundRectangle}. */ + protected static class Iterator implements PathIterator + { + private final double x, y, width, height, aw, ah; + private final AffineTransform t; + private int index; + + Iterator (IRoundRectangle rr, AffineTransform at) { + this.x = rr.getX(); + this.y = rr.getY(); + this.width = rr.getWidth(); + this.height = rr.getHeight(); + this.aw = Math.min(width, rr.getArcWidth()); + this.ah = Math.min(height, rr.getArcHeight()); + this.t = at; + if (width < 0f || height < 0f || aw < 0f || ah < 0f) { + index = POINTS.length; + } + } + + @Override public int getWindingRule () { + return WIND_NON_ZERO; + } + + @Override public boolean isDone () { + return index > POINTS.length; + } + + @Override public void next () { + index++; + } + + @Override public int currentSegment (double[] coords) { + if (isDone()) { + throw new NoSuchElementException("Iterator out of bounds"); + } + if (index == POINTS.length) { + return SEG_CLOSE; + } + int j = 0; + double[] p = POINTS[index]; + for (int i = 0; i < p.length; i += 4) { + coords[j++] = x + p[i + 0] * width + p[i + 1] * aw; + coords[j++] = y + p[i + 2] * height + p[i + 3] * ah; + } + if (t != null) { + t.transform(coords, 0, coords, 0, j / 2); + } + return TYPES[index]; + } + } + + // the path for round corners is generated the same way as for Ellipse + + /** The segment types correspond to points array. */ + protected static final int[] TYPES = { + PathIterator.SEG_MOVETO, PathIterator.SEG_LINETO, PathIterator.SEG_CUBICTO, + PathIterator.SEG_LINETO, PathIterator.SEG_CUBICTO, PathIterator.SEG_LINETO, + PathIterator.SEG_CUBICTO, PathIterator.SEG_LINETO, PathIterator.SEG_CUBICTO + }; + + /** The coefficient to calculate control points of Bezier curves. */ + protected static final double U = 0.5f - 2f / 3f * (Math.sqrt(2f) - 1f); + + /** The points coordinates calculation table. */ + protected static final double[][] POINTS = { + { 0f, 0.5f, 0f, 0f }, // MOVETO + { 1f, -0.5f, 0f, 0f }, // LINETO + { 1f, -U, 0f, 0f, 1f, 0f, 0f, U, 1f, 0f, 0f, 0.5f }, // CUBICTO + { 1f, 0f, 1f, -0.5f }, // LINETO + { 1f, 0f, 1f, -U, 1f, -U, 1f, 0f, 1f, -0.5f, 1f, 0f }, // CUBICTO + { 0f, 0.5f, 1f, 0f }, // LINETO + { 0f, U, 1f, 0f, 0f, 0f, 1f, -U, 0f, 0f, 1f, -0.5f }, // CUBICTO + { 0f, 0f, 0f, 0.5f }, // LINETO + { 0f, 0f, 0f, U, 0f, U, 0f, 0f, 0f, 0.5f, 0f, 0f }, // CUBICTO + }; +} diff --git a/src/main/java/pythagoras/d/AffineTransform.java b/src/main/java/pythagoras/d/AffineTransform.java new file mode 100644 index 0000000..d3abe6c --- /dev/null +++ b/src/main/java/pythagoras/d/AffineTransform.java @@ -0,0 +1,489 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +import java.io.Serializable; + +/** + * Represents a 2D affine transform, which performs a linear mapping that preserves the + * straightness and parallelness of lines. + * + * @see http://download.oracle.com/javase/6/docs/api/java/awt/geom/AffineTransform.html + */ +public class AffineTransform implements Cloneable, Serializable +{ + public static final int TYPE_IDENTITY = 0; + public static final int TYPE_TRANSLATION = 1; + public static final int TYPE_UNIFORM_SCALE = 2; + public static final int TYPE_GENERAL_SCALE = 4; + public static final int TYPE_QUADRANT_ROTATION = 8; + public static final int TYPE_GENERAL_ROTATION = 16; + public static final int TYPE_GENERAL_TRANSFORM = 32; + public static final int TYPE_FLIP = 64; + public static final int TYPE_MASK_SCALE = TYPE_UNIFORM_SCALE | TYPE_GENERAL_SCALE; + public static final int TYPE_MASK_ROTATION = TYPE_QUADRANT_ROTATION | TYPE_GENERAL_ROTATION; + + public AffineTransform () { + this.type = TYPE_IDENTITY; + this.m00 = this.m11 = 1f; + this.m10 = this.m01 = this.m02 = this.m12 = 0; + } + + public AffineTransform (AffineTransform t) { + this.type = t.type; + this.m00 = t.m00; + this.m10 = t.m10; + this.m01 = t.m01; + this.m11 = t.m11; + this.m02 = t.m02; + this.m12 = t.m12; + } + + public AffineTransform (double m00, double m10, double m01, + double m11, double m02, double m12) { + this.type = TYPE_UNKNOWN; + this.m00 = m00; + this.m10 = m10; + this.m01 = m01; + this.m11 = m11; + this.m02 = m02; + this.m12 = m12; + } + + public AffineTransform (double[] matrix) { + this.type = TYPE_UNKNOWN; + m00 = matrix[0]; + m10 = matrix[1]; + m01 = matrix[2]; + m11 = matrix[3]; + if (matrix.length > 4) { + m02 = matrix[4]; + m12 = matrix[5]; + } + } + + /* + * Method returns type of affine transformation. + * + * Transform matrix is m00 m01 m02 m10 m11 m12 + * + * According analytic geometry new basis vectors are (m00, m01) and (m10, + * m11), translation vector is (m02, m12). Original basis vectors are (1, 0) + * and (0, 1). Type transformations classification: TYPE_IDENTITY - new + * basis equals original one and zero translation TYPE_TRANSLATION - + * translation vector isn't zero TYPE_UNIFORM_SCALE - vectors length of new + * basis equals TYPE_GENERAL_SCALE - vectors length of new basis doesn't + * equal TYPE_FLIP - new basis vector orientation differ from original one + * TYPE_QUADRANT_ROTATION - new basis is rotated by 90, 180, 270, or 360 + * degrees TYPE_GENERAL_ROTATION - new basis is rotated by arbitrary angle + * TYPE_GENERAL_TRANSFORM - transformation can't be inversed + */ + public int getType () { + if (type != TYPE_UNKNOWN) { + return type; + } + + int type = 0; + + if (m00 * m01 + m10 * m11 != 0) { + type |= TYPE_GENERAL_TRANSFORM; + return type; + } + + if (m02 != 0 || m12 != 0) { + type |= TYPE_TRANSLATION; + } else if (m00 == 1f && m11 == 1f && m01 == 0 && m10 == 0) { + type = TYPE_IDENTITY; + return type; + } + + if (m00 * m11 - m01 * m10 < 0) { + type |= TYPE_FLIP; + } + + double dx = m00 * m00 + m10 * m10; + double dy = m01 * m01 + m11 * m11; + if (dx != dy) { + type |= TYPE_GENERAL_SCALE; + } else if (dx != 1f) { + type |= TYPE_UNIFORM_SCALE; + } + + if ((m00 == 0 && m11 == 0) || (m10 == 0 && m01 == 0 && (m00 < 0 || m11 < 0))) { + type |= TYPE_QUADRANT_ROTATION; + } else if (m01 != 0 || m10 != 0) { + type |= TYPE_GENERAL_ROTATION; + } + + return type; + } + + public double getScaleX () { + return m00; + } + + public double getScaleY () { + return m11; + } + + public double getShearX () { + return m01; + } + + public double getShearY () { + return m10; + } + + public double getTranslateX () { + return m02; + } + + public double getTranslateY () { + return m12; + } + + public boolean isIdentity () { + return getType() == TYPE_IDENTITY; + } + + public void getMatrix (double[] matrix) { + matrix[0] = m00; + matrix[1] = m10; + matrix[2] = m01; + matrix[3] = m11; + if (matrix.length > 4) { + matrix[4] = m02; + matrix[5] = m12; + } + } + + public double getDeterminant () { + return m00 * m11 - m01 * m10; + } + + public void setTransform (double m00, double m10, double m01, double m11, double m02, double m12) { + this.type = TYPE_UNKNOWN; + this.m00 = m00; + this.m10 = m10; + this.m01 = m01; + this.m11 = m11; + this.m02 = m02; + this.m12 = m12; + } + + public void setTransform (AffineTransform t) { + type = t.type; + setTransform(t.m00, t.m10, t.m01, t.m11, t.m02, t.m12); + } + + public void setToIdentity () { + type = TYPE_IDENTITY; + m00 = m11 = 1f; + m10 = m01 = m02 = m12 = 0; + } + + public void setToTranslation (double mx, double my) { + m00 = m11 = 1f; + m01 = m10 = 0; + m02 = mx; + m12 = my; + if (mx == 0 && my == 0) { + type = TYPE_IDENTITY; + } else { + type = TYPE_TRANSLATION; + } + } + + public void setToScale (double scx, double scy) { + m00 = scx; + m11 = scy; + m10 = m01 = m02 = m12 = 0; + if (scx != 1f || scy != 1f) { + type = TYPE_UNKNOWN; + } else { + type = TYPE_IDENTITY; + } + } + + public void setToShear (double shx, double shy) { + m00 = m11 = 1f; + m02 = m12 = 0; + m01 = shx; + m10 = shy; + if (shx != 0 || shy != 0) { + type = TYPE_UNKNOWN; + } else { + type = TYPE_IDENTITY; + } + } + + public void setToRotation (double angle) { + double sin = Math.sin(angle); + double cos = Math.cos(angle); + if (Math.abs(cos) < ZERO) { + cos = 0; + sin = sin > 0 ? 1f : -1f; + } else if (Math.abs(sin) < ZERO) { + sin = 0; + cos = cos > 0 ? 1f : -1f; + } + m00 = m11 = cos; + m01 = -sin; + m10 = sin; + m02 = m12 = 0; + type = TYPE_UNKNOWN; + } + + public void setToRotation (double angle, double px, double py) { + setToRotation(angle); + m02 = px * (1f - m00) + py * m10; + m12 = py * (1f - m00) - px * m10; + type = TYPE_UNKNOWN; + } + + public static AffineTransform getTranslateInstance (double mx, double my) { + AffineTransform t = new AffineTransform(); + t.setToTranslation(mx, my); + return t; + } + + public static AffineTransform getScaleInstance (double scx, double scY) { + AffineTransform t = new AffineTransform(); + t.setToScale(scx, scY); + return t; + } + + public static AffineTransform getShearInstance (double shx, double shy) { + AffineTransform m = new AffineTransform(); + m.setToShear(shx, shy); + return m; + } + + public static AffineTransform getRotateInstance (double angle) { + AffineTransform t = new AffineTransform(); + t.setToRotation(angle); + return t; + } + + public static AffineTransform getRotateInstance (double angle, double x, double y) { + AffineTransform t = new AffineTransform(); + t.setToRotation(angle, x, y); + return t; + } + + public void translate (double mx, double my) { + concatenate(AffineTransform.getTranslateInstance(mx, my)); + } + + public void scale (double scx, double scy) { + concatenate(AffineTransform.getScaleInstance(scx, scy)); + } + + public void shear (double shx, double shy) { + concatenate(AffineTransform.getShearInstance(shx, shy)); + } + + public void rotate (double angle) { + concatenate(AffineTransform.getRotateInstance(angle)); + } + + public void rotate (double angle, double px, double py) { + concatenate(AffineTransform.getRotateInstance(angle, px, py)); + } + + /** + * Multiply matrix of two AffineTransform objects + * + * @param t1 + * - the AffineTransform object is a multiplicand + * @param t2 + * - the AffineTransform object is a multiplier + * @return an AffineTransform object that is a result of t1 multiplied by + * matrix t2. + */ + AffineTransform multiply (AffineTransform t1, AffineTransform t2) { + return new AffineTransform(t1.m00 * t2.m00 + t1.m10 * t2.m01, // m00 + t1.m00 * t2.m10 + t1.m10 * t2.m11, // m01 + t1.m01 * t2.m00 + t1.m11 * t2.m01, // m10 + t1.m01 * t2.m10 + t1.m11 * t2.m11, // m11 + t1.m02 * t2.m00 + t1.m12 * t2.m01 + t2.m02, // m02 + t1.m02 * t2.m10 + t1.m12 * t2.m11 + t2.m12);// m12 + } + + public void concatenate (AffineTransform t) { + setTransform(multiply(t, this)); + } + + public void preConcatenate (AffineTransform t) { + setTransform(multiply(this, t)); + } + + public AffineTransform createInverse () throws NoninvertibleTransformException { + double det = getDeterminant(); + if (Math.abs(det) < ZERO) { + throw new NoninvertibleTransformException("Determinant is zero"); + } + return new AffineTransform(m11 / det, // m00 + -m10 / det, // m10 + -m01 / det, // m01 + m00 / det, // m11 + (m01 * m12 - m11 * m02) / det, // m02 + (m10 * m02 - m00 * m12) / det // m12 + ); + } + + public Point transform (IPoint src, Point dst) { + if (dst == null) { + dst = new Point(); + } + + double x = src.getX(), y = src.getY(); + dst.setLocation(x * m00 + y * m01 + m02, x * m10 + y * m11 + m12); + return dst; + } + + public void transform (IPoint[] src, int srcOff, Point[] dst, int dstOff, int length) { + while (--length >= 0) { + IPoint srcPoint = src[srcOff++]; + double x = srcPoint.getX(); + double y = srcPoint.getY(); + Point dstPoint = dst[dstOff]; + if (dstPoint == null) { + dstPoint = new Point(); + } + dstPoint.setLocation(x * m00 + y * m01 + m02, x * m10 + y * m11 + m12); + dst[dstOff++] = dstPoint; + } + } + + public void transform (double[] src, int srcOff, double[] dst, int dstOff, int length) { + int step = 2; + if (src == dst && srcOff < dstOff && dstOff < srcOff + length * 2) { + srcOff = srcOff + length * 2 - 2; + dstOff = dstOff + length * 2 - 2; + step = -2; + } + while (--length >= 0) { + double x = src[srcOff + 0]; + double y = src[srcOff + 1]; + dst[dstOff + 0] = (x * m00 + y * m01 + m02); + dst[dstOff + 1] = (x * m10 + y * m11 + m12); + srcOff += step; + dstOff += step; + } + } + + public Point deltaTransform (IPoint src, Point dst) { + if (dst == null) { + dst = new Point(); + } + double x = src.getX(), y = src.getY(); + dst.setLocation(x * m00 + y * m01, x * m10 + y * m11); + return dst; + } + + public void deltaTransform (double[] src, int srcOff, double[] dst, int dstOff, int length) { + while (--length >= 0) { + double x = src[srcOff++], y = src[srcOff++]; + dst[dstOff++] = x * m00 + y * m01; + dst[dstOff++] = x * m10 + y * m11; + } + } + + public Point inverseTransform (IPoint src, Point dst) throws NoninvertibleTransformException { + double det = getDeterminant(); + if (Math.abs(det) < ZERO) { + throw new NoninvertibleTransformException("Determinant is zero"); + } + if (dst == null) { + dst = new Point(); + } + double x = src.getX() - m02, y = src.getY() - m12; + dst.setLocation((x * m11 - y * m01) / det, (y * m00 - x * m10) / det); + return dst; + } + + public void inverseTransform (double[] src, int srcOff, double[] dst, int dstOff, int length) + throws NoninvertibleTransformException { + double det = getDeterminant(); + if (Math.abs(det) < ZERO) { + throw new NoninvertibleTransformException("Determinant is zero"); + } + while (--length >= 0) { + double x = src[srcOff++] - m02, y = src[srcOff++] - m12; + dst[dstOff++] = (x * m11 - y * m01) / det; + dst[dstOff++] = (y * m00 - x * m10) / det; + } + } + + public IShape createTransformedShape (IShape src) { + if (src == null) { + return null; + } + if (src instanceof Path) { + return ((Path)src).createTransformedShape(this); + } + PathIterator path = src.getPathIterator(this); + Path dst = new Path(path.getWindingRule()); + dst.append(path, false); + return dst; + } + + @Override + public String toString () { + return getClass().getName() + + "[[" + m00 + ", " + m01 + ", " + m02 + "], [" + m10 + ", " + m11 + ", " + m12 + "]]"; + } + + @Override + public AffineTransform clone () { + try { + return (AffineTransform)super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + @Override + public int hashCode () { + long bits = Double.doubleToLongBits(m00); + bits += Double.doubleToLongBits(m01) * 37; + bits += Double.doubleToLongBits(m02) * 43; + bits += Double.doubleToLongBits(m10) * 47; + bits += Double.doubleToLongBits(m11) * 53; + bits += Double.doubleToLongBits(m12) * 59; + return (((int) bits) ^ ((int) (bits >> 32))); + } + + @Override + public boolean equals (Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof AffineTransform) { + AffineTransform t = (AffineTransform)obj; + return m00 == t.m00 && m01 == t.m01 && m02 == t.m02 && + m10 == t.m10 && m11 == t.m11 && m12 == t.m12; + } + return false; + } + + // the values of transformation matrix + private double m00; + private double m10; + private double m01; + private double m11; + private double m02; + private double m12; + + /** The transformation {@code type}. */ + private transient int type; + + /** An initial type value. */ + private static final int TYPE_UNKNOWN = -1; + + /** The min value equivalent to zero. An absolute value < ZERO is considered to be zero. */ + private static final double ZERO = 1E-10f; +} diff --git a/src/main/java/pythagoras/d/Arc.java b/src/main/java/pythagoras/d/Arc.java new file mode 100644 index 0000000..1c55adf --- /dev/null +++ b/src/main/java/pythagoras/d/Arc.java @@ -0,0 +1,241 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +import java.io.Serializable; + +/** + * Represents an arc defined by a framing rectangle, start angle, angular extend, and closure type. + */ +public class Arc extends AbstractArc implements Serializable +{ + /** The x-coordinate of this arc's framing rectangle. */ + public double x; + + /** The y-coordinate of this arc's framing rectangle. */ + public double y; + + /** The width of this arc's framing rectangle. */ + public double width; + + /** The height of this arc's framing rectangle. */ + public double height; + + /** The starting angle of this arc. */ + public double start; + + /** The angular extent of this arc. */ + public double extent; + + /** + * Creates an open arc with frame (0x0+0+0) and zero angles. + */ + public Arc () { + this(OPEN); + } + + /** + * Creates an arc of the specified type with frame (0x0+0+0) and zero angles. + */ + public Arc (int type) { + setArcType(type); + } + + /** + * Creates an arc of the specified type with the specified framing rectangle, starting angle + * and angular extent. + */ + public Arc (double x, double y, double width, double height, + double start, double extent, int type) { + setArc(x, y, width, height, start, extent, type); + } + + /** + * Creates an arc of the specified type with the supplied framing rectangle, starting angle and + * angular extent. + */ + public Arc (IRectangle bounds, double start, double extent, int type) { + setArc(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), + start, extent, type); + } + + @Override // from interface IArc + public int getArcType () { + return type; + } + + @Override // from interface IArc + public double getX () { + return x; + } + + @Override // from interface IArc + public double getY () { + return y; + } + + @Override // from interface IArc + public double getWidth () { + return width; + } + + @Override // from interface IArc + public double getHeight () { + return height; + } + + @Override // from interface IArc + public double getAngleStart () { + return start; + } + + @Override // from interface IArc + public double getAngleExtent () { + return extent; + } + + /** + * Sets the type of this arc to the specified value. + */ + public void setArcType (int type) { + if (type != OPEN && type != CHORD && type != PIE) { + throw new IllegalArgumentException("Invalid Arc type: " + type); + } + this.type = type; + } + + /** + * Sets the starting angle of this arc to the specified value. + */ + public void setAngleStart (double start) { + this.start = start; + } + + /** + * Sets the angular extent of this arc to the specified value. + */ + public void setAngleExtent (double extent) { + this.extent = extent; + } + + /** + * Sets the location, size, angular extents, and closure type of this arc to the specified + * values. + */ + public void setArc (double x, double y, double width, double height, + double start, double extent, int type) { + setArcType(type); + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.start = start; + this.extent = extent; + } + + /** + * Sets the location, size, angular extents, and closure type of this arc to the specified + * values. + */ + public void setArc (IPoint point, IDimension size, double start, double extent, int type) { + setArc(point.getX(), point.getY(), size.getWidth(), size.getHeight(), start, extent, type); + } + + /** + * Sets the location, size, angular extents, and closure type of this arc to the specified + * values. + */ + public void setArc (IRectangle rect, double start, double extent, int type) { + setArc(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight(), start, extent, type); + } + + /** + * Sets the location, size, angular extents, and closure type of this arc to the same values as + * the supplied arc. + */ + public void setArc (IArc arc) { + setArc(arc.getX(), arc.getY(), arc.getWidth(), arc.getHeight(), arc.getAngleStart(), + arc.getAngleExtent(), arc.getArcType()); + } + + /** + * Sets the location, size, angular extents, and closure type of this arc based on the + * specified values. + */ + public void setArcByCenter (double x, double y, double radius, + double start, double extent, int type) { + setArc(x - radius, y - radius, radius * 2f, radius * 2f, start, extent, type); + } + + /** + * Sets the location, size, angular extents, and closure type of this arc based on the + * specified values. + */ + public void setArcByTangent (IPoint p1, IPoint p2, IPoint p3, double radius) { + // use simple geometric calculations of arc center, radius and angles by tangents + double a1 = -Math.atan2(p1.getY() - p2.getY(), p1.getX() - p2.getX()); + double a2 = -Math.atan2(p3.getY() - p2.getY(), p3.getX() - p2.getX()); + double am = (a1 + a2) / 2f; + double ah = a1 - am; + double d = radius / Math.abs(Math.sin(ah)); + double x = p2.getX() + d * Math.cos(am); + double y = p2.getY() - d * Math.sin(am); + ah = ah >= 0f ? Math.PI * 1.5f - ah : Math.PI * 0.5f - ah; + a1 = getNormAngle(Math.toDegrees(am - ah)); + a2 = getNormAngle(Math.toDegrees(am + ah)); + double delta = a2 - a1; + if (delta <= 0f) { + delta += 360f; + } + setArcByCenter(x, y, radius, a1, delta, type); + } + + /** + * Sets the starting angle of this arc to the angle defined by the supplied point relative to + * the center of this arc. + */ + public void setAngleStart (IPoint point) { + double angle = Math.atan2(point.getY() - getCenterY(), point.getX() - getCenterX()); + setAngleStart(getNormAngle(-Math.toDegrees(angle))); + } + + /** + * Sets the starting angle and angular extent of this arc using two sets of coordinates. The + * first set of coordinates is used to determine the angle of the starting point relative to + * the arc's center. The second set of coordinates is used to determine the angle of the end + * point relative to the arc's center. The arc will always be non-empty and extend + * counterclockwise from the first point around to the second point. + */ + public void setAngles (double x1, double y1, double x2, double y2) { + double cx = getCenterX(); + double cy = getCenterY(); + double a1 = getNormAngle(-Math.toDegrees(Math.atan2(y1 - cy, x1 - cx))); + double a2 = getNormAngle(-Math.toDegrees(Math.atan2(y2 - cy, x2 - cx))); + a2 -= a1; + if (a2 <= 0f) { + a2 += 360f; + } + setAngleStart(a1); + setAngleExtent(a2); + } + + /** + * Sets the starting angle and angular extent of this arc using two sets of coordinates. The + * first set of coordinates is used to determine the angle of the starting point relative to + * the arc's center. The second set of coordinates is used to determine the angle of the end + * point relative to the arc's center. The arc will always be non-empty and extend + * counterclockwise from the first point around to the second point. + */ + public void setAngles (IPoint p1, IPoint p2) { + setAngles(p1.getX(), p1.getY(), p2.getX(), p2.getY()); + } + + @Override // from RectangularShape + public void setFrame (double x, double y, double width, double height) { + setArc(x, y, width, height, getAngleStart(), getAngleExtent(), type); + } + + private int type; +} diff --git a/src/main/java/pythagoras/d/Area.java b/src/main/java/pythagoras/d/Area.java new file mode 100644 index 0000000..e2652b1 --- /dev/null +++ b/src/main/java/pythagoras/d/Area.java @@ -0,0 +1,1244 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +import java.util.NoSuchElementException; + +/** + * Stores and manipulates an enclosed area of 2D space. + * @see http://download.oracle.com/javase/6/docs/api/java/awt/geom/Area.html + */ +public class Area implements IShape, Cloneable +{ + /** + * Creates an empty area. + */ + public Area () { + } + + /** + * Creates an area from the supplied shape. + */ + public Area (IShape s) { + double[] segmentCoords = new double[6]; + double lastMoveX = 0f; + double lastMoveY = 0f; + int rulesIndex = 0; + int coordsIndex = 0; + + for (PathIterator pi = s.getPathIterator(null); !pi.isDone(); pi.next()) { + coords = adjustSize(coords, coordsIndex + 6); + rules = adjustSize(rules, rulesIndex + 1); + offsets = adjustSize(offsets, rulesIndex + 1); + rules[rulesIndex] = pi.currentSegment(segmentCoords); + offsets[rulesIndex] = coordsIndex; + + switch (rules[rulesIndex]) { + case PathIterator.SEG_MOVETO: + coords[coordsIndex++] = segmentCoords[0]; + coords[coordsIndex++] = segmentCoords[1]; + lastMoveX = segmentCoords[0]; + lastMoveY = segmentCoords[1]; + ++moveToCount; + break; + case PathIterator.SEG_LINETO: + if ((segmentCoords[0] != lastMoveX) || (segmentCoords[1] != lastMoveY)) { + coords[coordsIndex++] = segmentCoords[0]; + coords[coordsIndex++] = segmentCoords[1]; + } else { + --rulesIndex; + } + break; + case PathIterator.SEG_QUADTO: + System.arraycopy(segmentCoords, 0, coords, coordsIndex, 4); + coordsIndex += 4; + isPolygonal = false; + break; + case PathIterator.SEG_CUBICTO: + System.arraycopy(segmentCoords, 0, coords, coordsIndex, 6); + coordsIndex += 6; + isPolygonal = false; + break; + case PathIterator.SEG_CLOSE: + break; + } + ++rulesIndex; + } + + if ((rulesIndex != 0) && (rules[rulesIndex - 1] != PathIterator.SEG_CLOSE)) { + rules[rulesIndex] = PathIterator.SEG_CLOSE; + offsets[rulesIndex] = coordsSize; + } + + rulesSize = rulesIndex; + coordsSize = coordsIndex; + } + + /** + * Returns true if this area is polygonal. + */ + public boolean isPolygonal () { + return isPolygonal; + } + + /** + * Returns true if this area is rectangular. + */ + public boolean isRectangular () { + return (isPolygonal) && (rulesSize <= 5) && (coordsSize <= 8) && + (coords[1] == coords[3]) && (coords[7] == coords[5]) && + (coords[0] == coords[6]) && (coords[2] == coords[4]); + } + + /** + * Returns true if this area encloses only a single contiguous space. + */ + public boolean isSingular () { + return (moveToCount <= 1); + } + + /** + * Resets this area to empty. + */ + public void reset () { + coordsSize = 0; + rulesSize = 0; + } + + /** + * Transforms this area with the supplied transform. + */ + public void transform (AffineTransform t) { + copy(new Area(t.createTransformedShape(this)), this); + } + + /** + * Creates a new area equal to this area transformed by the supplied transform. + */ + public Area createTransformedArea (AffineTransform t) { + return new Area(t.createTransformedShape(this)); + } + + /** + * Adds the supplied area to this area. + */ + public void add (Area area) { + if (area == null || area.isEmpty()) { + return; + } else if (isEmpty()) { + copy(area, this); + return; + } + + if (isPolygonal() && area.isPolygonal()) { + addPolygon(area); + } else { + addCurvePolygon(area); + } + + if (getAreaBoundsSquare() < GeometryUtil.EPSILON) { + reset(); + } + } + + /** + * Intersects the supplied area with this area. + */ + public void intersect (Area area) { + if (area == null) { + return; + } else if (isEmpty() || area.isEmpty()) { + reset(); + return; + } + + if (isPolygonal() && area.isPolygonal()) { + intersectPolygon(area); + } else { + intersectCurvePolygon(area); + } + + if (getAreaBoundsSquare() < GeometryUtil.EPSILON) { + reset(); + } + } + + /** + * Subtracts the supplied area from this area. + */ + public void subtract (Area area) { + if (area == null || isEmpty() || area.isEmpty()) { + return; + } + + if (isPolygonal() && area.isPolygonal()) { + subtractPolygon(area); + } else { + subtractCurvePolygon(area); + } + + if (getAreaBoundsSquare() < GeometryUtil.EPSILON) { + reset(); + } + } + + /** + * Computes the exclusive or of this area and the supplied area and sets this area to the + * result. + */ + public void exclusiveOr (Area area) { + Area a = clone(); + a.intersect(area); + add(area); + subtract(a); + } + + @Override // from interface IShape + public boolean isEmpty () { + return (rulesSize == 0) && (coordsSize == 0); + } + + @Override // from interface IShape + public boolean contains (double x, double y) { + return !isEmpty() && containsExact(x, y) > 0; + } + + @Override // from interface IShape + public boolean contains (double x, double y, double width, double height) { + int crossCount = Crossing.intersectPath(getPathIterator(null), x, y, width, height); + return crossCount != Crossing.CROSSING && Crossing.isInsideEvenOdd(crossCount); + } + + @Override // from interface IShape + public boolean contains (IPoint p) { + return contains(p.getX(), p.getY()); + } + + @Override // from interface IShape + public boolean contains (IRectangle r) { + return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + @Override // from interface IShape + public boolean intersects (double x, double y, double width, double height) { + if ((width <= 0f) || (height <= 0f)) { + return false; + } else if (!getBounds().intersects(x, y, width, height)) { + return false; + } + int crossCount = Crossing.intersectShape(this, x, y, width, height); + return Crossing.isInsideEvenOdd(crossCount); + } + + @Override // from interface IShape + public boolean intersects (IRectangle r) { + return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + @Override // from interface IShape + public Rectangle getBounds () { + return getBounds(new Rectangle()); + } + + @Override // from interface IShape + public Rectangle getBounds (Rectangle target) { + double maxX = coords[0], maxY = coords[1]; + double minX = coords[0], minY = coords[1]; + for (int i = 0; i < coordsSize;) { + minX = Math.min(minX, coords[i]); + maxX = Math.max(maxX, coords[i++]); + minY = Math.min(minY, coords[i]); + maxY = Math.max(maxY, coords[i++]); + } + return new Rectangle(minX, minY, maxX - minX, maxY - minY); + } + + @Override // from interface IShape + public PathIterator getPathIterator (AffineTransform t) { + return new AreaPathIterator(t); + } + + @Override // from interface IShape + public PathIterator getPathIterator (AffineTransform t, double flatness) { + return new FlatteningPathIterator(getPathIterator(t), flatness); + } + + @Override // from Object + public boolean equals (Object obj) { + if (this == obj) { + return true; + } else if (!(obj instanceof Area)) { + return false; + } + Area area = clone(); + area.subtract((Area)obj); + return area.isEmpty(); + } + + @Override // from Cloneable + public Area clone () { + Area area = new Area(); + copy(this, area); + return area; + } + + private void addCurvePolygon (Area area) { + CurveCrossingHelper crossHelper = new CurveCrossingHelper( + new double[][] { coords, area.coords }, + new int[] { coordsSize, area.coordsSize }, + new int[][] { rules, area.rules }, + new int[] { rulesSize, area.rulesSize }, + new int[][] { offsets, area.offsets }); + IntersectPoint[] intersectPoints = crossHelper.findCrossing(); + + if (intersectPoints.length == 0) { + if (area.contains(getBounds())) { + copy(area, this); + } else if (!contains(area.getBounds())) { + coords = adjustSize(coords, coordsSize + area.coordsSize); + System.arraycopy(area.coords, 0, coords, coordsSize, area.coordsSize); + coordsSize += area.coordsSize; + rules = adjustSize(rules, rulesSize + area.rulesSize); + System.arraycopy(area.rules, 0, rules, rulesSize, area.rulesSize); + rulesSize += area.rulesSize; + offsets = adjustSize(offsets, rulesSize + area.rulesSize); + System.arraycopy(area.offsets, 0, offsets, rulesSize, area.rulesSize); + } + + return; + } + + double[] resultCoords = new double[coordsSize + area.coordsSize + intersectPoints.length]; + int[] resultRules = new int[rulesSize + area.rulesSize + intersectPoints.length]; + int[] resultOffsets = new int[rulesSize + area.rulesSize + intersectPoints.length]; + int resultCoordPos = 0; + int resultRulesPos = 0; + boolean isCurrentArea = true; + + IntersectPoint point = intersectPoints[0]; + resultRules[resultRulesPos] = PathIterator.SEG_MOVETO; + resultOffsets[resultRulesPos++] = resultCoordPos; + + do { + resultCoords[resultCoordPos++] = point.getX(); + resultCoords[resultCoordPos++] = point.getY(); + int curIndex = point.getEndIndex(true); + if (curIndex < 0) { + isCurrentArea = !isCurrentArea; + } else if (area.containsExact(coords[2 * curIndex], coords[2 * curIndex + 1]) > 0) { + isCurrentArea = false; + } else { + isCurrentArea = true; + } + + IntersectPoint nextPoint = getNextIntersectPoint(intersectPoints, point, isCurrentArea); + double[] coords = (isCurrentArea) ? this.coords : area.coords; + int[] offsets = (isCurrentArea) ? this.offsets : area.offsets; + int[] rules = (isCurrentArea) ? this.rules : area.rules; + int offset = point.getRuleIndex(isCurrentArea); + boolean isCopyUntilZero = false; + if ((point.getRuleIndex(isCurrentArea) > nextPoint.getRuleIndex(isCurrentArea))) { + int rulesSize = (isCurrentArea) ? this.rulesSize : area.rulesSize; + resultCoordPos = includeCoordsAndRules(offset + 1, rulesSize, rules, offsets, + resultRules, resultOffsets, resultCoords, coords, resultRulesPos, + resultCoordPos, point, isCurrentArea, false, 0); + resultRulesPos += rulesSize - offset - 1; + offset = 1; + isCopyUntilZero = true; + } + + int length = nextPoint.getRuleIndex(isCurrentArea) - offset + 1; + if (isCopyUntilZero) { + offset = 0; + } + + resultCoordPos = includeCoordsAndRules(offset, length, rules, offsets, resultRules, + resultOffsets, resultCoords, coords, resultRulesPos, resultCoordPos, point, + isCurrentArea, true, 0); + resultRulesPos += length - offset; + point = nextPoint; + } while (point != intersectPoints[0]); + + resultRules[resultRulesPos++] = PathIterator.SEG_CLOSE; + resultOffsets[resultRulesPos - 1] = resultCoordPos; + this.coords = resultCoords; + this.rules = resultRules; + this.offsets = resultOffsets; + this.coordsSize = resultCoordPos; + this.rulesSize = resultRulesPos; + } + + private void addPolygon (Area area) { + CrossingHelper crossHelper = new CrossingHelper( + new double[][] { coords, area.coords }, + new int[] { coordsSize, area.coordsSize }); + IntersectPoint[] intersectPoints = crossHelper.findCrossing(); + + if (intersectPoints.length == 0) { + if (area.contains(getBounds())) { + copy(area, this); + } else if (!contains(area.getBounds())) { + coords = adjustSize(coords, coordsSize + area.coordsSize); + System.arraycopy(area.coords, 0, coords, coordsSize, area.coordsSize); + coordsSize += area.coordsSize; + rules = adjustSize(rules, rulesSize + area.rulesSize); + System.arraycopy(area.rules, 0, rules, rulesSize, area.rulesSize); + rulesSize += area.rulesSize; + offsets = adjustSize(offsets, rulesSize + area.rulesSize); + System.arraycopy(area.offsets, 0, offsets, rulesSize, area.rulesSize); + } + return; + } + + double[] resultCoords = new double[coordsSize + area.coordsSize + intersectPoints.length]; + int[] resultRules = new int[rulesSize + area.rulesSize + intersectPoints.length]; + int[] resultOffsets = new int[rulesSize + area.rulesSize + intersectPoints.length]; + int resultCoordPos = 0; + int resultRulesPos = 0; + boolean isCurrentArea = true; + + IntersectPoint point = intersectPoints[0]; + resultRules[resultRulesPos] = PathIterator.SEG_MOVETO; + resultOffsets[resultRulesPos++] = resultCoordPos; + + do { + resultCoords[resultCoordPos++] = point.getX(); + resultCoords[resultCoordPos++] = point.getY(); + resultRules[resultRulesPos] = PathIterator.SEG_LINETO; + resultOffsets[resultRulesPos++] = resultCoordPos - 2; + int curIndex = point.getEndIndex(true); + if (curIndex < 0) { + isCurrentArea = !isCurrentArea; + } else if (area.containsExact(coords[2 * curIndex], coords[2 * curIndex + 1]) > 0) { + isCurrentArea = false; + } else { + isCurrentArea = true; + } + + IntersectPoint nextPoint = getNextIntersectPoint(intersectPoints, point, isCurrentArea); + double[] coords = (isCurrentArea) ? this.coords : area.coords; + int offset = 2 * point.getEndIndex(isCurrentArea); + if ((offset >= 0) && + (nextPoint.getBegIndex(isCurrentArea) < point.getEndIndex(isCurrentArea))) { + int coordSize = (isCurrentArea) ? this.coordsSize : area.coordsSize; + int length = coordSize - offset; + System.arraycopy(coords, offset, resultCoords, resultCoordPos, length); + + for (int i = 0; i < length / 2; i++) { + resultRules[resultRulesPos] = PathIterator.SEG_LINETO; + resultOffsets[resultRulesPos++] = resultCoordPos; + resultCoordPos += 2; + } + + offset = 0; + } + + if (offset >= 0) { + int length = 2 * nextPoint.getBegIndex(isCurrentArea) - offset + 2; + System.arraycopy(coords, offset, resultCoords, resultCoordPos, length); + + for (int i = 0; i < length / 2; i++) { + resultRules[resultRulesPos] = PathIterator.SEG_LINETO; + resultOffsets[resultRulesPos++] = resultCoordPos; + resultCoordPos += 2; + } + } + + point = nextPoint; + } while (point != intersectPoints[0]); + + resultRules[resultRulesPos - 1] = PathIterator.SEG_CLOSE; + resultOffsets[resultRulesPos - 1] = resultCoordPos; + coords = resultCoords; + rules = resultRules; + offsets = resultOffsets; + coordsSize = resultCoordPos; + rulesSize = resultRulesPos; + } + + private void intersectCurvePolygon (Area area) { + CurveCrossingHelper crossHelper = new CurveCrossingHelper( + new double[][] { coords, area.coords }, + new int[] { coordsSize, area.coordsSize }, + new int[][] { rules, area.rules }, + new int[] { rulesSize, area.rulesSize }, + new int[][] { offsets, area.offsets }); + IntersectPoint[] intersectPoints = crossHelper.findCrossing(); + if (intersectPoints.length == 0) { + if (contains(area.getBounds())) { + copy(area, this); + } else if (!area.contains(getBounds())) { + reset(); + } + return; + } + + double[] resultCoords = new double[coordsSize + area.coordsSize + intersectPoints.length]; + int[] resultRules = new int[rulesSize + area.rulesSize + intersectPoints.length]; + int[] resultOffsets = new int[rulesSize + area.rulesSize + intersectPoints.length]; + int resultCoordPos = 0; + int resultRulesPos = 0; + boolean isCurrentArea = true; + + IntersectPoint point = intersectPoints[0]; + IntersectPoint nextPoint = intersectPoints[0]; + resultRules[resultRulesPos] = PathIterator.SEG_MOVETO; + resultOffsets[resultRulesPos++] = resultCoordPos; + + do { + resultCoords[resultCoordPos++] = point.getX(); + resultCoords[resultCoordPos++] = point.getY(); + + int curIndex = point.getEndIndex(true); + if ((curIndex < 0) || + (area.containsExact(coords[2 * curIndex], coords[2 * curIndex + 1]) == 0)) { + isCurrentArea = !isCurrentArea; + } else if (area.containsExact(coords[2 * curIndex], coords[2 * curIndex + 1]) > 0) { + isCurrentArea = true; + } else { + isCurrentArea = false; + } + + nextPoint = getNextIntersectPoint(intersectPoints, point, isCurrentArea); + double[] coords = (isCurrentArea) ? this.coords : area.coords; + int[] offsets = (isCurrentArea) ? this.offsets : area.offsets; + int[] rules = (isCurrentArea) ? this.rules : area.rules; + int offset = point.getRuleIndex(isCurrentArea); + boolean isCopyUntilZero = false; + + if (point.getRuleIndex(isCurrentArea) > nextPoint.getRuleIndex(isCurrentArea)) { + int rulesSize = (isCurrentArea) ? this.rulesSize : area.rulesSize; + resultCoordPos = includeCoordsAndRules( + offset + 1, rulesSize, rules, offsets, resultRules, resultOffsets, + resultCoords, coords, resultRulesPos, resultCoordPos, point, isCurrentArea, + false, 1); + resultRulesPos += rulesSize - offset - 1; + offset = 1; + isCopyUntilZero = true; + } + + int length = nextPoint.getRuleIndex(isCurrentArea) - offset + 1; + + if (isCopyUntilZero) { + offset = 0; + isCopyUntilZero = false; + } + if ((length == offset) && + (nextPoint.getRule(isCurrentArea) != PathIterator.SEG_LINETO) && + (nextPoint.getRule(isCurrentArea) != PathIterator.SEG_CLOSE) && + (point.getRule(isCurrentArea) != PathIterator.SEG_LINETO) && + (point.getRule(isCurrentArea) != PathIterator.SEG_CLOSE)) { + isCopyUntilZero = true; + length++; + } + + resultCoordPos = includeCoordsAndRules( + offset, length, rules, offsets, resultRules, resultOffsets, resultCoords, coords, + resultRulesPos, resultCoordPos, nextPoint, isCurrentArea, true, 1); + resultRulesPos = ((length <= offset) || (isCopyUntilZero)) ? + resultRulesPos + 1 : resultRulesPos + length; + + point = nextPoint; + } while (point != intersectPoints[0]); + + if (resultRules[resultRulesPos - 1] == PathIterator.SEG_LINETO) { + resultRules[resultRulesPos - 1] = PathIterator.SEG_CLOSE; + } else { + resultCoords[resultCoordPos++] = nextPoint.getX(); + resultCoords[resultCoordPos++] = nextPoint.getY(); + resultRules[resultRulesPos++] = PathIterator.SEG_CLOSE; + } + + resultOffsets[resultRulesPos - 1] = resultCoordPos; + coords = resultCoords; + rules = resultRules; + offsets = resultOffsets; + coordsSize = resultCoordPos; + rulesSize = resultRulesPos; + } + + private void intersectPolygon (Area area) { + CrossingHelper crossHelper = new CrossingHelper( + new double[][] { coords, area.coords }, + new int[] { coordsSize, area.coordsSize }); + IntersectPoint[] intersectPoints = crossHelper.findCrossing(); + if (intersectPoints.length == 0) { + if (contains(area.getBounds())) { + copy(area, this); + } else if (!area.contains(getBounds())) { + reset(); + } + return; + } + + double[] resultCoords = new double[coordsSize + area.coordsSize + intersectPoints.length]; + int[] resultRules = new int[rulesSize + area.rulesSize + intersectPoints.length]; + int[] resultOffsets = new int[rulesSize + area.rulesSize + intersectPoints.length]; + int resultCoordPos = 0; + int resultRulesPos = 0; + boolean isCurrentArea = true; + + IntersectPoint point = intersectPoints[0]; + resultRules[resultRulesPos] = PathIterator.SEG_MOVETO; + resultOffsets[resultRulesPos++] = resultCoordPos; + + do { + resultCoords[resultCoordPos++] = point.getX(); + resultCoords[resultCoordPos++] = point.getY(); + resultRules[resultRulesPos] = PathIterator.SEG_LINETO; + resultOffsets[resultRulesPos++] = resultCoordPos - 2; + int curIndex = point.getEndIndex(true); + + if ((curIndex < 0) || + (area.containsExact(coords[2 * curIndex], coords[2 * curIndex + 1]) == 0)) { + isCurrentArea = !isCurrentArea; + } else if (area.containsExact(coords[2 * curIndex], coords[2 * curIndex + 1]) > 0) { + isCurrentArea = true; + } else { + isCurrentArea = false; + } + + IntersectPoint nextPoint = getNextIntersectPoint(intersectPoints, point, isCurrentArea); + double[] coords = (isCurrentArea) ? this.coords : area.coords; + int offset = 2 * point.getEndIndex(isCurrentArea); + if ((offset >= 0) && + (nextPoint.getBegIndex(isCurrentArea) < point.getEndIndex(isCurrentArea))) { + int coordSize = (isCurrentArea) ? this.coordsSize : area.coordsSize; + int length = coordSize - offset; + System.arraycopy(coords, offset, resultCoords, resultCoordPos, length); + + for (int i = 0; i < length / 2; i++) { + resultRules[resultRulesPos] = PathIterator.SEG_LINETO; + resultOffsets[resultRulesPos++] = resultCoordPos; + resultCoordPos += 2; + } + + offset = 0; + } + + if (offset >= 0) { + int length = 2 * nextPoint.getBegIndex(isCurrentArea) - offset + 2; + System.arraycopy(coords, offset, resultCoords, resultCoordPos, length); + + for (int i = 0; i < length / 2; i++) { + resultRules[resultRulesPos] = PathIterator.SEG_LINETO; + resultOffsets[resultRulesPos++] = resultCoordPos; + resultCoordPos += 2; + } + } + + point = nextPoint; + } while (point != intersectPoints[0]); + + resultRules[resultRulesPos - 1] = PathIterator.SEG_CLOSE; + resultOffsets[resultRulesPos - 1] = resultCoordPos; + coords = resultCoords; + rules = resultRules; + offsets = resultOffsets; + coordsSize = resultCoordPos; + rulesSize = resultRulesPos; + } + + private void subtractCurvePolygon (Area area) { + CurveCrossingHelper crossHelper = new CurveCrossingHelper( + new double[][] { coords, area.coords }, + new int[] { coordsSize, area.coordsSize }, + new int[][] { rules, area.rules }, + new int[] { rulesSize, area.rulesSize }, + new int[][] { offsets, area.offsets }); + IntersectPoint[] intersectPoints = crossHelper.findCrossing(); + if (intersectPoints.length == 0 && contains(area.getBounds())) { + copy(area, this); + return; + } + + double[] resultCoords = new double[coordsSize + area.coordsSize + intersectPoints.length]; + int[] resultRules = new int[rulesSize + area.rulesSize + intersectPoints.length]; + int[] resultOffsets = new int[rulesSize + area.rulesSize + intersectPoints.length]; + int resultCoordPos = 0; + int resultRulesPos = 0; + boolean isCurrentArea = true; + + IntersectPoint point = intersectPoints[0]; + resultRules[resultRulesPos] = PathIterator.SEG_MOVETO; + resultOffsets[resultRulesPos++] = resultCoordPos; + + do { + resultCoords[resultCoordPos++] = point.getX(); + resultCoords[resultCoordPos++] = point.getY(); + int curIndex = offsets[point.getRuleIndex(true)] % coordsSize; + if (area.containsExact(coords[curIndex], coords[curIndex + 1]) == 0) { + isCurrentArea = !isCurrentArea; + } else if (area.containsExact(coords[curIndex], coords[curIndex + 1]) > 0) { + isCurrentArea = false; + } else { + isCurrentArea = true; + } + + IntersectPoint nextPoint = (isCurrentArea) ? + getNextIntersectPoint(intersectPoints, point, isCurrentArea) : + getPrevIntersectPoint(intersectPoints, point, isCurrentArea); + double[] coords = (isCurrentArea) ? this.coords : area.coords; + int[] offsets = (isCurrentArea) ? this.offsets : area.offsets; + int[] rules = (isCurrentArea) ? this.rules : area.rules; + int offset = (isCurrentArea) ? point.getRuleIndex(isCurrentArea) : + nextPoint.getRuleIndex(isCurrentArea); + boolean isCopyUntilZero = false; + + if (((isCurrentArea) && + (point.getRuleIndex(isCurrentArea) > nextPoint.getRuleIndex(isCurrentArea))) || + ((!isCurrentArea) && + (nextPoint.getRuleIndex(isCurrentArea) > nextPoint.getRuleIndex(isCurrentArea)))) { + int rulesSize = (isCurrentArea) ? this.rulesSize : area.rulesSize; + resultCoordPos = includeCoordsAndRules( + offset + 1, rulesSize, rules, offsets, resultRules, resultOffsets, resultCoords, + coords, resultRulesPos, resultCoordPos, point, isCurrentArea, false, 2); + resultRulesPos += rulesSize - offset - 1; + offset = 1; + isCopyUntilZero = true; + } + + int length = nextPoint.getRuleIndex(isCurrentArea) - offset + 1; + + if (isCopyUntilZero) { + offset = 0; + isCopyUntilZero = false; + } + + resultCoordPos = includeCoordsAndRules( + offset, length, rules, offsets, resultRules, resultOffsets, resultCoords, coords, + resultRulesPos, resultCoordPos, point, isCurrentArea, true, 2); + + if ((length == offset) && + ((rules[offset] == PathIterator.SEG_QUADTO) || + (rules[offset] == PathIterator.SEG_CUBICTO))) { + resultRulesPos++; + } else { + resultRulesPos = (length < offset || isCopyUntilZero) ? + resultRulesPos + 1 : resultRulesPos + length - offset; + } + + point = nextPoint; + } while (point != intersectPoints[0]); + + resultRules[resultRulesPos++] = PathIterator.SEG_CLOSE; + resultOffsets[resultRulesPos - 1] = resultCoordPos; + coords = resultCoords; + rules = resultRules; + offsets = resultOffsets; + coordsSize = resultCoordPos; + rulesSize = resultRulesPos; + } + + private void subtractPolygon (Area area) { + CrossingHelper crossHelper = new CrossingHelper( + new double[][] { coords, area.coords }, + new int[] { coordsSize, area.coordsSize }); + IntersectPoint[] intersectPoints = crossHelper.findCrossing(); + if (intersectPoints.length == 0) { + if (contains(area.getBounds())) { + copy(area, this); + return; + } + return; + } + + double[] resultCoords = new double[ + 2 * (coordsSize + area.coordsSize + intersectPoints.length)]; + int[] resultRules = new int[2 * (rulesSize + area.rulesSize + intersectPoints.length)]; + int[] resultOffsets = new int[2 * (rulesSize + area.rulesSize + intersectPoints.length)]; + int resultCoordPos = 0; + int resultRulesPos = 0; + boolean isCurrentArea = true; + int countPoints = 0; + boolean curArea = false; + boolean addArea = false; + + IntersectPoint point = intersectPoints[0]; + resultRules[resultRulesPos] = PathIterator.SEG_MOVETO; + resultOffsets[resultRulesPos++] = resultCoordPos; + + do { + resultCoords[resultCoordPos++] = point.getX(); + resultCoords[resultCoordPos++] = point.getY(); + resultRules[resultRulesPos] = PathIterator.SEG_LINETO; + resultOffsets[resultRulesPos++] = resultCoordPos - 2; + int curIndex = point.getEndIndex(true); + + if ((curIndex < 0) || + (area.isVertex(coords[2 * curIndex], coords[2 * curIndex + 1]) && + crossHelper.containsPoint(new double[] { coords[2 * curIndex], + coords[2 * curIndex + 1] }) && + (coords[2 * curIndex] != point.getX() || + coords[2 * curIndex + 1] != point.getY()))) { + isCurrentArea = !isCurrentArea; + } else if (area.containsExact(coords[2 * curIndex], coords[2 * curIndex + 1]) > 0) { + isCurrentArea = false; + } else { + isCurrentArea = true; + } + + if (countPoints >= intersectPoints.length) { + isCurrentArea = !isCurrentArea; + } + + if (isCurrentArea) { + curArea = true; + } else { + addArea = true; + } + + IntersectPoint nextPoint = (isCurrentArea) ? + getNextIntersectPoint(intersectPoints, point, isCurrentArea) : + getPrevIntersectPoint(intersectPoints, point, isCurrentArea); + double[] coords = (isCurrentArea) ? this.coords : area.coords; + + int offset = (isCurrentArea) ? 2 * point.getEndIndex(isCurrentArea) : + 2 * nextPoint.getEndIndex(isCurrentArea); + + if ((offset > 0) && + (((isCurrentArea) && + (nextPoint.getBegIndex(isCurrentArea) < point.getEndIndex(isCurrentArea))) || + ((!isCurrentArea) && + (nextPoint.getEndIndex(isCurrentArea) < nextPoint.getBegIndex(isCurrentArea))))) { + + int coordSize = (isCurrentArea) ? this.coordsSize : area.coordsSize; + int length = coordSize - offset; + + if (isCurrentArea) { + System.arraycopy(coords, offset, resultCoords, resultCoordPos, length); + } else { + double[] temp = new double[length]; + System.arraycopy(coords, offset, temp, 0, length); + reverseCopy(temp); + System.arraycopy(temp, 0, resultCoords, resultCoordPos, length); + } + + for (int i = 0; i < length / 2; i++) { + resultRules[resultRulesPos] = PathIterator.SEG_LINETO; + resultOffsets[resultRulesPos++] = resultCoordPos; + resultCoordPos += 2; + } + + offset = 0; + } + + if (offset >= 0) { + int length = (isCurrentArea) ? + 2 * nextPoint.getBegIndex(isCurrentArea) - offset + 2 : + 2 * point.getBegIndex(isCurrentArea) - offset + 2; + + if (isCurrentArea) { + System.arraycopy(coords, offset, resultCoords, resultCoordPos, length); + } else { + double[] temp = new double[length]; + System.arraycopy(coords, offset, temp, 0, length); + reverseCopy(temp); + System.arraycopy(temp, 0, resultCoords, resultCoordPos, length); + } + + for (int i = 0; i < length / 2; i++) { + resultRules[resultRulesPos] = PathIterator.SEG_LINETO; + resultOffsets[resultRulesPos++] = resultCoordPos; + resultCoordPos += 2; + } + } + + point = nextPoint; + countPoints++; + } while (point != intersectPoints[0] || !(curArea && addArea)); + + resultRules[resultRulesPos - 1] = PathIterator.SEG_CLOSE; + resultOffsets[resultRulesPos - 1] = resultCoordPos; + coords = resultCoords; + rules = resultRules; + offsets = resultOffsets; + coordsSize = resultCoordPos; + rulesSize = resultRulesPos; + } + + private IntersectPoint getNextIntersectPoint (IntersectPoint[] iPoints, + IntersectPoint isectPoint, + boolean isCurrentArea) { + int endIndex = isectPoint.getEndIndex(isCurrentArea); + if (endIndex < 0) { + return iPoints[Math.abs(endIndex) - 1]; + } + + IntersectPoint firstIsectPoint = null; + IntersectPoint nextIsectPoint = null; + for (IntersectPoint point : iPoints) { + int begIndex = point.getBegIndex(isCurrentArea); + if (begIndex >= 0) { + if (firstIsectPoint == null) { + firstIsectPoint = point; + } else if (begIndex < firstIsectPoint.getBegIndex(isCurrentArea)) { + firstIsectPoint = point; + } + } + + if (endIndex <= begIndex) { + if (nextIsectPoint == null) { + nextIsectPoint = point; + } else if (begIndex < nextIsectPoint.getBegIndex(isCurrentArea)) { + nextIsectPoint = point; + } + } + } + + return (nextIsectPoint != null) ? nextIsectPoint : firstIsectPoint; + } + + private IntersectPoint getPrevIntersectPoint (IntersectPoint[] iPoints, + IntersectPoint isectPoint, + boolean isCurrentArea) { + int begIndex = isectPoint.getBegIndex(isCurrentArea); + if (begIndex < 0) { + return iPoints[Math.abs(begIndex) - 1]; + } + + IntersectPoint firstIsectPoint = null; + IntersectPoint predIsectPoint = null; + for (IntersectPoint point : iPoints) { + int endIndex = point.getEndIndex(isCurrentArea); + if (endIndex >= 0) { + if (firstIsectPoint == null) { + firstIsectPoint = point; + } else if (endIndex < firstIsectPoint.getEndIndex(isCurrentArea)) { + firstIsectPoint = point; + } + } + + if (endIndex <= begIndex) { + if (predIsectPoint == null) { + predIsectPoint = point; + } else if (endIndex > predIsectPoint.getEndIndex(isCurrentArea)) { + predIsectPoint = point; + } + } + } + + return (predIsectPoint != null) ? predIsectPoint : firstIsectPoint; + } + + private int includeCoordsAndRules ( + int offset, int length, int[] rules, int[] offsets, int[] resultRules, int[] resultOffsets, + double[] resultCoords, double[] coords, int resultRulesPos, int resultCoordPos, + IntersectPoint point, boolean isCurrentArea, boolean way, int operation) { + + double[] temp = new double[8 * length]; + int coordsCount = 0; + boolean isMoveIndex = true; + boolean isMoveLength = true; + boolean additional = false; + + if (length <= offset) { + for (int i = resultRulesPos; i < resultRulesPos + 1; i++) { + resultRules[i] = PathIterator.SEG_LINETO; + } + } else { + int j = resultRulesPos; + for (int i = offset; i < length; i++) { + resultRules[j++] = PathIterator.SEG_LINETO; + } + } + + if ((length == offset) && + ((rules[offset] == PathIterator.SEG_QUADTO) || + (rules[offset] == PathIterator.SEG_CUBICTO))) { + length++; + additional = true; + } + + for (int i = offset; i < length; i++) { + int index = offsets[i]; + if (!isMoveIndex) { + index -= 2; + } + + if (!isMoveLength) { + length++; + isMoveLength = true; + } + + switch (rules[i]) { + case PathIterator.SEG_MOVETO: + isMoveIndex = false; + isMoveLength = false; + break; + + case PathIterator.SEG_LINETO: + case PathIterator.SEG_CLOSE: + resultRules[resultRulesPos] = PathIterator.SEG_LINETO; + resultOffsets[resultRulesPos++] = resultCoordPos + 2; + boolean isLeft = CrossingHelper.compare( + coords[index], coords[index + 1], point.getX(), point.getY()) > 0; + if (way || !isLeft) { + temp[coordsCount++] = coords[index]; + temp[coordsCount++] = coords[index + 1]; + } + break; + + case PathIterator.SEG_QUADTO: + resultRules[resultRulesPos] = PathIterator.SEG_QUADTO; + resultOffsets[resultRulesPos++] = resultCoordPos + 4; + double[] coefs = new double[] { + coords[index - 2], coords[index - 1], + coords[index], coords[index + 1], coords[index + 2], coords[index + 3] }; + isLeft = CrossingHelper.compare( + coords[index - 2], coords[index - 1], point.getX(), point.getY()) > 0; + + if ((!additional) && (operation == 0 || operation == 2)) { + isLeft = !isLeft; + way = false; + } + GeometryUtil.subQuad(coefs, point.getParam(isCurrentArea), isLeft); + + if (way || isLeft) { + temp[coordsCount++] = coefs[2]; + temp[coordsCount++] = coefs[3]; + } else { + System.arraycopy(coefs, 2, temp, coordsCount, 4); + coordsCount += 4; + } + break; + + case PathIterator.SEG_CUBICTO: + resultRules[resultRulesPos] = PathIterator.SEG_CUBICTO; + resultOffsets[resultRulesPos++] = resultCoordPos + 6; + coefs = new double[] { coords[index - 2], coords[index - 1], coords[index], + coords[index + 1], coords[index + 2], coords[index + 3], + coords[index + 4], coords[index + 5] }; + isLeft = CrossingHelper.compare( + coords[index - 2], coords[index - 1], point.getX(), point.getY()) > 0; + GeometryUtil.subCubic(coefs, point.getParam(isCurrentArea), !isLeft); + + if (isLeft) { + System.arraycopy(coefs, 2, temp, coordsCount, 6); + coordsCount += 6; + } else { + System.arraycopy(coefs, 2, temp, coordsCount, 4); + coordsCount += 4; + } + break; + } + } + + if (operation == 2 && !isCurrentArea && coordsCount > 2) { + reverseCopy(temp); + System.arraycopy(temp, 0, resultCoords, resultCoordPos, coordsCount); + } else { + System.arraycopy(temp, 0, resultCoords, resultCoordPos, coordsCount); + } + + return (resultCoordPos + coordsCount); + } + + private void copy (Area src, Area dst) { + dst.coordsSize = src.coordsSize; + dst.coords = src.coords.clone(); + dst.rulesSize = src.rulesSize; + dst.rules = src.rules.clone(); + dst.moveToCount = src.moveToCount; + dst.offsets = src.offsets.clone(); + } + + private int containsExact (double x, double y) { + PathIterator pi = getPathIterator(null); + int crossCount = Crossing.crossPath(pi, x, y); + if (Crossing.isInsideEvenOdd(crossCount)) { + return 1; + } + + double[] segmentCoords = new double[6]; + double[] resultPoints = new double[6]; + int rule; + double curX = -1; + double curY = -1; + double moveX = -1; + double moveY = -1; + + for (pi = getPathIterator(null); !pi.isDone(); pi.next()) { + rule = pi.currentSegment(segmentCoords); + switch (rule) { + case PathIterator.SEG_MOVETO: + moveX = curX = segmentCoords[0]; + moveY = curY = segmentCoords[1]; + break; + + case PathIterator.SEG_LINETO: + if (GeometryUtil.intersectLines(curX, curY, segmentCoords[0], segmentCoords[1], x, + y, x, y, resultPoints) != 0) { + return 0; + } + curX = segmentCoords[0]; + curY = segmentCoords[1]; + break; + + case PathIterator.SEG_QUADTO: + if (GeometryUtil.intersectLineAndQuad( + x, y, x, y, curX, curY, segmentCoords[0], segmentCoords[1], + segmentCoords[2], segmentCoords[3], resultPoints) > 0) { + return 0; + } + curX = segmentCoords[2]; + curY = segmentCoords[3]; + break; + + case PathIterator.SEG_CUBICTO: + if (GeometryUtil.intersectLineAndCubic( + x, y, x, y, curX, curY, segmentCoords[0], segmentCoords[1], + segmentCoords[2], segmentCoords[3], segmentCoords[4], segmentCoords[5], + resultPoints) > 0) { + return 0; + } + curX = segmentCoords[4]; + curY = segmentCoords[5]; + break; + + case PathIterator.SEG_CLOSE: + if (GeometryUtil.intersectLines( + curX, curY, moveX, moveY, x, y, x, y, resultPoints) != 0) { + return 0; + } + curX = moveX; + curY = moveY; + break; + } + } + return -1; + } + + private void reverseCopy (double[] coords) { + double[] temp = new double[coords.length]; + System.arraycopy(coords, 0, temp, 0, coords.length); + for (int i = 0; i < coords.length;) { + coords[i] = temp[coords.length - i - 2]; + coords[i + 1] = temp[coords.length - i - 1]; + i = i + 2; + } + } + + private double getAreaBoundsSquare () { + Rectangle bounds = getBounds(); + return bounds.getHeight() * bounds.getWidth(); + } + + private boolean isVertex (double x, double y) { + for (int i = 0; i < coordsSize;) { + if (x == coords[i++] && y == coords[i++]) { + return true; + } + } + return false; + } + + // the method check up the array size and necessarily increases it. + private static double[] adjustSize (double[] array, int newSize) { + if (newSize <= array.length) { + return array; + } + double[] newArray = new double[2 * newSize]; + System.arraycopy(array, 0, newArray, 0, array.length); + return newArray; + } + + private static int[] adjustSize (int[] array, int newSize) { + if (newSize <= array.length) { + return array; + } + int[] newArray = new int[2 * newSize]; + System.arraycopy(array, 0, newArray, 0, array.length); + return newArray; + } + + // the internal class implements PathIterator + private class AreaPathIterator implements PathIterator + { + private final AffineTransform transform; + private int curRuleIndex = 0; + private int curCoordIndex = 0; + + AreaPathIterator (AffineTransform t) { + this.transform = t; + } + + @Override public int getWindingRule () { + return WIND_EVEN_ODD; + } + + @Override public boolean isDone () { + return curRuleIndex >= rulesSize; + } + + @Override public void next () { + switch (rules[curRuleIndex]) { + case PathIterator.SEG_MOVETO: + case PathIterator.SEG_LINETO: + curCoordIndex += 2; + break; + case PathIterator.SEG_QUADTO: + curCoordIndex += 4; + break; + case PathIterator.SEG_CUBICTO: + curCoordIndex += 6; + break; + } + curRuleIndex++; + } + + @Override @SuppressWarnings("fallthrough") + public int currentSegment (double[] c) { + if (isDone()) { + throw new NoSuchElementException("Iterator out of bounds"); + } + + int count = 0; + // the fallthrough below is on purpose + switch (rules[curRuleIndex]) { + case PathIterator.SEG_CUBICTO: + c[4] = coords[curCoordIndex + 4]; + c[5] = coords[curCoordIndex + 5]; + count = 1; + case PathIterator.SEG_QUADTO: + c[2] = coords[curCoordIndex + 2]; + c[3] = coords[curCoordIndex + 3]; + count += 1; + case PathIterator.SEG_MOVETO: + case PathIterator.SEG_LINETO: + c[0] = coords[curCoordIndex]; + c[1] = coords[curCoordIndex + 1]; + count += 1; + } + + if (transform != null) { + transform.transform(c, 0, c, 0, count); + } + + return rules[curRuleIndex]; + } + } + + /** The coordinates array of the shape vertices. */ + private double[] coords = new double[20]; + + /** The coordinates quantity. */ + private int coordsSize = 0; + + /** The rules array for the drawing of the shape edges. */ + private int[] rules = new int[10]; + + /** The rules quantity. */ + private int rulesSize = 0; + + /** offsets[i] - index in array of coords and i - index in array of rules. */ + private int[] offsets = new int[10]; + + /** The quantity of MOVETO rule occurences. */ + private int moveToCount = 0; + + /** True if the shape is polygonal. */ + private boolean isPolygonal = true; +} diff --git a/src/main/java/pythagoras/d/Crossing.java b/src/main/java/pythagoras/d/Crossing.java new file mode 100644 index 0000000..f0d401a --- /dev/null +++ b/src/main/java/pythagoras/d/Crossing.java @@ -0,0 +1,861 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * Internal utility methods for computing intersections and containment. + */ +class Crossing +{ + /** Return value indicating that a crossing was found. */ + public static final int CROSSING = 255; + + /** Return value indicating the crossing result is unknown. */ + public static final int UNKNOWN = 254; + + /** + * Solves quadratic equation + * + * @param eqn the coefficients of the equation + * @param res the roots of the equation + * @return a number of roots + */ + public static int solveQuad (double[] eqn, double[] res) { + double a = eqn[2]; + double b = eqn[1]; + double c = eqn[0]; + int rc = 0; + if (a == 0f) { + if (b == 0f) { + return -1; + } + res[rc++] = -c / b; + } else { + double d = b * b - 4f * a * c; + // d < 0f + if (d < 0f) { + return 0; + } + d = Math.sqrt(d); + res[rc++] = (-b + d) / (a * 2f); + // d != 0f + if (d != 0f) { + res[rc++] = (-b - d) / (a * 2f); + } + } + return fixRoots(res, rc); + } + + /** + * Solves cubic equation + * + * @param eqn the coefficients of the equation + * @param res the roots of the equation + * @return a number of roots + */ + public static int solveCubic (double[] eqn, double[] res) { + double d = eqn[3]; + if (d == 0) { + return solveQuad(eqn, res); + } + double a = eqn[2] / d; + double b = eqn[1] / d; + double c = eqn[0] / d; + int rc = 0; + + double Q = (a * a - 3f * b) / 9f; + double R = (2f * a * a * a - 9f * a * b + 27f * c) / 54f; + double Q3 = Q * Q * Q; + double R2 = R * R; + double n = -a / 3f; + + if (R2 < Q3) { + double t = Math.acos(R / Math.sqrt(Q3)) / 3f; + double p = 2f * Math.PI / 3f; + double m = -2f * Math.sqrt(Q); + res[rc++] = m * Math.cos(t) + n; + res[rc++] = m * Math.cos(t + p) + n; + res[rc++] = m * Math.cos(t - p) + n; + } else { + // Debug.println("R2 >= Q3 (" + R2 + "/" + Q3 + ")"); + double A = Math.pow(Math.abs(R) + Math.sqrt(R2 - Q3), 1f / 3f); + if (R > 0f) { + A = -A; + } + // if (A == 0f) { + if (-ROOT_DELTA < A && A < ROOT_DELTA) { + res[rc++] = n; + } else { + double B = Q / A; + res[rc++] = A + B + n; + // if (R2 == Q3) { + double delta = R2 - Q3; + if (-ROOT_DELTA < delta && delta < ROOT_DELTA) { + res[rc++] = -(A + B) / 2f + n; + } + } + + } + return fixRoots(res, rc); + } + + /** + * Excludes double roots. Roots are double if they lies enough close with each other. + * + * @param res the roots + * @param rc the roots count + * @return new roots count + */ + protected static int fixRoots (double[] res, int rc) { + int tc = 0; + for (int i = 0; i < rc; i++) { + out: { + for (int j = i + 1; j < rc; j++) { + if (isZero(res[i] - res[j])) { + break out; + } + } + res[tc++] = res[i]; + } + } + return tc; + } + + /** + * QuadCurve class provides basic functionality to find curve crossing and calculating bounds + */ + public static class QuadCurve + { + double ax, ay, bx, by; + double Ax, Ay, Bx, By; + + public QuadCurve (double x1, double y1, double cx, double cy, double x2, double y2) { + ax = x2 - x1; + ay = y2 - y1; + bx = cx - x1; + by = cy - y1; + + Bx = bx + bx; // Bx = 2f * bx + Ax = ax - Bx; // Ax = ax - 2f * bx + + By = by + by; // By = 2f * by + Ay = ay - By; // Ay = ay - 2f * by + } + + public int cross (double[] res, int rc, double py1, double py2) { + int cross = 0; + + for (int i = 0; i < rc; i++) { + double t = res[i]; + + // CURVE-OUTSIDE + if (t < -DELTA || t > 1 + DELTA) { + continue; + } + // CURVE-START + if (t < DELTA) { + if (py1 < 0f && (bx != 0f ? bx : ax - bx) < 0f) { + cross--; + } + continue; + } + // CURVE-END + if (t > 1 - DELTA) { + if (py1 < ay && (ax != bx ? ax - bx : bx) > 0f) { + cross++; + } + continue; + } + // CURVE-INSIDE + double ry = t * (t * Ay + By); + // ry = t * t * Ay + t * By + if (ry > py2) { + double rxt = t * Ax + bx; + // rxt = 2f * t * Ax + Bx = 2f * t * Ax + 2f * bx + if (rxt > -DELTA && rxt < DELTA) { + continue; + } + cross += rxt > 0f ? 1 : -1; + } + } // for + + return cross; + } + + public int solvePoint (double[] res, double px) { + double[] eqn = { -px, Bx, Ax }; + return solveQuad(eqn, res); + } + + public int solveExtreme (double[] res) { + int rc = 0; + if (Ax != 0f) { + res[rc++] = -Bx / (Ax + Ax); + } + if (Ay != 0f) { + res[rc++] = -By / (Ay + Ay); + } + return rc; + } + + public int addBound (double[] bound, int bc, double[] res, int rc, double minX, double maxX, + boolean changeId, int id) { + for (int i = 0; i < rc; i++) { + double t = res[i]; + if (t > -DELTA && t < 1 + DELTA) { + double rx = t * (t * Ax + Bx); + if (minX <= rx && rx <= maxX) { + bound[bc++] = t; + bound[bc++] = rx; + bound[bc++] = t * (t * Ay + By); + bound[bc++] = id; + if (changeId) { + id++; + } + } + } + } + return bc; + } + } + + /** CubicCurve helper for finding curve crossing and calculating bounds. */ + public static class CubicCurveH + { + double ax, ay, bx, by, cx, cy; + double Ax, Ay, Bx, By, Cx, Cy; + double Ax3, Bx2; + + public CubicCurveH (double x1, double y1, double cx1, double cy1, double cx2, double cy2, + double x2, double y2) { + ax = x2 - x1; + ay = y2 - y1; + bx = cx1 - x1; + by = cy1 - y1; + cx = cx2 - x1; + cy = cy2 - y1; + + Cx = bx + bx + bx; // Cx = 3f * bx + Bx = cx + cx + cx - Cx - Cx; // Bx = 3f * cx - 6f * bx + Ax = ax - Bx - Cx; // Ax = ax - 3f * cx + 3f * bx + + Cy = by + by + by; // Cy = 3f * by + By = cy + cy + cy - Cy - Cy; // By = 3f * cy - 6f * by + Ay = ay - By - Cy; // Ay = ay - 3f * cy + 3f * by + + Ax3 = Ax + Ax + Ax; + Bx2 = Bx + Bx; + } + + public int cross (double[] res, int rc, double py1, double py2) { + int cross = 0; + for (int i = 0; i < rc; i++) { + double t = res[i]; + + // CURVE-OUTSIDE + if (t < -DELTA || t > 1 + DELTA) { + continue; + } + // CURVE-START + if (t < DELTA) { + if (py1 < 0f && (bx != 0f ? bx : (cx != bx ? cx - bx : ax - cx)) < 0f) { + cross--; + } + continue; + } + // CURVE-END + if (t > 1 - DELTA) { + if (py1 < ay && (ax != cx ? ax - cx : (cx != bx ? cx - bx : bx)) > 0f) { + cross++; + } + continue; + } + // CURVE-INSIDE + double ry = t * (t * (t * Ay + By) + Cy); + // ry = t * t * t * Ay + t * t * By + t * Cy + if (ry > py2) { + double rxt = t * (t * Ax3 + Bx2) + Cx; + // rxt = 3f * t * t * Ax + 2f * t * Bx + Cx + if (rxt > -DELTA && rxt < DELTA) { + rxt = t * (Ax3 + Ax3) + Bx2; + // rxt = 6f * t * Ax + 2f * Bx + if (rxt < -DELTA || rxt > DELTA) { + // Inflection point + continue; + } + rxt = ax; + } + cross += rxt > 0f ? 1 : -1; + } + } // for + + return cross; + } + + public int solvePoint (double[] res, double px) { + double[] eqn = { -px, Cx, Bx, Ax }; + return solveCubic(eqn, res); + } + + public int solveExtremeX (double[] res) { + double[] eqn = { Cx, Bx2, Ax3 }; + return solveQuad(eqn, res); + } + + public int solveExtremeY (double[] res) { + double[] eqn = { Cy, By + By, Ay + Ay + Ay }; + return solveQuad(eqn, res); + } + + public int addBound (double[] bound, int bc, double[] res, int rc, double minX, double maxX, + boolean changeId, int id) { + for (int i = 0; i < rc; i++) { + double t = res[i]; + if (t > -DELTA && t < 1 + DELTA) { + double rx = t * (t * (t * Ax + Bx) + Cx); + if (minX <= rx && rx <= maxX) { + bound[bc++] = t; + bound[bc++] = rx; + bound[bc++] = t * (t * (t * Ay + By) + Cy); + bound[bc++] = id; + if (changeId) { + id++; + } + } + } + } + return bc; + } + } + + /** + * Returns how many times ray from point (x,y) cross line. + */ + public static int crossLine (double x1, double y1, double x2, double y2, double x, double y) { + // LEFT/RIGHT/UP/EMPTY + if ((x < x1 && x < x2) || (x > x1 && x > x2) || (y > y1 && y > y2) || (x1 == x2)) { + return 0; + } + + // DOWN + if (y < y1 && y < y2) { + } else { + // INSIDE + if ((y2 - y1) * (x - x1) / (x2 - x1) <= y - y1) { + // INSIDE-UP + return 0; + } + } + + // START + if (x == x1) { + return x1 < x2 ? 0 : -1; + } + + // END + if (x == x2) { + return x1 < x2 ? 1 : 0; + } + + // INSIDE-DOWN + return x1 < x2 ? 1 : -1; + } + + /** + * Returns how many times ray from point (x,y) cross quard curve + */ + public static int crossQuad (double x1, double y1, double cx, double cy, double x2, double y2, + double x, double y) { + // LEFT/RIGHT/UP/EMPTY + if ((x < x1 && x < cx && x < x2) || (x > x1 && x > cx && x > x2) + || (y > y1 && y > cy && y > y2) || (x1 == cx && cx == x2)) { + return 0; + } + + // DOWN + if (y < y1 && y < cy && y < y2 && x != x1 && x != x2) { + if (x1 < x2) { + return x1 < x && x < x2 ? 1 : 0; + } + return x2 < x && x < x1 ? -1 : 0; + } + + // INSIDE + QuadCurve c = new QuadCurve(x1, y1, cx, cy, x2, y2); + double px = x - x1, py = y - y1; + double[] res = new double[3]; + int rc = c.solvePoint(res, px); + return c.cross(res, rc, py, py); + } + + /** + * Returns how many times ray from point (x,y) cross cubic curve + */ + public static int crossCubic (double x1, double y1, double cx1, double cy1, double cx2, + double cy2, double x2, double y2, double x, double y) { + // LEFT/RIGHT/UP/EMPTY + if ((x < x1 && x < cx1 && x < cx2 && x < x2) || (x > x1 && x > cx1 && x > cx2 && x > x2) + || (y > y1 && y > cy1 && y > cy2 && y > y2) + || (x1 == cx1 && cx1 == cx2 && cx2 == x2)) { + return 0; + } + + // DOWN + if (y < y1 && y < cy1 && y < cy2 && y < y2 && x != x1 && x != x2) { + if (x1 < x2) { + return x1 < x && x < x2 ? 1 : 0; + } + return x2 < x && x < x1 ? -1 : 0; + } + + // INSIDE + CubicCurveH c = new CubicCurveH(x1, y1, cx1, cy1, cx2, cy2, x2, y2); + double px = x - x1, py = y - y1; + double[] res = new double[3]; + int rc = c.solvePoint(res, px); + return c.cross(res, rc, py, py); + } + + /** + * Returns how many times ray from point (x,y) cross path + */ + public static int crossPath (PathIterator p, double x, double y) { + int cross = 0; + double mx, my, cx, cy; + mx = my = cx = cy = 0f; + double[] coords = new double[6]; + + while (!p.isDone()) { + switch (p.currentSegment(coords)) { + case PathIterator.SEG_MOVETO: + if (cx != mx || cy != my) { + cross += crossLine(cx, cy, mx, my, x, y); + } + mx = cx = coords[0]; + my = cy = coords[1]; + break; + case PathIterator.SEG_LINETO: + cross += crossLine(cx, cy, cx = coords[0], cy = coords[1], x, y); + break; + case PathIterator.SEG_QUADTO: + cross += crossQuad(cx, cy, coords[0], coords[1], cx = coords[2], cy = coords[3], x, + y); + break; + case PathIterator.SEG_CUBICTO: + cross += crossCubic(cx, cy, coords[0], coords[1], coords[2], coords[3], + cx = coords[4], cy = coords[5], x, y); + break; + case PathIterator.SEG_CLOSE: + if (cy != my || cx != mx) { + cross += crossLine(cx, cy, cx = mx, cy = my, x, y); + } + break; + } + + // checks if the point (x,y) is the vertex of shape with PathIterator p + if (x == cx && y == cy) { + cross = 0; + cy = my; + break; + } + p.next(); + } + if (cy != my) { + cross += crossLine(cx, cy, mx, my, x, y); + } + return cross; + } + + /** + * Returns how many times a ray from point (x,y) crosses a shape. + */ + public static int crossShape (IShape s, double x, double y) { + if (!s.getBounds().contains(x, y)) { + return 0; + } + return crossPath(s.getPathIterator(null), x, y); + } + + /** + * Returns true if value is close enough to zero. + */ + public static boolean isZero (double val) { + return -DELTA < val && val < DELTA; + } + + /** + * Returns how many times rectangle stripe cross line or the are intersect + */ + public static int intersectLine (double x1, double y1, double x2, double y2, double rx1, + double ry1, double rx2, double ry2) { + // LEFT/RIGHT/UP + if ((rx2 < x1 && rx2 < x2) || (rx1 > x1 && rx1 > x2) || (ry1 > y1 && ry1 > y2)) { + return 0; + } + + // DOWN + if (ry2 < y1 && ry2 < y2) { + + } else { + // INSIDE + if (x1 == x2) { + return CROSSING; + } + + // Build bound + double bx1, bx2; + if (x1 < x2) { + bx1 = x1 < rx1 ? rx1 : x1; + bx2 = x2 < rx2 ? x2 : rx2; + } else { + bx1 = x2 < rx1 ? rx1 : x2; + bx2 = x1 < rx2 ? x1 : rx2; + } + double k = (y2 - y1) / (x2 - x1); + double by1 = k * (bx1 - x1) + y1; + double by2 = k * (bx2 - x1) + y1; + + // BOUND-UP + if (by1 < ry1 && by2 < ry1) { + return 0; + } + + // BOUND-DOWN + if (by1 > ry2 && by2 > ry2) { + } else { + return CROSSING; + } + } + + // EMPTY + if (x1 == x2) { + return 0; + } + + // CURVE-START + if (rx1 == x1) { + return x1 < x2 ? 0 : -1; + } + + // CURVE-END + if (rx1 == x2) { + return x1 < x2 ? 1 : 0; + } + + if (x1 < x2) { + return x1 < rx1 && rx1 < x2 ? 1 : 0; + } + return x2 < rx1 && rx1 < x1 ? -1 : 0; + } + + /** + * Returns how many times rectangle stripe cross quad curve or the are + * intersect + */ + public static int intersectQuad (double x1, double y1, double cx, double cy, double x2, + double y2, double rx1, double ry1, double rx2, double ry2) { + // LEFT/RIGHT/UP ------------------------------------------------------ + if ((rx2 < x1 && rx2 < cx && rx2 < x2) || (rx1 > x1 && rx1 > cx && rx1 > x2) || + (ry1 > y1 && ry1 > cy && ry1 > y2)) { + return 0; + } + + // DOWN --------------------------------------------------------------- + if (ry2 < y1 && ry2 < cy && ry2 < y2 && rx1 != x1 && rx1 != x2) { + if (x1 < x2) { + return x1 < rx1 && rx1 < x2 ? 1 : 0; + } + return x2 < rx1 && rx1 < x1 ? -1 : 0; + } + + // INSIDE ------------------------------------------------------------- + QuadCurve c = new QuadCurve(x1, y1, cx, cy, x2, y2); + double px1 = rx1 - x1; + double py1 = ry1 - y1; + double px2 = rx2 - x1; + double py2 = ry2 - y1; + + double[] res1 = new double[3]; + double[] res2 = new double[3]; + int rc1 = c.solvePoint(res1, px1); + int rc2 = c.solvePoint(res2, px2); + + // INSIDE-LEFT/RIGHT + if (rc1 == 0 && rc2 == 0) { + return 0; + } + + // Build bound -------------------------------------------------------- + double minX = px1 - DELTA; + double maxX = px2 + DELTA; + double[] bound = new double[28]; + int bc = 0; + // Add roots + bc = c.addBound(bound, bc, res1, rc1, minX, maxX, false, 0); + bc = c.addBound(bound, bc, res2, rc2, minX, maxX, false, 1); + // Add extremal points + rc2 = c.solveExtreme(res2); + bc = c.addBound(bound, bc, res2, rc2, minX, maxX, true, 2); + // Add start and end + if (rx1 < x1 && x1 < rx2) { + bound[bc++] = 0f; + bound[bc++] = 0f; + bound[bc++] = 0f; + bound[bc++] = 4; + } + if (rx1 < x2 && x2 < rx2) { + bound[bc++] = 1f; + bound[bc++] = c.ax; + bound[bc++] = c.ay; + bound[bc++] = 5; + } + // End build bound ---------------------------------------------------- + + int cross = crossBound(bound, bc, py1, py2); + if (cross != UNKNOWN) { + return cross; + } + return c.cross(res1, rc1, py1, py2); + } + + /** + * Returns how many times rectangle stripe cross cubic curve or the are + * intersect + */ + public static int intersectCubic (double x1, double y1, double cx1, double cy1, + double cx2, double cy2, double x2, double y2, + double rx1, double ry1, double rx2, double ry2) { + // LEFT/RIGHT/UP + if ((rx2 < x1 && rx2 < cx1 && rx2 < cx2 && rx2 < x2) + || (rx1 > x1 && rx1 > cx1 && rx1 > cx2 && rx1 > x2) + || (ry1 > y1 && ry1 > cy1 && ry1 > cy2 && ry1 > y2)) { + return 0; + } + + // DOWN + if (ry2 < y1 && ry2 < cy1 && ry2 < cy2 && ry2 < y2 && rx1 != x1 && rx1 != x2) { + if (x1 < x2) { + return x1 < rx1 && rx1 < x2 ? 1 : 0; + } + return x2 < rx1 && rx1 < x1 ? -1 : 0; + } + + // INSIDE + CubicCurveH c = new CubicCurveH(x1, y1, cx1, cy1, cx2, cy2, x2, y2); + double px1 = rx1 - x1; + double py1 = ry1 - y1; + double px2 = rx2 - x1; + double py2 = ry2 - y1; + + double[] res1 = new double[3]; + double[] res2 = new double[3]; + int rc1 = c.solvePoint(res1, px1); + int rc2 = c.solvePoint(res2, px2); + + // LEFT/RIGHT + if (rc1 == 0 && rc2 == 0) { + return 0; + } + + double minX = px1 - DELTA; + double maxX = px2 + DELTA; + + // Build bound -------------------------------------------------------- + double[] bound = new double[40]; + int bc = 0; + // Add roots + bc = c.addBound(bound, bc, res1, rc1, minX, maxX, false, 0); + bc = c.addBound(bound, bc, res2, rc2, minX, maxX, false, 1); + // Add extremal points + rc2 = c.solveExtremeX(res2); + bc = c.addBound(bound, bc, res2, rc2, minX, maxX, true, 2); + rc2 = c.solveExtremeY(res2); + bc = c.addBound(bound, bc, res2, rc2, minX, maxX, true, 4); + // Add start and end + if (rx1 < x1 && x1 < rx2) { + bound[bc++] = 0f; + bound[bc++] = 0f; + bound[bc++] = 0f; + bound[bc++] = 6; + } + if (rx1 < x2 && x2 < rx2) { + bound[bc++] = 1f; + bound[bc++] = c.ax; + bound[bc++] = c.ay; + bound[bc++] = 7; + } + // End build bound ---------------------------------------------------- + + int cross = crossBound(bound, bc, py1, py2); + if (cross != UNKNOWN) { + return cross; + } + return c.cross(res1, rc1, py1, py2); + } + + /** + * Returns how many times rectangle stripe cross path or the are intersect + */ + public static int intersectPath (PathIterator p, double x, double y, double w, double h) { + int cross = 0; + int count; + double mx, my, cx, cy; + mx = my = cx = cy = 0f; + double[] coords = new double[6]; + + double rx1 = x; + double ry1 = y; + double rx2 = x + w; + double ry2 = y + h; + + while (!p.isDone()) { + count = 0; + switch (p.currentSegment(coords)) { + case PathIterator.SEG_MOVETO: + if (cx != mx || cy != my) { + count = intersectLine(cx, cy, mx, my, rx1, ry1, rx2, ry2); + } + mx = cx = coords[0]; + my = cy = coords[1]; + break; + case PathIterator.SEG_LINETO: + count = intersectLine(cx, cy, cx = coords[0], cy = coords[1], rx1, ry1, rx2, ry2); + break; + case PathIterator.SEG_QUADTO: + count = intersectQuad(cx, cy, coords[0], coords[1], cx = coords[2], cy = coords[3], + rx1, ry1, rx2, ry2); + break; + case PathIterator.SEG_CUBICTO: + count = intersectCubic(cx, cy, coords[0], coords[1], coords[2], coords[3], + cx = coords[4], cy = coords[5], rx1, ry1, rx2, ry2); + break; + case PathIterator.SEG_CLOSE: + if (cy != my || cx != mx) { + count = intersectLine(cx, cy, mx, my, rx1, ry1, rx2, ry2); + } + cx = mx; + cy = my; + break; + } + if (count == CROSSING) { + return CROSSING; + } + cross += count; + p.next(); + } + if (cy != my) { + count = intersectLine(cx, cy, mx, my, rx1, ry1, rx2, ry2); + if (count == CROSSING) { + return CROSSING; + } + cross += count; + } + return cross; + } + + /** + * Returns how many times rectangle stripe cross shape or the are intersect + */ + public static int intersectShape (IShape s, double x, double y, double w, double h) { + if (!s.getBounds().intersects(x, y, w, h)) { + return 0; + } + return intersectPath(s.getPathIterator(null), x, y, w, h); + } + + /** + * Returns true if cross count correspond inside location for non zero path + * rule + */ + public static boolean isInsideNonZero (int cross) { + return cross != 0; + } + + /** + * Returns true if cross count correspond inside location for even-odd path + * rule + */ + public static boolean isInsideEvenOdd (int cross) { + return (cross & 1) != 0; + } + + /** + * Sorts a bound array. + */ + protected static void sortBound (double[] bound, int bc) { + for (int i = 0; i < bc - 4; i += 4) { + int k = i; + for (int j = i + 4; j < bc; j += 4) { + if (bound[k] > bound[j]) { + k = j; + } + } + if (k != i) { + double tmp = bound[i]; + bound[i] = bound[k]; + bound[k] = tmp; + tmp = bound[i + 1]; + bound[i + 1] = bound[k + 1]; + bound[k + 1] = tmp; + tmp = bound[i + 2]; + bound[i + 2] = bound[k + 2]; + bound[k + 2] = tmp; + tmp = bound[i + 3]; + bound[i + 3] = bound[k + 3]; + bound[k + 3] = tmp; + } + } + } + + /** + * Returns whether bounds intersect a rectangle or not. + */ + protected static int crossBound (double[] bound, int bc, double py1, double py2) { + // LEFT/RIGHT + if (bc == 0) { + return 0; + } + + // Check Y coordinate + int up = 0; + int down = 0; + for (int i = 2; i < bc; i += 4) { + if (bound[i] < py1) { + up++; + continue; + } + if (bound[i] > py2) { + down++; + continue; + } + return CROSSING; + } + + // UP + if (down == 0) { + return 0; + } + + if (up != 0) { + // bc >= 2 + sortBound(bound, bc); + boolean sign = bound[2] > py2; + for (int i = 6; i < bc; i += 4) { + boolean sign2 = bound[i] > py2; + if (sign != sign2 && bound[i + 1] != bound[i - 3]) { + return CROSSING; + } + sign = sign2; + } + } + return UNKNOWN; + } + + /** Allowable tolerance for bounds comparison */ + protected static final double DELTA = 1E-5f; + + /** If roots have distance less then ROOT_DELTA they are double */ + protected static final double ROOT_DELTA = 1E-10f; +} diff --git a/src/main/java/pythagoras/d/CrossingHelper.java b/src/main/java/pythagoras/d/CrossingHelper.java new file mode 100644 index 0000000..25f9298 --- /dev/null +++ b/src/main/java/pythagoras/d/CrossingHelper.java @@ -0,0 +1,291 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * An internal class used to compute crossings. + */ +class CrossingHelper +{ + private double[][] coords; + private int[] sizes; + private List isectPoints = new ArrayList(); + + public CrossingHelper (double[][] coords, int[] sizes) { + this.coords = coords; + this.sizes = sizes; + } + + public IntersectPoint[] findCrossing () { + int pointCount1 = sizes[0] / 2; + int pointCount2 = sizes[1] / 2; + int[] indices = new int[pointCount1 + pointCount2]; + for (int i = 0; i < pointCount1 + pointCount2; i++) { + indices[i] = i; + } + + sort(coords[0], pointCount1, coords[1], pointCount2, indices); + // the set for the shapes edges storing + List edges = new ArrayList(); + Edge edge; + int begIndex, endIndex; + int areaNumber; + + for (int i = 0; i < indices.length; i++) { + if (indices[i] < pointCount1) { + begIndex = indices[i]; + endIndex = indices[i] - 1; + if (endIndex < 0) { + endIndex = pointCount1 - 1; + } + areaNumber = 0; + + } else if (indices[i] < pointCount1 + pointCount2) { + begIndex = indices[i] - pointCount1; + endIndex = indices[i] - 1 - pointCount1; + if (endIndex < 0) { + endIndex = pointCount2 - 1; + } + areaNumber = 1; + + } else { + throw new IndexOutOfBoundsException(); + } + + if (!removeEdge(edges, begIndex, endIndex)) { + edge = new Edge(begIndex, endIndex, areaNumber); + intersectShape(edges, coords[0], pointCount1, coords[1], pointCount2, edge); + edges.add(edge); + } + + begIndex = indices[i]; + endIndex = indices[i] + 1; + + if ((begIndex < pointCount1) && (endIndex == pointCount1)) { + endIndex = 0; + } else if ((begIndex >= pointCount1) && (endIndex == (pointCount2 + pointCount1))) { + endIndex = pointCount1; + } + + if (endIndex < pointCount1) { + areaNumber = 0; + } else { + areaNumber = 1; + endIndex -= pointCount1; + begIndex -= pointCount1; + } + + if (!removeEdge(edges, begIndex, endIndex)) { + edge = new Edge(begIndex, endIndex, areaNumber); + intersectShape(edges, coords[0], pointCount1, coords[1], pointCount2, edge); + edges.add(edge); + } + } + + return isectPoints.toArray(new IntersectPoint[isectPoints.size()]); + } + + private boolean removeEdge (List edges, int begIndex, int endIndex) { + for (Edge edge : edges) { + if (edge.reverseCompare(begIndex, endIndex)) { + edges.remove(edge); + return true; + } + } + return false; + } + + // return the quantity of intersect points + private void intersectShape (List edges, double[] coords1, int length1, + double[] coords2, int length2, Edge initEdge) { + int areaOfEdge1, areaOfEdge2; + int initBegin, initEnd; + int addBegin, addEnd; + double x1, y1, x2, y2, x3, y3, x4, y4; + double[] point = new double[2]; + Edge edge; + + if (initEdge.areaNumber == 0) { + x1 = coords1[2 * initEdge.begIndex]; + y1 = coords1[2 * initEdge.begIndex + 1]; + x2 = coords1[2 * initEdge.endIndex]; + y2 = coords1[2 * initEdge.endIndex + 1]; + areaOfEdge1 = 0; + } else { + x1 = coords2[2 * initEdge.begIndex]; + y1 = coords2[2 * initEdge.begIndex + 1]; + x2 = coords2[2 * initEdge.endIndex]; + y2 = coords2[2 * initEdge.endIndex + 1]; + areaOfEdge1 = 1; + } + + for (Iterator iter = edges.iterator(); iter.hasNext();) { + edge = iter.next(); + + if (edge.areaNumber == 0) { + x3 = coords1[2 * edge.begIndex]; + y3 = coords1[2 * edge.begIndex + 1]; + x4 = coords1[2 * edge.endIndex]; + y4 = coords1[2 * edge.endIndex + 1]; + areaOfEdge2 = 0; + } else { + x3 = coords2[2 * edge.begIndex]; + y3 = coords2[2 * edge.begIndex + 1]; + x4 = coords2[2 * edge.endIndex]; + y4 = coords2[2 * edge.endIndex + 1]; + areaOfEdge2 = 1; + } + + if ((areaOfEdge1 != areaOfEdge2) && + (GeometryUtil.intersectLines(x1, y1, x2, y2, x3, y3, x4, y4, point) == 1) && + (!containsPoint(point))) { + + if (initEdge.areaNumber == 0) { + initBegin = initEdge.begIndex; + initEnd = initEdge.endIndex; + addBegin = edge.begIndex; + addEnd = edge.endIndex; + } else { + initBegin = edge.begIndex; + initEnd = edge.endIndex; + addBegin = initEdge.begIndex; + addEnd = initEdge.endIndex; + } + + if (((initEnd == length1 - 1) && (initBegin == 0 && initEnd > initBegin)) || + (((initEnd != length1 - 1) || (initBegin != 0)) && + ((initBegin != length1 - 1) || (initEnd != 0)) && (initBegin > initEnd))) { + int temp = initBegin; + initBegin = initEnd; + initEnd = temp; + } + + if (((addEnd == length2 - 1) && (addBegin == 0) && (addEnd > addBegin)) || + (((addEnd != length2 - 1) || (addBegin != 0)) && + ((addBegin != length2 - 1) || (addEnd != 0)) && (addBegin > addEnd))) { + int temp = addBegin; + addBegin = addEnd; + addEnd = temp; + } + + IntersectPoint ip; + for (Iterator i = isectPoints.iterator(); i.hasNext();) { + ip = i.next(); + if ((initBegin == ip.getBegIndex(true)) && (initEnd == ip.getEndIndex(true))) { + if (compare(ip.getX(), ip.getY(), point[0], point[1]) > 0) { + initEnd = -(isectPoints.indexOf(ip) + 1); + ip.setBegIndex1(-(isectPoints.size() + 1)); + } else { + initBegin = -(isectPoints.indexOf(ip) + 1); + ip.setEndIndex1(-(isectPoints.size() + 1)); + } + } + + if ((addBegin == ip.getBegIndex(false)) && (addEnd == ip.getEndIndex(false))) { + if (compare(ip.getX(), ip.getY(), point[0], point[1]) > 0) { + addEnd = -(isectPoints.indexOf(ip) + 1); + ip.setBegIndex2(-(isectPoints.size() + 1)); + } else { + addBegin = -(isectPoints.indexOf(ip) + 1); + ip.setEndIndex2(-(isectPoints.size() + 1)); + } + } + } + + isectPoints.add(new IntersectPoint(initBegin, initEnd, addBegin, addEnd, + point[0], point[1])); + } + } + } + + // the array sorting + private static void sort (double[] coords1, int length1, + double[] coords2, int length2, int[] array) { + int temp; + int length = length1 + length2; + double x1, y1, x2, y2; + + for (int i = 1; i < length; i++) { + if (array[i - 1] < length1) { + x1 = coords1[2 * array[i - 1]]; + y1 = coords1[2 * array[i - 1] + 1]; + } else { + x1 = coords2[2 * (array[i - 1] - length1)]; + y1 = coords2[2 * (array[i - 1] - length1) + 1]; + } + if (array[i] < length1) { + x2 = coords1[2 * array[i]]; + y2 = coords1[2 * array[i] + 1]; + } else { + x2 = coords2[2 * (array[i] - length1)]; + y2 = coords2[2 * (array[i] - length1) + 1]; + } + int j = i; + while (j > 0 && compare(x1, y1, x2, y2) <= 0) { + temp = array[j]; + array[j] = array[j - 1]; + array[j - 1] = temp; + j--; + if (j > 0) { + if (array[j - 1] < length1) { + x1 = coords1[2 * array[j - 1]]; + y1 = coords1[2 * array[j - 1] + 1]; + } else { + x1 = coords2[2 * (array[j - 1] - length1)]; + y1 = coords2[2 * (array[j - 1] - length1) + 1]; + } + if (array[j] < length1) { + x2 = coords1[2 * array[j]]; + y2 = coords1[2 * array[j] + 1]; + } else { + x2 = coords2[2 * (array[j] - length1)]; + y2 = coords2[2 * (array[j] - length1) + 1]; + } + } + } + } + } + + public boolean containsPoint (double[] point) { + IntersectPoint ipoint; + for (Iterator i = isectPoints.iterator(); i.hasNext();) { + ipoint = i.next(); + if (ipoint.getX() == point[0] && ipoint.getY() == point[1]) { + return true; + } + } + return false; + } + + public static int compare (double x1, double y1, double x2, double y2) { + if ((x1 < x2) || (x1 == x2 && y1 < y2)) { + return 1; + } else if (x1 == x2 && y1 == y2) { + return 0; + } + return -1; + } + + private static final class Edge + { + final int begIndex; + final int endIndex; + final int areaNumber; + + Edge (int begIndex, int endIndex, int areaNumber) { + this.begIndex = begIndex; + this.endIndex = endIndex; + this.areaNumber = areaNumber; + } + + boolean reverseCompare (int begIndex, int endIndex) { + return this.begIndex == endIndex && this.endIndex == begIndex; + } + } +} diff --git a/src/main/java/pythagoras/d/CubicCurve.java b/src/main/java/pythagoras/d/CubicCurve.java new file mode 100644 index 0000000..a27f93f --- /dev/null +++ b/src/main/java/pythagoras/d/CubicCurve.java @@ -0,0 +1,143 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +import java.io.Serializable; + +/** + * Represents a cubic curve. + */ +public class CubicCurve extends AbstractCubicCurve implements Serializable +{ + /** The x-coordinate of the start of this curve. */ + public double x1; + + /** The y-coordinate of the start of this curve. */ + public double y1; + + /** The x-coordinate of the first control point. */ + public double ctrlx1; + + /** The y-coordinate of the first control point. */ + public double ctrly1; + + /** The x-coordinate of the second control point. */ + public double ctrlx2; + + /** The x-coordinate of the second control point. */ + public double ctrly2; + + /** The x-coordinate of the end of this curve. */ + public double x2; + + /** The y-coordinate of the end of this curve. */ + public double y2; + + /** + * Creates a cubic curve with all points at (0,0). + */ + public CubicCurve () { + } + + /** + * Creates a cubic curve with the specified start, control, and end points. + */ + public CubicCurve (double x1, double y1, double ctrlx1, double ctrly1, + double ctrlx2, double ctrly2, double x2, double y2) { + setCurve(x1, y1, ctrlx1, ctrly1, ctrlx2, ctrly2, x2, y2); + } + + /** + * Configures the start, control and end points for this curve. + */ + public void setCurve (double x1, double y1, double ctrlx1, double ctrly1, double ctrlx2, + double ctrly2, double x2, double y2) { + this.x1 = x1; + this.y1 = y1; + this.ctrlx1 = ctrlx1; + this.ctrly1 = ctrly1; + this.ctrlx2 = ctrlx2; + this.ctrly2 = ctrly2; + this.x2 = x2; + this.y2 = y2; + } + + /** + * Configures the start, control and end points for this curve. + */ + public void setCurve (IPoint p1, IPoint cp1, IPoint cp2, IPoint p2) { + setCurve(p1.getX(), p1.getY(), cp1.getX(), cp1.getY(), + cp2.getX(), cp2.getY(), p2.getX(), p2.getY()); + } + + /** + * Configures the start, control and end points for this curve, using the values at the + * specified offset in the {@link coords} array. + */ + public void setCurve (double[] coords, int offset) { + setCurve(coords[offset + 0], coords[offset + 1], coords[offset + 2], coords[offset + 3], + coords[offset + 4], coords[offset + 5], coords[offset + 6], coords[offset + 7]); + } + + /** + * Configures the start, control and end points for this curve, using the values at the + * specified offset in the {@link points} array. + */ + public void setCurve (IPoint[] points, int offset) { + setCurve(points[offset + 0].getX(), points[offset + 0].getY(), + points[offset + 1].getX(), points[offset + 1].getY(), + points[offset + 2].getX(), points[offset + 2].getY(), + points[offset + 3].getX(), points[offset + 3].getY()); + } + + /** + * Configures the start, control and end points for this curve to be the same as the supplied + * curve. + */ + public void setCurve (ICubicCurve curve) { + setCurve(curve.getX1(), curve.getY1(), curve.getCtrlX1(), curve.getCtrlY1(), + curve.getCtrlX2(), curve.getCtrlY2(), curve.getX2(), curve.getY2()); + } + + @Override // from interface ICubicCurve + public double getX1 () { + return x1; + } + + @Override // from interface ICubicCurve + public double getY1 () { + return y1; + } + + @Override // from interface ICubicCurve + public double getCtrlX1 () { + return ctrlx1; + } + + @Override // from interface ICubicCurve + public double getCtrlY1 () { + return ctrly1; + } + + @Override // from interface ICubicCurve + public double getCtrlX2 () { + return ctrlx2; + } + + @Override // from interface ICubicCurve + public double getCtrlY2 () { + return ctrly2; + } + + @Override // from interface ICubicCurve + public double getX2 () { + return x2; + } + + @Override // from interface ICubicCurve + public double getY2 () { + return y2; + } +} diff --git a/src/main/java/pythagoras/d/CubicCurves.java b/src/main/java/pythagoras/d/CubicCurves.java new file mode 100644 index 0000000..f8e15e0 --- /dev/null +++ b/src/main/java/pythagoras/d/CubicCurves.java @@ -0,0 +1,101 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * Cubic curve-related utility methods. + */ +public class CubicCurves +{ + public static double getFlatnessSq (double x1, double y1, double ctrlx1, double ctrly1, + double ctrlx2, double ctrly2, double x2, double y2) { + return Math.max(Lines.pointSegDistSq(ctrlx1, ctrly1, x1, y1, x2, y2), + Lines.pointSegDistSq(ctrlx2, ctrly2, x1, y1, x2, y2)); + } + + public static double getFlatnessSq (double[] coords, int offset) { + return getFlatnessSq(coords[offset + 0], coords[offset + 1], coords[offset + 2], + coords[offset + 3], coords[offset + 4], coords[offset + 5], + coords[offset + 6], coords[offset + 7]); + } + + public static double getFlatness (double x1, double y1, double ctrlx1, double ctrly1, + double ctrlx2, double ctrly2, double x2, double y2) { + return Math.sqrt(getFlatnessSq(x1, y1, ctrlx1, ctrly1, ctrlx2, ctrly2, x2, y2)); + } + + public static double getFlatness (double[] coords, int offset) { + return getFlatness(coords[offset + 0], coords[offset + 1], coords[offset + 2], + coords[offset + 3], coords[offset + 4], coords[offset + 5], + coords[offset + 6], coords[offset + 7]); + } + + public static void subdivide (ICubicCurve src, CubicCurve left, CubicCurve right) { + double x1 = src.getX1(), y1 = src.getY1(); + double cx1 = src.getCtrlX1(), cy1 = src.getCtrlY1(); + double cx2 = src.getCtrlX2(), cy2 = src.getCtrlY2(); + double x2 = src.getX2(), y2 = src.getY2(); + double cx = (cx1 + cx2) / 2f, cy = (cy1 + cy2) / 2f; + cx1 = (x1 + cx1) / 2f; + cy1 = (y1 + cy1) / 2f; + cx2 = (x2 + cx2) / 2f; + cy2 = (y2 + cy2) / 2f; + double ax = (cx1 + cx) / 2f, ay = (cy1 + cy) / 2f; + double bx = (cx2 + cx) / 2f, by = (cy2 + cy) / 2f; + cx = (ax + bx) / 2f; + cy = (ay + by) / 2f; + if (left != null) { + left.setCurve(x1, y1, cx1, cy1, ax, ay, cx, cy); + } + if (right != null) { + right.setCurve(cx, cy, bx, by, cx2, cy2, x2, y2); + } + } + + public static void subdivide (double[] src, int srcOff, double left[], int leftOff, + double[] right, int rightOff) { + double x1 = src[srcOff + 0], y1 = src[srcOff + 1]; + double cx1 = src[srcOff + 2], cy1 = src[srcOff + 3]; + double cx2 = src[srcOff + 4], cy2 = src[srcOff + 5]; + double x2 = src[srcOff + 6], y2 = src[srcOff + 7]; + double cx = (cx1 + cx2) / 2f, cy = (cy1 + cy2) / 2f; + cx1 = (x1 + cx1) / 2f; + cy1 = (y1 + cy1) / 2f; + cx2 = (x2 + cx2) / 2f; + cy2 = (y2 + cy2) / 2f; + double ax = (cx1 + cx) / 2f, ay = (cy1 + cy) / 2f; + double bx = (cx2 + cx) / 2f, by = (cy2 + cy) / 2f; + cx = (ax + bx) / 2f; + cy = (ay + by) / 2f; + if (left != null) { + left[leftOff + 0] = x1; + left[leftOff + 1] = y1; + left[leftOff + 2] = cx1; + left[leftOff + 3] = cy1; + left[leftOff + 4] = ax; + left[leftOff + 5] = ay; + left[leftOff + 6] = cx; + left[leftOff + 7] = cy; + } + if (right != null) { + right[rightOff + 0] = cx; + right[rightOff + 1] = cy; + right[rightOff + 2] = bx; + right[rightOff + 3] = by; + right[rightOff + 4] = cx2; + right[rightOff + 5] = cy2; + right[rightOff + 6] = x2; + right[rightOff + 7] = y2; + } + } + + public static int solveCubic (double[] eqn) { + return solveCubic(eqn, eqn); + } + + public static int solveCubic (double[] eqn, double[] res) { + return Crossing.solveCubic(eqn, res); + } +} diff --git a/src/main/java/pythagoras/d/CurveCrossingHelper.java b/src/main/java/pythagoras/d/CurveCrossingHelper.java new file mode 100644 index 0000000..da011b7 --- /dev/null +++ b/src/main/java/pythagoras/d/CurveCrossingHelper.java @@ -0,0 +1,273 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +import java.util.Iterator; +import java.util.List; +import java.util.ArrayList; + +/** + * An internal class used to compute crossings. + */ +class CurveCrossingHelper +{ + private double[][] coords; + private int[][] rules; + private int[] sizes; + private int[] rulesSizes; + private int[][] offsets; + private List isectPoints = new ArrayList(); + + public CurveCrossingHelper (double[][] coords, int[] sizes, + int[][] rules, int[] rulesSizes, int[][] offsets) { + this.coords = coords; + this.rules = rules; + this.sizes = sizes; + this.rulesSizes = rulesSizes; + this.offsets = offsets; + } + + public IntersectPoint[] findCrossing () { + double[] edge1 = new double[8]; + double[] edge2 = new double[8]; + double[] points = new double[6]; + double[] params = new double[6]; + double[] mp1 = new double[2]; + double[] cp1 = new double[2]; + double[] mp2 = new double[2]; + double[] cp2 = new double[2]; + int rule1, rule2, endIndex1, endIndex2; + int ipCount = 0; + + for (int i = 0; i < rulesSizes[0]; i++) { + rule1 = rules[0][i]; + endIndex1 = getCurrentEdge(0, i, edge1, mp1, cp1); + for (int j = 0; j < rulesSizes[1]; j++) { + ipCount = 0; + rule2 = rules[1][j]; + endIndex2 = getCurrentEdge(1, j, edge2, mp2, cp2); + if (((rule1 == PathIterator.SEG_LINETO) || (rule1 == PathIterator.SEG_CLOSE)) && + ((rule2 == PathIterator.SEG_LINETO) || (rule2 == PathIterator.SEG_CLOSE))) { + ipCount = GeometryUtil.intersectLinesWithParams( + edge1[0], edge1[1], edge1[2], edge1[3], + edge2[0], edge2[1], edge2[2], edge2[3], params); + + if (ipCount != 0) { + points[0] = GeometryUtil.line(params[0], edge1[0], edge1[2]); + points[1] = GeometryUtil.line(params[0], edge1[1], edge1[3]); + } + + } else if (((rule1 == PathIterator.SEG_LINETO) || + (rule1 == PathIterator.SEG_CLOSE)) && + (rule2 == PathIterator.SEG_QUADTO)) { + ipCount = GeometryUtil.intersectLineAndQuad( + edge1[0], edge1[1], edge1[2], edge1[3], + edge2[0], edge2[1], edge2[2], edge2[3], edge2[4], edge2[5], params); + for (int k = 0; k < ipCount; k++) { + points[2 * k] = GeometryUtil.line(params[2 * k], edge1[0], edge1[2]); + points[2 * k + 1] = GeometryUtil.line(params[2 * k], edge1[1], edge1[3]); + } + + } else if (rule1 == PathIterator.SEG_QUADTO && + (rule2 == PathIterator.SEG_LINETO || rule2 == PathIterator.SEG_CLOSE)) { + ipCount = GeometryUtil.intersectLineAndQuad( + edge2[0], edge2[1], edge2[2], edge2[3], + edge1[0], edge1[1], edge1[2], edge1[3], edge1[4], edge1[5], params); + for (int k = 0; k < ipCount; k++) { + points[2 * k] = GeometryUtil.line(params[2 * k + 1], edge2[0], edge2[2]); + points[2 * k + 1] = GeometryUtil.line( + params[2 * k + 1], edge2[1], edge2[3]); + } + + } else if ((rule1 == PathIterator.SEG_CUBICTO) && + ((rule2 == PathIterator.SEG_LINETO) || + (rule2 == PathIterator.SEG_CLOSE))) { + ipCount = GeometryUtil.intersectLineAndCubic( + edge1[0], edge1[1], edge1[2], edge1[3], edge1[4], edge1[5], edge1[6], + edge1[7], edge2[0], edge2[1], edge2[2], edge2[3], params); + for (int k = 0; k < ipCount; k++) { + points[2 * k] = GeometryUtil.line(params[2 * k + 1], edge2[0], edge2[2]); + points[2 * k + 1] = GeometryUtil.line( + params[2 * k + 1], edge2[1], edge2[3]); + } + + } else if (((rule1 == PathIterator.SEG_LINETO) || + (rule1 == PathIterator.SEG_CLOSE)) && + (rule2 == PathIterator.SEG_CUBICTO)) { + ipCount = GeometryUtil.intersectLineAndCubic( + edge1[0], edge1[1], edge1[2], edge1[3], edge2[0], edge2[1], + edge2[2], edge2[3], edge2[4], edge2[5], edge2[6], edge2[7], params); + for (int k = 0; k < ipCount; k++) { + points[2 * k] = GeometryUtil.line(params[2 * k], edge1[0], edge1[2]); + points[2 * k + 1] = GeometryUtil.line(params[2 * k], edge1[1], edge1[3]); + } + + } else if ((rule1 == PathIterator.SEG_QUADTO) && + (rule2 == PathIterator.SEG_QUADTO)) { + ipCount = GeometryUtil.intersectQuads( + edge1[0], edge1[1], edge1[2], edge1[3], edge1[4], edge1[5], + edge2[0], edge2[1], edge2[2], edge2[3], edge2[4], edge2[5], params); + for (int k = 0; k < ipCount; k++) { + points[2 * k] = GeometryUtil.quad( + params[2 * k], edge1[0], edge1[2], edge1[4]); + points[2 * k + 1] = GeometryUtil.quad( + params[2 * k], edge1[1], edge1[3], edge1[5]); + } + + } else if ((rule1 == PathIterator.SEG_QUADTO) && + (rule2 == PathIterator.SEG_CUBICTO)) { + ipCount = GeometryUtil.intersectQuadAndCubic( + edge1[0], edge1[1], edge1[2], edge1[3], edge1[4], edge1[5], + edge2[0], edge2[1], edge2[2], edge2[3], edge2[4], edge2[5], + edge2[6], edge2[7], params); + for (int k = 0; k < ipCount; k++) { + points[2 * k] = GeometryUtil.quad( + params[2 * k], edge1[0], edge1[2], edge1[4]); + points[2 * k + 1] = GeometryUtil.quad( + params[2 * k], edge1[1], edge1[3], edge1[5]); + } + + } else if ((rule1 == PathIterator.SEG_CUBICTO) && + (rule2 == PathIterator.SEG_QUADTO)) { + ipCount = GeometryUtil.intersectQuadAndCubic( + edge2[0], edge2[1], edge2[2], edge2[3], edge2[4], edge2[5], + edge1[0], edge1[1], edge1[2], edge1[3], edge1[4], edge1[5], + edge2[6], edge2[7], params); + for (int k = 0; k < ipCount; k++) { + points[2 * k] = GeometryUtil.quad( + params[2 * k + 1], edge2[0], edge2[2], edge2[4]); + points[2 * k + 1] = GeometryUtil.quad( + params[2 * k + 1], edge2[1], edge2[3], edge2[5]); + } + + } else if ((rule1 == PathIterator.SEG_CUBICTO) && + (rule2 == PathIterator.SEG_CUBICTO)) { + ipCount = GeometryUtil.intersectCubics( + edge1[0], edge1[1], edge1[2], edge1[3], edge1[4], edge1[5], edge1[6], + edge1[7], edge2[0], edge2[1], edge2[2], edge2[3], edge2[4], edge2[5], + edge2[6], edge2[7], params); + for (int k = 0; k < ipCount; k++) { + points[2 * k] = GeometryUtil.cubic( + params[2 * k], edge1[0], edge1[2], edge1[4], edge1[6]); + points[2 * k + 1] = GeometryUtil.cubic( + params[2 * k], edge1[1], edge1[3], edge1[5], edge1[7]); + } + } + + endIndex1 = i; + endIndex2 = j; + int begIndex1 = i - 1; + int begIndex2 = j - 1; + + for (int k = 0; k < ipCount; k++) { + IntersectPoint ip = null; + if (!containsPoint(points[2 * k], points[2 * k + 1])) { + for (Iterator iter = isectPoints.iterator(); + iter.hasNext();) { + ip = iter.next(); + if ((begIndex1 == ip.getBegIndex(true)) && + (endIndex1 == ip.getEndIndex(true))) { + if (ip.getParam(true) > params[2 * k]) { + endIndex1 = -(isectPoints.indexOf(ip) + 1); + ip.setBegIndex1(-(isectPoints.size() + 1)); + } else { + begIndex1 = -(isectPoints.indexOf(ip) + 1); + ip.setEndIndex1(-(isectPoints.size() + 1)); + } + } + + if ((begIndex2 == ip.getBegIndex(false)) && + (endIndex2 == ip.getEndIndex(false))) { + if (ip.getParam(false) > params[2 * k + 1]) { + endIndex2 = -(isectPoints.indexOf(ip) + 1); + ip.setBegIndex2(-(isectPoints.size() + 1)); + } else { + begIndex2 = -(isectPoints.indexOf(ip) + 1); + ip.setEndIndex2(-(isectPoints.size() + 1)); + } + } + } + + if (rule1 == PathIterator.SEG_CLOSE) { + rule1 = PathIterator.SEG_LINETO; + } + + if (rule2 == PathIterator.SEG_CLOSE) { + rule2 = PathIterator.SEG_LINETO; + } + + isectPoints.add(new IntersectPoint( + begIndex1, endIndex1, rule1, i, begIndex2, endIndex2, + rule2, j, points[2 * k], points[2 * k + 1], + params[2 * k], params[2 * k + 1])); + } + } + } + } + return isectPoints.toArray(new IntersectPoint[isectPoints.size()]); + } + + private int getCurrentEdge (int areaIndex, int index, double[] c, double[] mp, double[] cp) { + int endIndex = 0; + + switch (rules[areaIndex][index]) { + case PathIterator.SEG_MOVETO: + cp[0] = mp[0] = coords[areaIndex][offsets[areaIndex][index]]; + cp[1] = mp[1] = coords[areaIndex][offsets[areaIndex][index] + 1]; + break; + case PathIterator.SEG_LINETO: + c[0] = cp[0]; + c[1] = cp[1]; + cp[0] = c[2] = coords[areaIndex][offsets[areaIndex][index]]; + cp[1] = c[3] = coords[areaIndex][offsets[areaIndex][index] + 1]; + endIndex = 0; + break; + case PathIterator.SEG_QUADTO: + c[0] = cp[0]; + c[1] = cp[1]; + c[2] = coords[areaIndex][offsets[areaIndex][index]]; + c[3] = coords[areaIndex][offsets[areaIndex][index] + 1]; + cp[0] = c[4] = coords[areaIndex][offsets[areaIndex][index] + 2]; + cp[1] = c[5] = coords[areaIndex][offsets[areaIndex][index] + 3]; + endIndex = 2; + break; + case PathIterator.SEG_CUBICTO: + c[0] = cp[0]; + c[1] = cp[1]; + c[2] = coords[areaIndex][offsets[areaIndex][index]]; + c[3] = coords[areaIndex][offsets[areaIndex][index] + 1]; + c[4] = coords[areaIndex][offsets[areaIndex][index] + 2]; + c[5] = coords[areaIndex][offsets[areaIndex][index] + 3]; + cp[0] = c[6] = coords[areaIndex][offsets[areaIndex][index] + 4]; + cp[1] = c[7] = coords[areaIndex][offsets[areaIndex][index] + 5]; + endIndex = 4; + break; + case PathIterator.SEG_CLOSE: + c[0] = cp[0]; + c[1] = cp[1]; + cp[0] = c[2] = mp[0]; + cp[1] = c[3] = mp[1]; + if (offsets[areaIndex][index] >= sizes[areaIndex]) { + endIndex = -sizes[areaIndex]; + } else { + endIndex = 0; + } + break; + } + return offsets[areaIndex][index] + endIndex; + } + + private boolean containsPoint (double x, double y) { + IntersectPoint ipoint; + for (Iterator i = isectPoints.iterator(); i.hasNext();) { + ipoint = i.next(); + if ((Math.abs(ipoint.getX() - x) < Math.pow(10, -6)) && + (Math.abs(ipoint.getY() - y) < Math.pow(10, -6))) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/pythagoras/d/Dimension.java b/src/main/java/pythagoras/d/Dimension.java new file mode 100644 index 0000000..f8d39e0 --- /dev/null +++ b/src/main/java/pythagoras/d/Dimension.java @@ -0,0 +1,65 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +import java.io.Serializable; + +/** + * Represents a magnitude in two dimensions. + */ +public class Dimension extends AbstractDimension implements Serializable +{ + /** The magnitude in the x-dimension. */ + public double width; + + /** The magnitude in the y-dimension. */ + public double height; + + /** + * Creates a dimension with magnitude (0, 0). + */ + public Dimension () { + this(0, 0); + } + + /** + * Creates a dimension with the specified width and height. + */ + public Dimension (double width, double height) { + setSize(width, height); + } + + /** + * Creates a dimension with width and height equal to the supplied dimension. + */ + public Dimension (IDimension d) { + this(d.getWidth(), d.getHeight()); + } + + /** + * Sets the magnitudes of this dimension to the specified width and height. + */ + public void setSize (double width, double height) { + this.width = width; + this.height = height; + } + + /** + * Sets the magnitudes of this dimension to be equal to the supplied dimension. + */ + public void setSize (IDimension d) { + setSize(d.getWidth(), d.getHeight()); + } + + @Override // from interface IDimension + public double getWidth () { + return width; + } + + @Override // from interface IDimension + public double getHeight () { + return height; + } +} diff --git a/src/main/java/pythagoras/d/Dimensions.java b/src/main/java/pythagoras/d/Dimensions.java new file mode 100644 index 0000000..b52cc45 --- /dev/null +++ b/src/main/java/pythagoras/d/Dimensions.java @@ -0,0 +1,18 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * Dimension-related utility methods. + */ +public class Dimensions +{ + /** + * Returns a string describing the supplied dimension, of the form widthxheight. + */ + public static String dimenToString (double width, double height) { + return width + "x" + height; + } +} diff --git a/src/main/java/pythagoras/d/Ellipse.java b/src/main/java/pythagoras/d/Ellipse.java new file mode 100644 index 0000000..b42937f --- /dev/null +++ b/src/main/java/pythagoras/d/Ellipse.java @@ -0,0 +1,66 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +import java.io.Serializable; + +/** + * Represents an ellipse that is described by a framing rectangle. + */ +public class Ellipse extends AbstractEllipse implements Serializable +{ + /** The x-coordinate of the framing rectangle. */ + public double x; + + /** The y-coordinate of the framing rectangle. */ + public double y; + + /** The width of the framing rectangle. */ + public double width; + + /** The height of the framing rectangle. */ + public double height; + + /** + * Creates an ellipse with framing rectangle (0x0+0+0). + */ + public Ellipse () { + } + + /** + * Creates an ellipse with the specified framing rectangle. + */ + public Ellipse (double x, double y, double width, double height) { + setFrame(x, y, width, height); + } + + @Override // from interface IRectangularShape + public double getX () { + return x; + } + + @Override // from interface IRectangularShape + public double getY () { + return y; + } + + @Override // from interface IRectangularShape + public double getWidth () { + return width; + } + + @Override // from interface IRectangularShape + public double getHeight () { + return height; + } + + @Override // from RectangularShape + public void setFrame (double x, double y, double width, double height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } +} diff --git a/src/main/java/pythagoras/d/FlatteningPathIterator.java b/src/main/java/pythagoras/d/FlatteningPathIterator.java new file mode 100644 index 0000000..041c787 --- /dev/null +++ b/src/main/java/pythagoras/d/FlatteningPathIterator.java @@ -0,0 +1,233 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +import java.util.NoSuchElementException; + +/** + * A path iterator that flattens curves. + */ +class FlatteningPathIterator implements PathIterator +{ + public FlatteningPathIterator (PathIterator path, double flatness) { + this(path, flatness, BUFFER_LIMIT); + } + + public FlatteningPathIterator (PathIterator path, double flatness, int limit) { + if (flatness < 0) { + throw new IllegalArgumentException("Flatness is less then zero"); + } + if (limit < 0) { + throw new IllegalArgumentException("Limit is less then zero"); + } + if (path == null) { + throw new NullPointerException("Path is null"); + } + this.p = path; + this.flatness = flatness; + this.flatness2 = flatness * flatness; + this.bufLimit = limit; + this.bufSize = Math.min(bufLimit, BUFFER_SIZE); + this.buf = new double[bufSize]; + this.bufIndex = bufSize; + } + + public double getFlatness () { + return flatness; + } + + public int getRecursionLimit () { + return bufLimit; + } + + @Override + // from interface PathIterator + public int getWindingRule () { + return p.getWindingRule(); + } + + @Override + // from interface PathIterator + public boolean isDone () { + return bufEmpty && p.isDone(); + } + + @Override + // from interface PathIterator + public void next () { + if (bufEmpty) { + p.next(); + } + } + + @Override + // from interface PathIterator + public int currentSegment (double[] coords) { + if (isDone()) { + throw new NoSuchElementException("Iterator out of bounds"); + } + evaluate(); + int type = bufType; + if (type != SEG_CLOSE) { + coords[0] = px; + coords[1] = py; + if (type != SEG_MOVETO) { + type = SEG_LINETO; + } + } + return type; + } + + /** Calculates flat path points for the current segment of the source shape. Line segment is + * flat by itself. Flatness of quad and cubic curves are evaluated by the getFlatnessSq() + * method. Curves are subdivided until current flatness is bigger than user defined value and + * subdivision limit isn't exhausted. Single source segments are translated to a series of + * buffer points. The smaller the flatness the bigger the series. Every currentSegment() call + * extracts one point from the buffer. When a series is completed, evaluate() takes the next + * source shape segment. */ + protected void evaluate () { + if (bufEmpty) { + bufType = p.currentSegment(coords); + } + + switch (bufType) { + case SEG_MOVETO: + case SEG_LINETO: + px = coords[0]; + py = coords[1]; + break; + + case SEG_QUADTO: + if (bufEmpty) { + bufIndex -= 6; + buf[bufIndex + 0] = px; + buf[bufIndex + 1] = py; + System.arraycopy(coords, 0, buf, bufIndex + 2, 4); + bufSubdiv = 0; + } + + while (bufSubdiv < bufLimit) { + if (QuadCurves.getFlatnessSq(buf, bufIndex) < flatness2) { + break; + } + + // Realloc buffer + if (bufIndex <= 4) { + double[] tmp = new double[bufSize + BUFFER_CAPACITY]; + System.arraycopy(buf, bufIndex, tmp, bufIndex + BUFFER_CAPACITY, bufSize + - bufIndex); + buf = tmp; + bufSize += BUFFER_CAPACITY; + bufIndex += BUFFER_CAPACITY; + } + + QuadCurves.subdivide(buf, bufIndex, buf, bufIndex - 4, buf, bufIndex); + + bufIndex -= 4; + bufSubdiv++; + } + + bufIndex += 4; + px = buf[bufIndex]; + py = buf[bufIndex + 1]; + + bufEmpty = (bufIndex == bufSize - 2); + if (bufEmpty) { + bufIndex = bufSize; + bufType = SEG_LINETO; + } + break; + + case SEG_CUBICTO: + if (bufEmpty) { + bufIndex -= 8; + buf[bufIndex + 0] = px; + buf[bufIndex + 1] = py; + System.arraycopy(coords, 0, buf, bufIndex + 2, 6); + bufSubdiv = 0; + } + + while (bufSubdiv < bufLimit) { + if (CubicCurves.getFlatnessSq(buf, bufIndex) < flatness2) { + break; + } + + // Realloc buffer + if (bufIndex <= 6) { + double[] tmp = new double[bufSize + BUFFER_CAPACITY]; + System.arraycopy(buf, bufIndex, tmp, bufIndex + BUFFER_CAPACITY, bufSize + - bufIndex); + buf = tmp; + bufSize += BUFFER_CAPACITY; + bufIndex += BUFFER_CAPACITY; + } + + CubicCurves.subdivide(buf, bufIndex, buf, bufIndex - 6, buf, bufIndex); + + bufIndex -= 6; + bufSubdiv++; + } + + bufIndex += 6; + px = buf[bufIndex]; + py = buf[bufIndex + 1]; + + bufEmpty = (bufIndex == bufSize - 2); + if (bufEmpty) { + bufIndex = bufSize; + bufType = SEG_LINETO; + } + break; + } + } + + /** The type of current segment to be flat */ + private int bufType; + + /** The curve subdivision limit */ + private int bufLimit; + + /** The current points buffer size */ + private int bufSize; + + /** The inner cursor position in points buffer */ + private int bufIndex; + + /** The current subdivision count */ + private int bufSubdiv; + + /** The points buffer */ + private double[] buf; + + /** The indicator of empty points buffer */ + private boolean bufEmpty = true; + + /** The source PathIterator */ + private PathIterator p; + + /** The flatness of new path */ + private double flatness; + + /** The square of flatness */ + private double flatness2; + + /** The x coordinate of previous path segment */ + private double px; + + /** The y coordinate of previous path segment */ + private double py; + + /** The tamporary buffer for getting points from PathIterator */ + private double[] coords = new double[6]; + + /** The default points buffer size */ + private static final int BUFFER_SIZE = 16; + + /** The default curve subdivision limit */ + private static final int BUFFER_LIMIT = 16; + + /** The points buffer capacity */ + private static final int BUFFER_CAPACITY = 16; +} diff --git a/src/main/java/pythagoras/d/GeometryUtil.java b/src/main/java/pythagoras/d/GeometryUtil.java new file mode 100644 index 0000000..d5b1489 --- /dev/null +++ b/src/main/java/pythagoras/d/GeometryUtil.java @@ -0,0 +1,483 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * Various geometry utility methods. + */ +public class GeometryUtil +{ + public static final double EPSILON = Math.pow(10, -14); + + public static int intersectLinesWithParams (double x1, double y1, double x2, double y2, + double x3, double y3, double x4, double y4, + double[] params) { + double dx = x4 - x3; + double dy = y4 - y3; + double d = dx * (y2 - y1) - dy * (x2 - x1); + // double comparison + if (Math.abs(d) < EPSILON) { + return 0; + } + + params[0] = (-dx * (y1 - y3) + dy * (x1 - x3)) / d; + + if (dx != 0) { + params[1] = (line(params[0], x1, x2) - x3) / dx; + } else if (dy != 0) { + params[1] = (line(params[0], y1, y2) - y3) / dy; + } else { + params[1] = 0f; + } + + if (params[0] >= 0 && params[0] <= 1 && params[1] >= 0 && params[1] <= 1) { + return 1; + } + + return 0; + } + + /** + * Checks whether line (x1, y1) - (x2, y2) and line (x3, y3) - (x4, y4) intersect. If lines + * intersect then the result parameters are saved to point array. The size of {@code point} + * must be at least 2. + * + * @return 1 if two lines intersect in the defined interval, otherwise 0. + */ + public static int intersectLines (double x1, double y1, double x2, double y2, + double x3, double y3, double x4, double y4, double[] point) { + double A1 = -(y2 - y1); + double B1 = (x2 - x1); + double C1 = x1 * y2 - x2 * y1; + double A2 = -(y4 - y3); + double B2 = (x4 - x3); + double C2 = x3 * y4 - x4 * y3; + double coefParallel = A1 * B2 - A2 * B1; + // double comparison + if (x3 == x4 && y3 == y4 && (A1 * x3 + B1 * y3 + C1 == 0) && (x3 >= Math.min(x1, x2)) && + (x3 <= Math.max(x1, x2)) && (y3 >= Math.min(y1, y2)) && (y3 <= Math.max(y1, y2))) { + return 1; + } + if (Math.abs(coefParallel) < EPSILON) { + return 0; + } + point[0] = (B1 * C2 - B2 * C1) / coefParallel; + point[1] = (A2 * C1 - A1 * C2) / coefParallel; + if (point[0] >= Math.min(x1, x2) && point[0] >= Math.min(x3, x4) && + point[0] <= Math.max(x1, x2) && point[0] <= Math.max(x3, x4) && + point[1] >= Math.min(y1, y2) && point[1] >= Math.min(y3, y4) && + point[1] <= Math.max(y1, y2) && point[1] <= Math.max(y3, y4)) { + return 1; + } + return 0; + } + + /** + * Checks whether there is intersection of the line (x1, y1) - (x2, y2) and the quad curve + * (qx1, qy1) - (qx2, qy2) - (qx3, qy3). The parameters of the intersection area saved to + * {@code params}. Therefore {@code params} must be of length at least 4. + * + * @return the number of roots that lie in the defined interval. + */ + public static int intersectLineAndQuad (double x1, double y1, double x2, double y2, + double qx1, double qy1, double qx2, double qy2, + double qx3, double qy3, double[] params) { + double[] eqn = new double[3]; + double[] t = new double[2]; + double[] s = new double[2]; + double dy = y2 - y1; + double dx = x2 - x1; + int quantity = 0; + int count = 0; + + eqn[0] = dy * (qx1 - x1) - dx * (qy1 - y1); + eqn[1] = 2 * dy * (qx2 - qx1) - 2 * dx * (qy2 - qy1); + eqn[2] = dy * (qx1 - 2 * qx2 + qx3) - dx * (qy1 - 2 * qy2 + qy3); + + if ((count = Crossing.solveQuad(eqn, t)) == 0) { + return 0; + } + + for (int i = 0; i < count; i++) { + if (dx != 0) { + s[i] = (quad(t[i], qx1, qx2, qx3) - x1) / dx; + } else if (dy != 0) { + s[i] = (quad(t[i], qy1, qy2, qy3) - y1) / dy; + } else { + s[i] = 0f; + } + if (t[i] >= 0 && t[i] <= 1 && s[i] >= 0 && s[i] <= 1) { + params[2 * quantity] = t[i]; + params[2 * quantity + 1] = s[i]; + ++quantity; + } + } + + return quantity; + } + + /** + * Checks whether the line (x1, y1) - (x2, y2) and the cubic curve (cx1, cy1) - (cx2, cy2) - + * (cx3, cy3) - (cx4, cy4) intersect. The points of intersection are saved to {@code points}. + * Therefore {@code points} must be of length at least 6. + * + * @return the numbers of roots that lie in the defined interval. + */ + public static int intersectLineAndCubic (double x1, double y1, double x2, double y2, + double cx1, double cy1, double cx2, double cy2, + double cx3, double cy3, double cx4, double cy4, + double[] params) { + double[] eqn = new double[4]; + double[] t = new double[3]; + double[] s = new double[3]; + double dy = y2 - y1; + double dx = x2 - x1; + int quantity = 0; + int count = 0; + + eqn[0] = (cy1 - y1) * dx + (x1 - cx1) * dy; + eqn[1] = -3 * (cy1 - cy2) * dx + 3 * (cx1 - cx2) * dy; + eqn[2] = (3 * cy1 - 6 * cy2 + 3 * cy3) * dx - (3 * cx1 - 6 * cx2 + 3 * cx3) * dy; + eqn[3] = (-3 * cy1 + 3 * cy2 - 3 * cy3 + cy4) * dx + + (3 * cx1 - 3 * cx2 + 3 * cx3 - cx4) * dy; + + if ((count = Crossing.solveCubic(eqn, t)) == 0) { + return 0; + } + + for (int i = 0; i < count; i++) { + if (dx != 0) { + s[i] = (cubic(t[i], cx1, cx2, cx3, cx4) - x1) / dx; + } else if (dy != 0) { + s[i] = (cubic(t[i], cy1, cy2, cy3, cy4) - y1) / dy; + } else { + s[i] = 0f; + } + if (t[i] >= 0 && t[i] <= 1 && s[i] >= 0 && s[i] <= 1) { + params[2 * quantity] = t[i]; + params[2 * quantity + 1] = s[i]; + ++quantity; + } + } + + return quantity; + } + + /** + * Checks whether two quads (x1, y1) - (x2, y2) - (x3, y3) and (qx1, qy1) - (qx2, qy2) - (qx3, + * qy3) intersect. The result is saved to {@code params}. Thus {@code params} must be of length + * at least 4. + * + * @return the number of roots that lie in the interval. + */ + public static int intersectQuads (double x1, double y1, double x2, double y2, + double x3, double y3, + double qx1, double qy1, double qx2, double qy2, + double qx3, double qy3, double[] params) { + double[] initParams = new double[2]; + double[] xCoefs1 = new double[3]; + double[] yCoefs1 = new double[3]; + double[] xCoefs2 = new double[3]; + double[] yCoefs2 = new double[3]; + int quantity = 0; + + xCoefs1[0] = x1 - 2 * x2 + x3; + xCoefs1[1] = -2 * x1 + 2 * x2; + xCoefs1[2] = x1; + + yCoefs1[0] = y1 - 2 * y2 + y3; + yCoefs1[1] = -2 * y1 + 2 * y2; + yCoefs1[2] = y1; + + xCoefs2[0] = qx1 - 2 * qx2 + qx3; + xCoefs2[1] = -2 * qx1 + 2 * qx2; + xCoefs2[2] = qx1; + + yCoefs2[0] = qy1 - 2 * qy2 + qy3; + yCoefs2[1] = -2 * qy1 + 2 * qy2; + yCoefs2[2] = qy1; + + // initialize params[0] and params[1] + params[0] = params[1] = 0.25f; + quadNewton(xCoefs1, yCoefs1, xCoefs2, yCoefs2, initParams); + if (initParams[0] <= 1 && initParams[0] >= 0 && initParams[1] >= 0 && initParams[1] <= 1) { + params[2 * quantity] = initParams[0]; + params[2 * quantity + 1] = initParams[1]; + ++quantity; + } + // initialize params + params[0] = params[1] = 0.75f; + quadNewton(xCoefs1, yCoefs1, xCoefs2, yCoefs2, params); + if (initParams[0] <= 1 && initParams[0] >= 0 && initParams[1] >= 0 && initParams[1] <= 1) { + params[2 * quantity] = initParams[0]; + params[2 * quantity + 1] = initParams[1]; + ++quantity; + } + + return quantity; + } + + /** + * Checks whether the quad (x1, y1) - (x2, y2) - (x3, y3) and the cubic (cx1, cy1) - (cx2, cy2) + * - (cx3, cy3) - (cx4, cy4) curves intersect. The points of the intersection are saved to + * {@code params}. Thus {@code params} must be of length at least 6. + * + * @return the number of intersection points that lie in the interval. + */ + public static int intersectQuadAndCubic (double qx1, double qy1, double qx2, double qy2, + double qx3, double qy3, double cx1, double cy1, + double cx2, double cy2, double cx3, double cy3, + double cx4, double cy4, double[] params) { + int quantity = 0; + double[] initParams = new double[3]; + double[] xCoefs1 = new double[3]; + double[] yCoefs1 = new double[3]; + double[] xCoefs2 = new double[4]; + double[] yCoefs2 = new double[4]; + xCoefs1[0] = qx1 - 2 * qx2 + qx3; + xCoefs1[1] = 2 * qx2 - 2 * qx1; + xCoefs1[2] = qx1; + + yCoefs1[0] = qy1 - 2 * qy2 + qy3; + yCoefs1[1] = 2 * qy2 - 2 * qy1; + yCoefs1[2] = qy1; + + xCoefs2[0] = -cx1 + 3 * cx2 - 3 * cx3 + cx4; + xCoefs2[1] = 3 * cx1 - 6 * cx2 + 3 * cx3; + xCoefs2[2] = -3 * cx1 + 3 * cx2; + xCoefs2[3] = cx1; + + yCoefs2[0] = -cy1 + 3 * cy2 - 3 * cy3 + cy4; + yCoefs2[1] = 3 * cy1 - 6 * cy2 + 3 * cy3; + yCoefs2[2] = -3 * cy1 + 3 * cy2; + yCoefs2[3] = cy1; + + // initialize params[0] and params[1] + params[0] = params[1] = 0.25f; + quadAndCubicNewton(xCoefs1, yCoefs1, xCoefs2, yCoefs2, initParams); + if (initParams[0] <= 1 && initParams[0] >= 0 && initParams[1] >= 0 && initParams[1] <= 1) { + params[2 * quantity] = initParams[0]; + params[2 * quantity + 1] = initParams[1]; + ++quantity; + } + // initialize params + params[0] = params[1] = 0.5f; + quadAndCubicNewton(xCoefs1, yCoefs1, xCoefs2, yCoefs2, params); + if (initParams[0] <= 1 && initParams[0] >= 0 && initParams[1] >= 0 && initParams[1] <= 1) { + params[2 * quantity] = initParams[0]; + params[2 * quantity + 1] = initParams[1]; + ++quantity; + } + + params[0] = params[1] = 0.75f; + quadAndCubicNewton(xCoefs1, yCoefs1, xCoefs2, yCoefs2, params); + if (initParams[0] <= 1 && initParams[0] >= 0 && initParams[1] >= 0 && initParams[1] <= 1) { + params[2 * quantity] = initParams[0]; + params[2 * quantity + 1] = initParams[1]; + ++quantity; + } + return quantity; + } + + /** + * Checks whether two cubic curves (x1, y1) - (x2, y2) - (x3, y3) - (x4, y4) and (cx1, cy1) - + * (cx2, cy2) - (cx3, cy3) - (cx4, cy4) intersect. The result is saved to {@code params}. Thus + * {@code params} must be of length at least 6. + * + * @return the number of intersection points that lie in the interval. + */ + public static int intersectCubics (double x1, double y1, double x2, double y2, + double x3, double y3, double x4, double y4, + double cx1, double cy1, double cx2, double cy2, + double cx3, double cy3, double cx4, double cy4, + double[] params) { + int quantity = 0; + double[] initParams = new double[3]; + double[] xCoefs1 = new double[4]; + double[] yCoefs1 = new double[4]; + double[] xCoefs2 = new double[4]; + double[] yCoefs2 = new double[4]; + xCoefs1[0] = -x1 + 3 * x2 - 3 * x3 + x4; + xCoefs1[1] = 3 * x1 - 6 * x2 + 3 * x3; + xCoefs1[2] = -3 * x1 + 3 * x2; + xCoefs1[3] = x1; + + yCoefs1[0] = -y1 + 3 * y2 - 3 * y3 + y4; + yCoefs1[1] = 3 * y1 - 6 * y2 + 3 * y3; + yCoefs1[2] = -3 * y1 + 3 * y2; + yCoefs1[3] = y1; + + xCoefs2[0] = -cx1 + 3 * cx2 - 3 * cx3 + cx4; + xCoefs2[1] = 3 * cx1 - 6 * cx2 + 3 * cx3; + xCoefs2[2] = -3 * cx1 + 3 * cx2; + xCoefs2[3] = cx1; + + yCoefs2[0] = -cy1 + 3 * cy2 - 3 * cy3 + cy4; + yCoefs2[1] = 3 * cy1 - 6 * cy2 + 3 * cy3; + yCoefs2[2] = -3 * cy1 + 3 * cy2; + yCoefs2[3] = cy1; + + // TODO + params[0] = params[1] = 0.25f; + cubicNewton(xCoefs1, yCoefs1, xCoefs2, yCoefs2, initParams); + if (initParams[0] <= 1 && initParams[0] >= 0 && initParams[1] >= 0 && initParams[1] <= 1) { + params[2 * quantity] = initParams[0]; + params[2 * quantity + 1] = initParams[1]; + ++quantity; + } + + params[0] = params[1] = 0.5f; + cubicNewton(xCoefs1, yCoefs1, xCoefs2, yCoefs2, params); + if (initParams[0] <= 1 && initParams[0] >= 0 && initParams[1] >= 0 && initParams[1] <= 1) { + params[2 * quantity] = initParams[0]; + params[2 * quantity + 1] = initParams[1]; + ++quantity; + } + + params[0] = params[1] = 0.75f; + cubicNewton(xCoefs1, yCoefs1, xCoefs2, yCoefs2, params); + if (initParams[0] <= 1 && initParams[0] >= 0 && initParams[1] >= 0 && initParams[1] <= 1) { + params[2 * quantity] = initParams[0]; + params[2 * quantity + 1] = initParams[1]; + ++quantity; + } + return quantity; + } + + public static double line (double t, double x1, double x2) { + return x1 * (1f - t) + x2 * t; + } + + public static double quad (double t, double x1, double x2, double x3) { + return x1 * (1f - t) * (1f - t) + 2f * x2 * t * (1f - t) + x3 * t * t; + } + + public static double cubic (double t, double x1, double x2, double x3, double x4) { + return x1 * (1f - t) * (1f - t) * (1f - t) + 3f * x2 * (1f - t) * (1f - t) * t + 3f * x3 * + (1f - t) * t * t + x4 * t * t * t; + } + + // x, y - the coordinates of new vertex + // t0 - ? + public static void subQuad (double[] coef, double t0, boolean left) { + if (left) { + coef[2] = (1 - t0) * coef[0] + t0 * coef[2]; + coef[3] = (1 - t0) * coef[1] + t0 * coef[3]; + } else { + coef[2] = (1 - t0) * coef[2] + t0 * coef[4]; + coef[3] = (1 - t0) * coef[3] + t0 * coef[5]; + } + } + + public static void subCubic (double[] coef, double t0, boolean left) { + if (left) { + coef[2] = (1 - t0) * coef[0] + t0 * coef[2]; + coef[3] = (1 - t0) * coef[1] + t0 * coef[3]; + } else { + coef[4] = (1 - t0) * coef[4] + t0 * coef[6]; + coef[5] = (1 - t0) * coef[5] + t0 * coef[7]; + } + } + + private static void cubicNewton (double[] xCoefs1, double[] yCoefs1, + double[] xCoefs2, double[] yCoefs2, double[] params) { + double t = 0f, s = 0f; + double t1 = params[0]; + double s1 = params[1]; + double d, dt, ds; + + while (Math.sqrt((t - t1) * (t - t1) + (s - s1) * (s - s1)) > EPSILON) { + d = -(3 * t * t * xCoefs1[0] + 2 * t * xCoefs1[1] + xCoefs1[2]) * + (3 * s * s * yCoefs2[0] + 2 * s * yCoefs2[1] + yCoefs2[2]) + + (3 * t * t * yCoefs1[0] + 2 * t * yCoefs1[1] + yCoefs1[2]) * + (3 * s * s * xCoefs2[0] + 2 * s * xCoefs2[1] + xCoefs2[2]); + + dt = (t * t * t * xCoefs1[0] + t * t * xCoefs1[1] + t * xCoefs1[2] + xCoefs1[3] - + s * s * s * xCoefs2[0] - s * s * xCoefs2[1] - s * xCoefs2[2] - xCoefs2[3]) * + (-3 * s * s * yCoefs2[0] - 2 * s * yCoefs2[1] - yCoefs2[2]) + + (t * t * t * yCoefs1[0] + t * t * yCoefs1[1] + t * yCoefs1[2] + yCoefs1[3] - + s * s * s * yCoefs2[0] - s * s * yCoefs2[1] - s * yCoefs2[2] - yCoefs2[3]) * + (3 * s * s * xCoefs2[0] + 2 * s * xCoefs2[1] + xCoefs2[2]); + + ds = (3 * t * t * xCoefs1[0] + 2 * t * xCoefs1[1] + xCoefs1[2]) * + (t * t * t * yCoefs1[0] + t * t * yCoefs1[1] + t * yCoefs1[2] + yCoefs1[3] - + s * s * s * yCoefs2[0] - s * s * yCoefs2[1] - s * yCoefs2[2] - yCoefs2[3]) - + (3 * t * t * yCoefs1[0] + 2 * t * yCoefs1[1] + yCoefs1[2]) * + (t * t * t * xCoefs1[0] + t * t * xCoefs1[1] + t * xCoefs1[2] + xCoefs1[3] - + s * s * s * xCoefs2[0] - s * s * xCoefs2[1] - s * xCoefs2[2] - xCoefs2[3]); + + t1 = t - dt / d; + s1 = s - ds / d; + } + params[0] = t1; + params[1] = s1; + } + + private static void quadAndCubicNewton (double xCoefs1[], double yCoefs1[], + double xCoefs2[], double yCoefs2[], double[] params) { + double t = 0f, s = 0f; + double t1 = params[0]; + double s1 = params[1]; + double d, dt, ds; + + while (Math.sqrt((t - t1) * (t - t1) + (s - s1) * (s - s1)) > EPSILON) { + d = -(2 * t * xCoefs1[0] + xCoefs1[1]) * + (3 * s * s * yCoefs2[0] + 2 * s * yCoefs2[1] + yCoefs2[2]) + + (2 * t * yCoefs1[0] + yCoefs1[1]) * + (3 * s * s * xCoefs2[0] + 2 * s * xCoefs2[1] + xCoefs2[2]); + + dt = (t * t * xCoefs1[0] + t * xCoefs1[1] + xCoefs1[2] + -s * s * s * xCoefs2[0] - + s * s * xCoefs2[1] - s * xCoefs2[2] - xCoefs2[3]) * + (-3 * s * s * yCoefs2[0] - 2 * s * yCoefs2[1] - yCoefs2[2]) + + (t * t * yCoefs1[0] + t * yCoefs1[1] + yCoefs1[2] - s * s * s * yCoefs2[0] - + s * s * yCoefs2[1] - s * yCoefs2[2] - yCoefs2[3]) * + (3 * s * s * xCoefs2[0] + 2 * s * xCoefs2[1] + xCoefs2[2]); + + ds = (2 * t * xCoefs1[0] + xCoefs1[1]) * + (t * t * yCoefs1[0] + t * yCoefs1[1] + yCoefs1[2] - s * s * s * yCoefs2[0] - + s * s * yCoefs2[1] - s * yCoefs2[2] - yCoefs2[3]) - + (2 * t * yCoefs1[0] + yCoefs1[1]) * + (t * t * xCoefs1[0] + t * xCoefs1[1] + xCoefs1[2] - s * s * s * xCoefs2[0] - + s * s * xCoefs2[1] - s * xCoefs2[2] - xCoefs2[3]); + + t1 = t - dt / d; + s1 = s - ds / d; + } + params[0] = t1; + params[1] = s1; + } + + private static void quadNewton (double xCoefs1[], double yCoefs1[], + double xCoefs2[], double yCoefs2[], double params[]) { + double t = 0f, s = 0f; + double t1 = params[0]; + double s1 = params[1]; + double d, dt, ds; + + while (Math.sqrt((t - t1) * (t - t1) + (s - s1) * (s - s1)) > EPSILON) { + t = t1; + s = s1; + d = -(2 * t * xCoefs1[0] + xCoefs1[1]) * (2 * s * yCoefs2[0] + yCoefs2[1]) + + (2 * s * xCoefs2[0] + xCoefs2[1]) * (2 * t * yCoefs1[0] + yCoefs1[1]); + + dt = -(t * t * xCoefs1[0] + t * xCoefs1[1] + xCoefs1[1] - s * s * xCoefs2[0] - + s * xCoefs2[1] - xCoefs2[2]) * (2 * s * yCoefs2[0] + yCoefs2[1]) + + (2 * s * xCoefs2[0] + xCoefs2[1]) * + (t * t * yCoefs1[0] + t * yCoefs1[1] + yCoefs1[2] - s * s * yCoefs2[0] - + s * yCoefs2[1] - yCoefs2[2]); + + ds = (2 * t * xCoefs1[0] + xCoefs1[1]) * + (t * t * yCoefs1[0] + t * yCoefs1[1] + yCoefs1[2] - s * s * yCoefs2[0] - + s * yCoefs2[1] - yCoefs2[2]) - (2 * t * yCoefs1[0] + yCoefs1[1]) * + (t * t * xCoefs1[0] + t * xCoefs1[1] + xCoefs1[2] - s * s * xCoefs2[0] - + s * xCoefs2[1] - xCoefs2[2]); + + t1 = t - dt / d; + s1 = s - ds / d; + } + params[0] = t1; + params[1] = s1; + } +} diff --git a/src/main/java/pythagoras/d/IArc.java b/src/main/java/pythagoras/d/IArc.java new file mode 100644 index 0000000..bccb37b --- /dev/null +++ b/src/main/java/pythagoras/d/IArc.java @@ -0,0 +1,56 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * Provides read-only access to an {@link Arc}. + */ +public interface IArc extends IRectangularShape, Cloneable +{ + /** An arc type indicating a simple, unconnected curve. */ + int OPEN = 0; + + /** An arc type indicating a closed curve, connected by a straight line from the starting to + * the ending point of the arc. */ + int CHORD = 1; + + /** An arc type indicating a closed curve, connected by a line from the starting point of the + * arc to the center of the circle defining the arc, and another straight line from that center + * to the ending point of the arc. */ + int PIE = 2; + + /** Returns the type of this arc: {@link #OPEN}, etc. */ + int getArcType (); + + /** Returns the starting angle of this arc. */ + double getAngleStart (); + + /** Returns the angular extent of this arc. */ + double getAngleExtent (); + + /** Returns the intersection of the ray from the center (defined by the starting angle) and the + * elliptical boundary of the arc. */ + Point getStartPoint (); + + /** Writes the intersection of the ray from the center (defined by the starting angle) and the + * elliptical boundary of the arc into {@code target}. + * @return the supplied point. */ + Point getStartPoint (Point target); + + /** Returns the intersection of the ray from the center (defined by the starting angle plus the + * angular extent of the arc) and the elliptical boundary of the arc. */ + Point getEndPoint (); + + /** Writes the intersection of the ray from the center (defined by the starting angle plus the + * angular extent of the arc) and the elliptical boundary of the arc into {@code target}. + * @return the supplied point. */ + Point getEndPoint (Point target); + + /** Returns whether the specified angle is within the angular extents of this arc. */ + boolean containsAngle (double angle); + + /** Returns a mutable copy of this arc. */ + Arc clone (); +} diff --git a/src/main/java/pythagoras/d/ICubicCurve.java b/src/main/java/pythagoras/d/ICubicCurve.java new file mode 100644 index 0000000..9b34209 --- /dev/null +++ b/src/main/java/pythagoras/d/ICubicCurve.java @@ -0,0 +1,61 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * Provides read-only access to a {@link CubicCurve}. + */ +public interface ICubicCurve extends IShape, Cloneable +{ + /** Returns the x-coordinate of the start of this curve. */ + double getX1 (); + + /** Returns the y-coordinate of the start of this curve. */ + double getY1 (); + + /** Returns the x-coordinate of the first control point. */ + double getCtrlX1 (); + + /** Returns the y-coordinate of the first control point. */ + double getCtrlY1 (); + + /** Returns the x-coordinate of the second control point. */ + double getCtrlX2 (); + + /** Returns the y-coordinate of the second control point. */ + double getCtrlY2 (); + + /** Returns the x-coordinate of the end of this curve. */ + double getX2 (); + + /** Returns the y-coordinate of the end of this curve. */ + double getY2 (); + + /** Returns a copy of the starting point of this curve. */ + Point getP1 (); + + /** Returns a copy of the first control point of this curve. */ + Point getCtrlP1 (); + + /** Returns a copy of the second control point of this curve. */ + Point getCtrlP2 (); + + /** Returns a copy of the ending point of this curve. */ + Point getP2 (); + + /** Returns the square of the flatness (maximum distance of a control point from the line + * connecting the end points) of this curve. */ + double getFlatnessSq (); + + /** Returns the flatness (maximum distance of a control point from the line connecting the end + * points) of this curve. */ + double getFlatness (); + + /** Subdivides this curve and stores the results into {@code left} and {@code right}. */ + void subdivide (CubicCurve left, CubicCurve right); + + /** Returns a mutable copy of this curve. */ + CubicCurve clone (); +} diff --git a/src/main/java/pythagoras/d/IDimension.java b/src/main/java/pythagoras/d/IDimension.java new file mode 100644 index 0000000..f173148 --- /dev/null +++ b/src/main/java/pythagoras/d/IDimension.java @@ -0,0 +1,26 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * Provides read-only access to a {@link Dimension}. + */ +public interface IDimension extends Cloneable +{ + /** + * Returns the magnitude in the x-dimension. + */ + double getWidth (); + + /** + * Returns the magnitude in the y-dimension. + */ + double getHeight (); + + /** + * Returns a mutable copy of this dimension. + */ + Dimension clone (); +} diff --git a/src/main/java/pythagoras/d/IEllipse.java b/src/main/java/pythagoras/d/IEllipse.java new file mode 100644 index 0000000..7344441 --- /dev/null +++ b/src/main/java/pythagoras/d/IEllipse.java @@ -0,0 +1,14 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * Provides read-only access to an {@link Ellipse}. + */ +public interface IEllipse extends IRectangularShape, Cloneable +{ + /** Returns a mutable copy of this ellipse. */ + Ellipse clone (); +} diff --git a/src/main/java/pythagoras/d/ILine.java b/src/main/java/pythagoras/d/ILine.java new file mode 100644 index 0000000..448fa4b --- /dev/null +++ b/src/main/java/pythagoras/d/ILine.java @@ -0,0 +1,70 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * Provides read-only access to a {@link Line}. + */ +public interface ILine extends IShape, Cloneable +{ + /** Returns the x-coordinate of the start of this line. */ + double getX1 (); + + /** Returns the y-coordinate of the start of this line. */ + double getY1 (); + + /** Returns the x-coordinate of the end of this line. */ + double getX2 (); + + /** Returns the y-coordinate of the end of this line. */ + double getY2 (); + + /** Returns a copy of the starting point of this line. */ + Point getP1 (); + + /** Initializes the supplied point with this line's starting point. + * @return the supplied point. */ + Point getP1 (Point target); + + /** Returns a copy of the ending point of this line. */ + Point getP2 (); + + /** Initializes the supplied point with this line's ending point. + * @return the supplied point. */ + Point getP2 (Point target); + + /** Returns the square of the distance from the specified point to the line defined by this + * line segment. */ + double pointLineDistSq (double px, double py); + + /** Returns the square of the distance from the supplied point to the line defined by this line + * segment. */ + double pointLineDistSq (IPoint p); + + /** Returns the distance from the specified point to the line defined by this line segment. */ + double pointLineDist (double px, double py); + + /** Returns the distance from the supplied point to the line defined by this line segment. */ + double pointLineDist (IPoint p); + + /** Returns the square of the distance from the specified point this line segment. */ + double pointSegDistSq (double px, double py); + + /** Returns the square of the distance from the supplied point this line segment. */ + double pointSegDistSq (IPoint p); + + /** Returns the distance from the specified point this line segment. */ + double pointSegDist (double px, double py); + + /** Returns the distance from the supplied point this line segment. */ + double pointSegDist (IPoint p); + + int relativeCCW (double px, double py); + + int relativeCCW (IPoint p); + + /** Returns a mutable copy of this line. */ + Line clone (); +} diff --git a/src/main/java/pythagoras/d/IPoint.java b/src/main/java/pythagoras/d/IPoint.java new file mode 100644 index 0000000..5dbe070 --- /dev/null +++ b/src/main/java/pythagoras/d/IPoint.java @@ -0,0 +1,32 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * Provides read-only access to a {@link Point}. + */ +public interface IPoint extends Cloneable +{ + /** Returns this point's x-coordinate. */ + double getX (); + + /** Returns this point's y-coordinate. */ + double getY (); + + /** Returns the squared Euclidian distance between this point and the specified point. */ + double distanceSq (double px, double py); + + /** Returns the squared Euclidian distance between this point and the supplied point. */ + double distanceSq (IPoint p); + + /** Returns the Euclidian distance between this point and the specified point. */ + double distance (double px, double py); + + /** Returns the Euclidian distance between this point and the supplied point. */ + double distance (IPoint p); + + /** Returns a mutable copy of this point. */ + Point clone (); +} diff --git a/src/main/java/pythagoras/d/IQuadCurve.java b/src/main/java/pythagoras/d/IQuadCurve.java new file mode 100644 index 0000000..200ad90 --- /dev/null +++ b/src/main/java/pythagoras/d/IQuadCurve.java @@ -0,0 +1,52 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * Provides read-only access to a {@link QuadCurve}. + */ +public interface IQuadCurve extends IShape, Cloneable +{ + /** Returns the x-coordinate of the start of this curve. */ + double getX1 (); + + /** Returns the y-coordinate of the start of this curve. */ + double getY1 (); + + /** Returns the x-coordinate of the control point. */ + double getCtrlX (); + + /** Returns the y-coordinate of the control point. */ + double getCtrlY (); + + /** Returns the x-coordinate of the end of this curve. */ + double getX2 (); + + /** Returns the y-coordinate of the end of this curve. */ + double getY2 (); + + /** Returns a copy of the starting point of this curve. */ + Point getP1 (); + + /** Returns a copy of the control point of this curve. */ + Point getCtrlP (); + + /** Returns a copy of the ending point of this curve. */ + Point getP2 (); + + /** Returns the square of the flatness (maximum distance of a control point from the line + * connecting the end points) of this curve. */ + double getFlatnessSq (); + + /** Returns the flatness (maximum distance of a control point from the line connecting the end + * points) of this curve. */ + double getFlatness (); + + /** Subdivides this curve and stores the results into {@code left} and {@code right}. */ + void subdivide (QuadCurve left, QuadCurve right); + + /** Returns a mutable copy of this curve. */ + QuadCurve clone (); +} diff --git a/src/main/java/pythagoras/d/IRectangle.java b/src/main/java/pythagoras/d/IRectangle.java new file mode 100644 index 0000000..ba5eabb --- /dev/null +++ b/src/main/java/pythagoras/d/IRectangle.java @@ -0,0 +1,68 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * Provides read-only access to a {@link Rectangle}. + */ +public interface IRectangle extends IRectangularShape, Cloneable +{ + /** The bitmask that indicates that a point lies to the left of this rectangle. See + * {@link #outcode}. */ + int OUT_LEFT = 1; + + /** The bitmask that indicates that a point lies above this rectangle. See {@link #outcode}. */ + int OUT_TOP = 2; + + /** The bitmask that indicates that a point lies to the right of this rectangle. See + * {@link #outcode}. */ + int OUT_RIGHT = 4; + + /** The bitmask that indicates that a point lies below this rectangle. See {@link #outcode}. */ + int OUT_BOTTOM = 8; + + /** Returns a copy of this rectangle's upper-left corner. */ + Point getLocation (); + + /** Initializes the supplied point with this rectangle's upper-left corner. + * @return the supplied point. */ + Point getLocation (Point target); + + /** Returns a copy of this rectangle's size. */ + Dimension getSize (); + + /** Initializes the supplied dimension with this rectangle's size. + * @return the supplied dimension. */ + Dimension getSize (Dimension target); + + /** Returns the intersection of the specified rectangle and this rectangle (i.e. the largest + * rectangle contained in both this and the specified rectangle). */ + Rectangle intersection (double rx, double ry, double rw, double rh); + + /** Returns the intersection of the supplied rectangle and this rectangle (i.e. the largest + * rectangle contained in both this and the supplied rectangle). */ + Rectangle intersection (IRectangle r); + + /** Returns the union of the supplied rectangle and this rectangle (i.e. the smallest rectangle + * that contains both this and the supplied rectangle). */ + Rectangle union (IRectangle r); + + /** Returns true if the specified line segment intersects this rectangle. */ + boolean intersectsLine (double x1, double y1, double x2, double y2); + + /** Returns true if the supplied line segment intersects this rectangle. */ + boolean intersectsLine (ILine l); + + /** Returns a set of flags indicating where the specified point lies in relation to the bounds + * of this rectangle. See {@link #OUT_LEFT}, etc. */ + int outcode (double px, double py); + + /** Returns a set of flags indicating where the supplied point lies in relation to the bounds of + * this rectangle. See {@link #OUT_LEFT}, etc. */ + int outcode (IPoint point); + + /** Returns a mutable copy of this rectangle. */ + Rectangle clone (); +} diff --git a/src/main/java/pythagoras/d/IRectangularShape.java b/src/main/java/pythagoras/d/IRectangularShape.java new file mode 100644 index 0000000..5685d08 --- /dev/null +++ b/src/main/java/pythagoras/d/IRectangularShape.java @@ -0,0 +1,50 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * An interface implemented by {@link Shape} classes whose geometry is defined by a rectangular + * frame. The framing rectangle defines the geometry, but may in some cases differ from + * the bounding rectangle of the shape. + */ +public interface IRectangularShape extends IShape +{ + /** Returns the x-coordinate of the upper-left corner of the framing rectangle. */ + double getX (); + + /** Returns the y-coordinate of the upper-left corner of the framing rectangle. */ + double getY (); + + /** Returns the width of the framing rectangle. */ + double getWidth (); + + /** Returns the height of the framing rectangle. */ + double getHeight (); + + /** Returns the minimum x-coordinate of the framing rectangle. */ + double getMinX (); + + /** Returns the minimum y-coordinate of the framing rectangle. */ + double getMinY (); + + /** Returns the maximum x-coordinate of the framing rectangle. */ + double getMaxX (); + + /** Returns the maximum y-coordinate of the framing rectangle. */ + double getMaxY (); + + /** Returns the x-coordinate of the center of the framing rectangle. */ + double getCenterX (); + + /** Returns the y-coordinate of the center of the framing rectangle. */ + double getCenterY (); + + /** Returns a copy of this shape's framing rectangle. */ + Rectangle getFrame (); + + /** Initializes the supplied rectangle with this shape's framing rectangle. + * @return the supplied rectangle. */ + Rectangle getFrame (Rectangle target); +} diff --git a/src/main/java/pythagoras/d/IRoundRectangle.java b/src/main/java/pythagoras/d/IRoundRectangle.java new file mode 100644 index 0000000..75257c1 --- /dev/null +++ b/src/main/java/pythagoras/d/IRoundRectangle.java @@ -0,0 +1,20 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * Provides read-only access to a {@link RoundRectangle}. + */ +public interface IRoundRectangle extends IRectangularShape, Cloneable +{ + /** Returns the width of the corner arc. */ + double getArcWidth (); + + /** Returns the height of the corner arc. */ + double getArcHeight (); + + /** Returns a mutable copy of this round rectangle. */ + RoundRectangle clone (); +} diff --git a/src/main/java/pythagoras/d/IShape.java b/src/main/java/pythagoras/d/IShape.java new file mode 100644 index 0000000..fc25f37 --- /dev/null +++ b/src/main/java/pythagoras/d/IShape.java @@ -0,0 +1,56 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * An interface provided by all shapes. + */ +public interface IShape +{ + /** Returns true if this shape encloses no area. */ + boolean isEmpty (); + + /** Returns true if this shape contains the specified point. */ + boolean contains (double x, double y); + + /** Returns true if this shape contains the supplied point. */ + boolean contains (IPoint point); + + /** Returns true if this shape completely contains the specified rectangle. */ + boolean contains (double x, double y, double width, double height); + + /** Returns true if this shape completely contains the supplied rectangle. */ + boolean contains (IRectangle r); + + /** Returns true if this shape intersects the specified rectangle. */ + boolean intersects (double x, double y, double width, double height); + + /** Returns true if this shape intersects the supplied rectangle. */ + boolean intersects (IRectangle r); + + /** Returns a copy of the bounding rectangle for this shape. */ + Rectangle getBounds (); + + /** Initializes the supplied rectangle with this shape's bounding rectangle. + * @return the supplied rectangle. */ + Rectangle getBounds (Rectangle target); + + /** + * Returns an iterator over the path described by this shape. + * + * @param at if supplied, the points in the path are transformed using this. + */ + PathIterator getPathIterator (AffineTransform at); + + /** + * Returns an iterator over the path described by this shape. + * + * @param at if supplied, the points in the path are transformed using this. + * @param flatness when approximating curved segments with lines, this controls the maximum + * distance the lines are allowed to deviate from the approximated curve, thus a higher + * flatness value generally allows for a path with fewer segments. + */ + PathIterator getPathIterator (AffineTransform at, double flatness); +} diff --git a/src/main/java/pythagoras/d/IllegalPathStateException.java b/src/main/java/pythagoras/d/IllegalPathStateException.java new file mode 100644 index 0000000..a094a5c --- /dev/null +++ b/src/main/java/pythagoras/d/IllegalPathStateException.java @@ -0,0 +1,20 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * An exception thrown if an operation is performed on a {@link Path} that is in an illegal state + * with respect to the particular operation being performed. For example, appending a segment to a + * path without an initial moveto. + */ +public class IllegalPathStateException extends RuntimeException +{ + public IllegalPathStateException () { + } + + public IllegalPathStateException (String s) { + super(s); + } +} diff --git a/src/main/java/pythagoras/d/IntersectPoint.java b/src/main/java/pythagoras/d/IntersectPoint.java new file mode 100644 index 0000000..8503b17 --- /dev/null +++ b/src/main/java/pythagoras/d/IntersectPoint.java @@ -0,0 +1,107 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * An internal helper class that represents the intersection point of two edges. + */ +class IntersectPoint +{ + public IntersectPoint (int begIndex1, int endIndex1, int begIndex2, int endIndex2, + double x, double y) { + this.begIndex1 = begIndex1; + this.endIndex1 = endIndex1; + this.begIndex2 = begIndex2; + this.endIndex2 = endIndex2; + this.x = x; + this.y = y; + } + + public IntersectPoint (int begIndex1, int endIndex1, int rule1, int ruleIndex1, + int begIndex2, int endIndex2, int rule2, int ruleIndex2, + double x, double y, double param1, double param2) { + this.begIndex1 = begIndex1; + this.endIndex1 = endIndex1; + this.rule1 = rule1; + this.ruleIndex1 = ruleIndex1; + this.param1 = param1; + this.begIndex2 = begIndex2; + this.endIndex2 = endIndex2; + this.rule2 = rule2; + this.ruleIndex2 = ruleIndex2; + this.param2 = param2; + this.x = x; + this.y = y; + } + + public int getBegIndex (boolean isCurrentArea) { + return isCurrentArea ? begIndex1 : begIndex2; + } + + public int getEndIndex (boolean isCurrentArea) { + return isCurrentArea ? endIndex1 : endIndex2; + } + + public int getRuleIndex (boolean isCurrentArea) { + return isCurrentArea ? ruleIndex1 : ruleIndex2; + } + + public double getParam (boolean isCurrentArea) { + return isCurrentArea ? param1 : param2; + } + + public int getRule (boolean isCurrentArea) { + return isCurrentArea ? rule1 : rule2; + } + + public double getX () { + return x; + } + + public double getY () { + return y; + } + + public void setBegIndex1 (int begIndex) { + this.begIndex1 = begIndex; + } + + public void setEndIndex1 (int endIndex) { + this.endIndex1 = endIndex; + } + + public void setBegIndex2 (int begIndex) { + this.begIndex2 = begIndex; + } + + public void setEndIndex2 (int endIndex) { + this.endIndex2 = endIndex; + } + + // the edge begin number of first line + private int begIndex1; + // the edge end number of first line + private int endIndex1; + // the edge rule of first figure + private int rule1; + // the index of the first figure rules array + private int ruleIndex1; + // the parameter value of edge1 + private double param1; + // the edge begin number of second line + private int begIndex2; + // the edge end number of second line + private int endIndex2; + // the edge rule of second figure + private int rule2; + // the index of the second figure rules array + private int ruleIndex2; + // the absciss coordinate of the point + private final double x; + // the ordinate coordinate of the point + private final double y; + // the parameter value of edge2 + private double param2; +} diff --git a/src/main/java/pythagoras/d/Line.java b/src/main/java/pythagoras/d/Line.java new file mode 100644 index 0000000..e778903 --- /dev/null +++ b/src/main/java/pythagoras/d/Line.java @@ -0,0 +1,82 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +import java.io.Serializable; + +/** + * Represents a line segment. + */ +public class Line extends AbstractLine implements Serializable +{ + /** The x-coordinate of the start of this line segment. */ + public double x1; + + /** The y-coordinate of the start of this line segment. */ + public double y1; + + /** The x-coordinate of the end of this line segment. */ + public double x2; + + /** The y-coordinate of the end of this line segment. */ + public double y2; + + /** + * Creates a line from (0,0) to (0,0). + */ + public Line () { + } + + /** + * Creates a line from (x1,y1), to (x2,y2). + */ + public Line (double x1, double y1, double x2, double y2) { + setLine(x1, y1, x2, y2); + } + + /** + * Creates a line from p1 to p2. + */ + public Line (IPoint p1, IPoint p2) { + setLine(p1, p2); + } + + /** + * Sets the start and end point of this line to the specified values. + */ + public void setLine (double x1, double y1, double x2, double y2) { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + } + + /** + * Sets the start and end of this line to the specified points. + */ + public void setLine (IPoint p1, IPoint p2) { + setLine(p1.getX(), p1.getY(), p2.getY(), p2.getY()); + } + + @Override // from interface ILine + public double getX1 () { + return x1; + } + + @Override // from interface ILine + public double getY1 () { + return y1; + } + + @Override // from interface ILine + public double getX2 () { + return x2; + } + + @Override // from interface ILine + public double getY2 () { + return y2; + } +} diff --git a/src/main/java/pythagoras/d/Lines.java b/src/main/java/pythagoras/d/Lines.java new file mode 100644 index 0000000..65c6be1 --- /dev/null +++ b/src/main/java/pythagoras/d/Lines.java @@ -0,0 +1,155 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * Line-related utility methods. + */ +public class Lines +{ + /** + * Returns true if the specified two line segments intersect. + */ + public static boolean linesIntersect (double x1, double y1, double x2, double y2, + double x3, double y3, double x4, double y4) { + // A = (x2-x1, y2-y1) + // B = (x3-x1, y3-y1) + // C = (x4-x1, y4-y1) + // D = (x4-x3, y4-y3) = C-B + // E = (x1-x3, y1-y3) = -B + // F = (x2-x3, y2-y3) = A-B + // + // Result is ((AxB) * (AxC) <= 0) and ((DxE) * (DxF) <= 0) + // + // DxE = (C-B)x(-B) = BxB-CxB = BxC + // DxF = (C-B)x(A-B) = CxA-CxB-BxA+BxB = AxB+BxC-AxC + x2 -= x1; // A + y2 -= y1; + x3 -= x1; // B + y3 -= y1; + x4 -= x1; // C + y4 -= y1; + + double AvB = x2 * y3 - x3 * y2; + double AvC = x2 * y4 - x4 * y2; + + // online + if (AvB == 0 && AvC == 0) { + if (x2 != 0) { + return (x4 * x3 <= 0) || + ((x3 * x2 >= 0) && (x2 > 0 ? x3 <= x2 || x4 <= x2 : x3 >= x2 || x4 >= x2)); + } + if (y2 != 0) { + return (y4 * y3 <= 0) || + ((y3 * y2 >= 0) && (y2 > 0 ? y3 <= y2 || y4 <= y2 : y3 >= y2 || y4 >= y2)); + } + return false; + } + + double BvC = x3 * y4 - x4 * y3; + return (AvB * AvC <= 0) && (BvC * (AvB + BvC - AvC) <= 0); + } + + /** + * Returns true if the specified line segment intersects the specified rectangle. + */ + public static boolean lineIntersectsRect (double x1, double y1, double x2, double y2, + double rx, double ry, double rw, double rh) { + double rr = rx + rw, rb = ry + rh; + return (rx <= x1 && x1 <= rr && ry <= y1 && y1 <= rb) + || (rx <= x2 && x2 <= rr && ry <= y2 && y2 <= rb) + || linesIntersect(rx, ry, rr, rb, x1, y1, x2, y2) + || linesIntersect(rr, ry, rx, rb, x1, y1, x2, y2); + } + + /** + * Returns the square of the distance from the specified point to the specified line. + */ + public static double pointLineDistSq (double px, double py, + double x1, double y1, double x2, double y2) { + x2 -= x1; + y2 -= y1; + px -= x1; + py -= y1; + double s = px * y2 - py * x2; + return (s * s) / (x2 * x2 + y2 * y2); + } + + /** + * Returns the distance from the specified point to the specified line. + */ + public static double pointLineDist (double px, double py, + double x1, double y1, double x2, double y2) { + return Math.sqrt(pointLineDistSq(px, py, x1, y1, x2, y2)); + } + + /** + * Returns the square of the distance between the specified point and the specified line + * segment. + */ + public static double pointSegDistSq (double px, double py, + double x1, double y1, double x2, double y2) { + // A = (x2 - x1, y2 - y1) + // P = (px - x1, py - y1) + x2 -= x1; // A = (x2, y2) + y2 -= y1; + px -= x1; // P = (px, py) + py -= y1; + double dist; + if (px * x2 + py * y2 <= 0.0) { // P*A + dist = px * px + py * py; + } else { + px = x2 - px; // P = A - P = (x2 - px, y2 - py) + py = y2 - py; + if (px * x2 + py * y2 <= 0.0) { // P*A + dist = px * px + py * py; + } else { + dist = px * y2 - py * x2; + dist = dist * dist / (x2 * x2 + y2 * y2); // pxA/|A| + } + } + if (dist < 0) { + dist = 0; + } + return dist; + } + + /** + * Returns the distance between the specified point and the specified line segment. + */ + public static double pointSegDist (double px, double py, + double x1, double y1, double x2, double y2) { + return Math.sqrt(pointSegDistSq(px, py, x1, y1, x2, y2)); + } + + /** + * Returns an indicator of where the specified point (px,py) lies with respect to the line + * segment from (x1,y1) to (x2,y2). + * + * @see http://download.oracle.com/javase/6/docs/api/java/awt/geom/Line2D.html + */ + public static int relativeCCW (double px, double py, + double x1, double y1, double x2, double y2) { + // A = (x2-x1, y2-y1) + // P = (px-x1, py-y1) + x2 -= x1; + y2 -= y1; + px -= x1; + py -= y1; + double t = px * y2 - py * x2; // PxA + if (t == 0f) { + t = px * x2 + py * y2; // P*A + if (t > 0f) { + px -= x2; // B-A + py -= y2; + t = px * x2 + py * y2; // (P-A)*A + if (t < 0f) { + t = 0f; + } + } + } + return (t < 0f) ? -1 : (t > 0f ? 1 : 0); + } +} diff --git a/src/main/java/pythagoras/d/NoninvertibleTransformException.java b/src/main/java/pythagoras/d/NoninvertibleTransformException.java new file mode 100644 index 0000000..6b13670 --- /dev/null +++ b/src/main/java/pythagoras/d/NoninvertibleTransformException.java @@ -0,0 +1,16 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * An exception thrown by {@link AffineTransform} when a request for an inverse transform cannot be + * satisfied. + */ +public class NoninvertibleTransformException extends java.lang.Exception +{ + public NoninvertibleTransformException (String s) { + super(s); + } +} diff --git a/src/main/java/pythagoras/d/Path.java b/src/main/java/pythagoras/d/Path.java new file mode 100644 index 0000000..171d8c6 --- /dev/null +++ b/src/main/java/pythagoras/d/Path.java @@ -0,0 +1,374 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +import java.util.NoSuchElementException; + +/** + * Represents a path constructed from lines and curves and which can contain subpaths. + */ +public final class Path implements IShape, Cloneable +{ + /** Specifies the even/odd rule for determining the interior of a path. */ + public static final int WIND_EVEN_ODD = PathIterator.WIND_EVEN_ODD; + + /** Specifies the non-zero rule for determining the interior of a path. */ + public static final int WIND_NON_ZERO = PathIterator.WIND_NON_ZERO; + + public Path () { + this(WIND_NON_ZERO, BUFFER_SIZE); + } + + public Path (int rule) { + this(rule, BUFFER_SIZE); + } + + public Path (int rule, int initialCapacity) { + setWindingRule(rule); + types = new byte[initialCapacity]; + points = new double[initialCapacity * 2]; + } + + public Path (IShape shape) { + this(WIND_NON_ZERO, BUFFER_SIZE); + PathIterator p = shape.getPathIterator(null); + setWindingRule(p.getWindingRule()); + append(p, false); + } + + public void setWindingRule (int rule) { + if (rule != WIND_EVEN_ODD && rule != WIND_NON_ZERO) { + throw new IllegalArgumentException("Invalid winding rule value"); + } + this.rule = rule; + } + + public int getWindingRule () { + return rule; + } + + public void moveTo (double x, double y) { + if (typeSize > 0 && types[typeSize - 1] == PathIterator.SEG_MOVETO) { + points[pointSize - 2] = x; + points[pointSize - 1] = y; + } else { + checkBuf(2, false); + types[typeSize++] = PathIterator.SEG_MOVETO; + points[pointSize++] = x; + points[pointSize++] = y; + } + } + + public void lineTo (double x, double y) { + checkBuf(2, true); + types[typeSize++] = PathIterator.SEG_LINETO; + points[pointSize++] = x; + points[pointSize++] = y; + } + + public void quadTo (double x1, double y1, double x2, double y2) { + checkBuf(4, true); + types[typeSize++] = PathIterator.SEG_QUADTO; + points[pointSize++] = x1; + points[pointSize++] = y1; + points[pointSize++] = x2; + points[pointSize++] = y2; + } + + public void curveTo (double x1, double y1, double x2, double y2, double x3, double y3) { + checkBuf(6, true); + types[typeSize++] = PathIterator.SEG_CUBICTO; + points[pointSize++] = x1; + points[pointSize++] = y1; + points[pointSize++] = x2; + points[pointSize++] = y2; + points[pointSize++] = x3; + points[pointSize++] = y3; + } + + public void closePath () { + if (typeSize == 0 || types[typeSize - 1] != PathIterator.SEG_CLOSE) { + checkBuf(0, true); + types[typeSize++] = PathIterator.SEG_CLOSE; + } + } + + public void append (IShape shape, boolean connect) { + PathIterator p = shape.getPathIterator(null); + append(p, connect); + } + + public void append (PathIterator path, boolean connect) { + while (!path.isDone()) { + double[] coords = new double[6]; + switch (path.currentSegment(coords)) { + case PathIterator.SEG_MOVETO: + if (!connect || typeSize == 0) { + moveTo(coords[0], coords[1]); + } else if (types[typeSize - 1] != PathIterator.SEG_CLOSE && + points[pointSize - 2] == coords[0] && + points[pointSize - 1] == coords[1]) { + // we're already here + } else { + lineTo(coords[0], coords[1]); + } + break; + case PathIterator.SEG_LINETO: + lineTo(coords[0], coords[1]); + break; + case PathIterator.SEG_QUADTO: + quadTo(coords[0], coords[1], coords[2], coords[3]); + break; + case PathIterator.SEG_CUBICTO: + curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); + break; + case PathIterator.SEG_CLOSE: + closePath(); + break; + } + path.next(); + connect = false; + } + } + + public Point getCurrentPoint () { + if (typeSize == 0) { + return null; + } + int j = pointSize - 2; + if (types[typeSize - 1] == PathIterator.SEG_CLOSE) { + for (int i = typeSize - 2; i > 0; i--) { + int type = types[i]; + if (type == PathIterator.SEG_MOVETO) { + break; + } + j -= pointShift[type]; + } + } + return new Point(points[j], points[j + 1]); + } + + public void reset () { + typeSize = 0; + pointSize = 0; + } + + public void transform (AffineTransform t) { + t.transform(points, 0, points, 0, pointSize / 2); + } + + public IShape createTransformedShape (AffineTransform t) { + Path p = clone(); + if (t != null) { + p.transform(t); + } + return p; + } + + @Override // from interface IShape + public Rectangle getBounds () { + return getBounds(new Rectangle()); + } + + @Override // from interface IShape + public Rectangle getBounds (Rectangle target) { + double rx1, ry1, rx2, ry2; + if (pointSize == 0) { + rx1 = ry1 = rx2 = ry2 = 0f; + } else { + int i = pointSize - 1; + ry1 = ry2 = points[i--]; + rx1 = rx2 = points[i--]; + while (i > 0) { + double y = points[i--]; + double x = points[i--]; + if (x < rx1) { + rx1 = x; + } else if (x > rx2) { + rx2 = x; + } + if (y < ry1) { + ry1 = y; + } else if (y > ry2) { + ry2 = y; + } + } + } + target.setBounds(rx1, ry1, rx2 - rx1, ry2 - ry1); + return target; + } + + @Override // from interface IShape + public boolean isEmpty () { + // TODO: will this be insanely difficult to do correctly? + return getBounds().isEmpty(); + } + + @Override // from interface IShape + public boolean contains (double px, double py) { + return isInside(Crossing.crossShape(this, px, py)); + } + + @Override // from interface IShape + public boolean contains (double rx, double ry, double rw, double rh) { + int cross = Crossing.intersectShape(this, rx, ry, rw, rh); + return cross != Crossing.CROSSING && isInside(cross); + } + + @Override // from interface IShape + public boolean intersects (double rx, double ry, double rw, double rh) { + int cross = Crossing.intersectShape(this, rx, ry, rw, rh); + return cross == Crossing.CROSSING || isInside(cross); + } + + @Override // from interface IShape + public boolean contains (IPoint p) { + return contains(p.getX(), p.getY()); + } + + @Override // from interface IShape + public boolean contains (IRectangle r) { + return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + @Override // from interface IShape + public boolean intersects (IRectangle r) { + return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + @Override // from interface IShape + public PathIterator getPathIterator (AffineTransform t) { + return new Iterator(this, t); + } + + @Override // from interface IShape + public PathIterator getPathIterator (AffineTransform t, double flatness) { + return new FlatteningPathIterator(getPathIterator(t), flatness); + } + + @Override + public Path clone () { + try { + Path p = (Path)super.clone(); + p.types = types.clone(); + p.points = points.clone(); + return p; + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + /** + * Checks points and types buffer size to add pointCount points. If necessary realloc buffers + * to enlarge size. + * + * @param pointCount the point count to be added in buffer + */ + protected void checkBuf (int pointCount, boolean checkMove) { + if (checkMove && typeSize == 0) { + throw new IllegalPathStateException("First segment must be a SEG_MOVETO"); + } + if (typeSize == types.length) { + byte[] tmp = new byte[typeSize + BUFFER_CAPACITY]; + System.arraycopy(types, 0, tmp, 0, typeSize); + types = tmp; + } + if (pointSize + pointCount > points.length) { + double[] tmp = new double[pointSize + Math.max(BUFFER_CAPACITY * 2, pointCount)]; + System.arraycopy(points, 0, tmp, 0, pointSize); + points = tmp; + } + } + + /** + * Checks cross count according to path rule to define is it point inside shape or not. + * + * @param cross the point cross count. + * @return true if point is inside path, or false otherwise. + */ + protected boolean isInside (int cross) { + return (rule == WIND_NON_ZERO) ? Crossing.isInsideNonZero(cross) : + Crossing.isInsideEvenOdd(cross); + } + + /** An iterator over a {@link Path}. */ + protected static class Iterator implements PathIterator + { + /** The current cursor position in types buffer. */ + private int typeIndex; + + /** The current cursor position in points buffer. */ + private int pointIndex; + + /** The source Path object. */ + private Path p; + + /** The path iterator transformation. */ + private AffineTransform t; + + Iterator (Path path) { + this(path, null); + } + + Iterator (Path path, AffineTransform at) { + this.p = path; + this.t = at; + } + + @Override public int getWindingRule () { + return p.getWindingRule(); + } + + @Override public boolean isDone () { + return typeIndex >= p.typeSize; + } + + @Override public void next () { + typeIndex++; + } + + @Override public int currentSegment (double[] coords) { + if (isDone()) { + throw new NoSuchElementException("Iterator out of bounds"); + } + int type = p.types[typeIndex]; + int count = Path.pointShift[type]; + System.arraycopy(p.points, pointIndex, coords, 0, count); + if (t != null) { + t.transform(coords, 0, coords, 0, count / 2); + } + pointIndex += count; + return type; + } + } + + /** The point's types buffer. */ + protected byte[] types; + + /** The points buffer. */ + protected double[] points; + + /** The point's type buffer size. */ + protected int typeSize; + + /** The points buffer size. */ + protected int pointSize; + + /* The path rule. */ + protected int rule; + + /** The space required in points buffer for different segmenet types. */ + protected static int[] pointShift = { 2, // MOVETO + 2, // LINETO + 4, // QUADTO + 6, // CUBICTO + 0 }; // CLOSE + + /** The default initial buffer size. */ + protected static final int BUFFER_SIZE = 10; + + /** The amount by which to expand the buffer capacity. */ + protected static final int BUFFER_CAPACITY = 10; +} diff --git a/src/main/java/pythagoras/d/PathIterator.java b/src/main/java/pythagoras/d/PathIterator.java new file mode 100644 index 0000000..7687c04 --- /dev/null +++ b/src/main/java/pythagoras/d/PathIterator.java @@ -0,0 +1,61 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * Used to return the boundary of a {@link IShape}, one segment at a time. + */ +public interface PathIterator +{ + /** Specifies the even/odd rule for determining the interior of a path. */ + int WIND_EVEN_ODD = 0; + + /** Specifies the non-zero rule for determining the interior of a path. */ + int WIND_NON_ZERO = 1; + + /** Indicates the starting location for a new subpath. */ + int SEG_MOVETO = 0; + + /** Indicates the end point of a line to be drawn from the most recently specified point. */ + int SEG_LINETO = 1; + + /** Indicates a pair of points that specify a quadratic parametric curve to be drawn from the + * most recently specified point. */ + int SEG_QUADTO = 2; + + /** Indicates a pair of points that specify a cubic parametric curve to be drawn from the most + * recently specified point. */ + int SEG_CUBICTO = 3; + + /** Indicates that the preceding subpath should be closed by appending a line segment back to + * the point corresponding to the most recent {@link #SEG_MOVETO}. */ + int SEG_CLOSE = 4; + + /** + * Returns the winding rule used to determine the interior of this path. + */ + int getWindingRule (); + + /** + * Returns true if this path has no additional segments. + */ + boolean isDone (); + + /** + * Advances this path to the next segment. + */ + void next (); + + /** + * Returns the coordinates and type of the current path segment. The number of points stored in + * {@code coords} differs by path segment type: 0 - {@link #SEG_CLOSE}, 1 - {@link + * #SEG_MOVETO}, {@link #SEG_LINETO}, 2 - {@link #SEG_QUADTO}, 3 - {@link #SEG_CUBICTO}. + * + * @param coords a buffer into which the current coordinates will be copied. It must be of + * length 6. Each point is stored as a pair of x,y coordinates. + * @return the path segment type, e.g. {@link #SEG_MOVETO}. + */ + int currentSegment (double[] coords); +} diff --git a/src/main/java/pythagoras/d/Point.java b/src/main/java/pythagoras/d/Point.java new file mode 100644 index 0000000..0ef753e --- /dev/null +++ b/src/main/java/pythagoras/d/Point.java @@ -0,0 +1,79 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +import java.io.Serializable; + +/** + * Represents a point on a plane. + */ +public class Point extends AbstractPoint implements Serializable +{ + /** The x-coordinate of the point. */ + public double x; + + /** The y-coordinate of the point. */ + public double y; + + /** + * Constructs a point at (0, 0). + */ + public Point () { + } + + /** + * Constructs a point at the specified coordinates. + */ + public Point (double x, double y) { + setLocation(x, y); + } + + /** + * Constructs a point with coordinates equal to the supplied point. + */ + public Point (IPoint p) { + setLocation(p.getX(), p.getY()); + } + + /** + * Sets the coordinates of this point to be equal to those of the supplied point. + */ + public void setLocation (IPoint p) { + setLocation(p.getX(), p.getY()); + } + + /** + * Sets the coordinates of this point to the supplied values. + */ + public void setLocation (double x, double y) { + this.x = x; + this.y = y; + } + + /** + * A synonym for {@link #setLocation}. + */ + public void move (double x, double y) { + setLocation(x, y); + } + + /** + * Translates this point by the specified offset. + */ + public void translate (double dx, double dy) { + x += dx; + y += dy; + } + + @Override // from interface IPoint + public double getX () { + return x; + } + + @Override // from interface IPoint + public double getY () { + return y; + } +} diff --git a/src/main/java/pythagoras/d/Points.java b/src/main/java/pythagoras/d/Points.java new file mode 100644 index 0000000..70dddd2 --- /dev/null +++ b/src/main/java/pythagoras/d/Points.java @@ -0,0 +1,40 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * Point-related utility methods. + */ +public class Points +{ + /** + * Returns the squared Euclidian distance between the specified two points. + */ + public static double distanceSq (double x1, double y1, double x2, double y2) { + x2 -= x1; + y2 -= y1; + return x2 * x2 + y2 * y2; + } + + /** + * Returns the Euclidian distance between the specified two points. + */ + public static double distance (double x1, double y1, double x2, double y2) { + return Math.sqrt(distanceSq(x1, y1, x2, y2)); + } + + /** + * Returns a string describing the supplied point, of the form +x+y, + * +x-y, -x-y, etc. + */ + public static String pointToString (double x, double y) { + StringBuilder buf = new StringBuilder(); + if (x >= 0) buf.append("+"); + buf.append(x); + if (y >= 0) buf.append("+"); + buf.append(y); + return buf.toString(); + } +} diff --git a/src/main/java/pythagoras/d/QuadCurve.java b/src/main/java/pythagoras/d/QuadCurve.java new file mode 100644 index 0000000..9ba01a7 --- /dev/null +++ b/src/main/java/pythagoras/d/QuadCurve.java @@ -0,0 +1,122 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +import java.io.Serializable; + +/** + * Represents a quadratic curve. + */ +public class QuadCurve extends AbstractQuadCurve implements Serializable +{ + /** The x-coordinate of the start of this curve. */ + public double x1; + + /** The y-coordinate of the start of this curve. */ + public double y1; + + /** The x-coordinate of the control point. */ + public double ctrlx; + + /** The y-coordinate of the control point. */ + public double ctrly; + + /** The x-coordinate of the end of this curve. */ + public double x2; + + /** The y-coordinate of the end of this curve. */ + public double y2; + + /** + * Creates a quad curve with all points at (0,0). + */ + public QuadCurve () { + } + + /** + * Creates a quad curve with the specified start, control, and end points. + */ + public QuadCurve (double x1, double y1, double ctrlx, double ctrly, double x2, double y2) { + setCurve(x1, y1, ctrlx, ctrly, x2, y2); + } + + /** + * Configures the start, control, and end points for this curve. + */ + public void setCurve (double x1, double y1, double ctrlx, double ctrly, double x2, double y2) { + this.x1 = x1; + this.y1 = y1; + this.ctrlx = ctrlx; + this.ctrly = ctrly; + this.x2 = x2; + this.y2 = y2; + } + + /** + * Configures the start, control, and end points for this curve. + */ + public void setCurve (IPoint p1, IPoint cp, IPoint p2) { + setCurve(p1.getX(), p1.getY(), cp.getX(), cp.getY(), p2.getX(), p2.getY()); + } + + /** + * Configures the start, control, and end points for this curve, using the values at the + * specified offset in the {@link coords} array. + */ + public void setCurve (double[] coords, int offset) { + setCurve(coords[offset + 0], coords[offset + 1], + coords[offset + 2], coords[offset + 3], + coords[offset + 4], coords[offset + 5]); + } + + /** + * Configures the start, control, and end points for this curve, using the values at the + * specified offset in the {@link points} array. + */ + public void setCurve (IPoint[] points, int offset) { + setCurve(points[offset + 0].getX(), points[offset + 0].getY(), + points[offset + 1].getX(), points[offset + 1].getY(), + points[offset + 2].getX(), points[offset + 2].getY()); + } + + /** + * Configures the start, control, and end points for this curve to be the same as the supplied + * curve. + */ + public void setCurve (IQuadCurve curve) { + setCurve(curve.getX1(), curve.getY1(), curve.getCtrlX(), curve.getCtrlY(), + curve.getX2(), curve.getY2()); + } + + @Override // from interface IQuadCurve + public double getX1 () { + return x1; + } + + @Override // from interface IQuadCurve + public double getY1 () { + return y1; + } + + @Override // from interface IQuadCurve + public double getCtrlX () { + return ctrlx; + } + + @Override // from interface IQuadCurve + public double getCtrlY () { + return ctrly; + } + + @Override // from interface IQuadCurve + public double getX2 () { + return x2; + } + + @Override // from interface IQuadCurve + public double getY2 () { + return y2; + } +} diff --git a/src/main/java/pythagoras/d/QuadCurves.java b/src/main/java/pythagoras/d/QuadCurves.java new file mode 100644 index 0000000..57b0a53 --- /dev/null +++ b/src/main/java/pythagoras/d/QuadCurves.java @@ -0,0 +1,94 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * Quad curve-related utility methods. + */ +public class QuadCurves +{ + public static double getFlatnessSq (double x1, double y1, double ctrlx, double ctrly, + double x2, double y2) { + return Lines.pointSegDistSq(ctrlx, ctrly, x1, y1, x2, y2); + } + + public static double getFlatnessSq (double[] coords, int offset) { + return Lines.pointSegDistSq(coords[offset + 2], coords[offset + 3], + coords[offset + 0], coords[offset + 1], + coords[offset + 4], coords[offset + 5]); + } + + public static double getFlatness (double x1, double y1, double ctrlx, double ctrly, + double x2, double y2) { + return Lines.pointSegDist(ctrlx, ctrly, x1, y1, x2, y2); + } + + public static double getFlatness (double[] coords, int offset) { + return Lines.pointSegDist(coords[offset + 2], coords[offset + 3], + coords[offset + 0], coords[offset + 1], + coords[offset + 4], coords[offset + 5]); + } + + public static void subdivide (IQuadCurve src, QuadCurve left, QuadCurve right) { + double x1 = src.getX1(); + double y1 = src.getY1(); + double cx = src.getCtrlX(); + double cy = src.getCtrlY(); + double x2 = src.getX2(); + double y2 = src.getY2(); + double cx1 = (x1 + cx) / 2f; + double cy1 = (y1 + cy) / 2f; + double cx2 = (x2 + cx) / 2f; + double cy2 = (y2 + cy) / 2f; + cx = (cx1 + cx2) / 2f; + cy = (cy1 + cy2) / 2f; + if (left != null) { + left.setCurve(x1, y1, cx1, cy1, cx, cy); + } + if (right != null) { + right.setCurve(cx, cy, cx2, cy2, x2, y2); + } + } + + public static void subdivide (double[] src, int srcoff, + double[] left, int leftOff, double[] right, int rightOff) { + double x1 = src[srcoff + 0]; + double y1 = src[srcoff + 1]; + double cx = src[srcoff + 2]; + double cy = src[srcoff + 3]; + double x2 = src[srcoff + 4]; + double y2 = src[srcoff + 5]; + double cx1 = (x1 + cx) / 2f; + double cy1 = (y1 + cy) / 2f; + double cx2 = (x2 + cx) / 2f; + double cy2 = (y2 + cy) / 2f; + cx = (cx1 + cx2) / 2f; + cy = (cy1 + cy2) / 2f; + if (left != null) { + left[leftOff + 0] = x1; + left[leftOff + 1] = y1; + left[leftOff + 2] = cx1; + left[leftOff + 3] = cy1; + left[leftOff + 4] = cx; + left[leftOff + 5] = cy; + } + if (right != null) { + right[rightOff + 0] = cx; + right[rightOff + 1] = cy; + right[rightOff + 2] = cx2; + right[rightOff + 3] = cy2; + right[rightOff + 4] = x2; + right[rightOff + 5] = y2; + } + } + + public static int solveQuadratic (double[] eqn) { + return solveQuadratic(eqn, eqn); + } + + public static int solveQuadratic (double[] eqn, double[] res) { + return Crossing.solveQuad(eqn, res); + } +} diff --git a/src/main/java/pythagoras/d/Rectangle.java b/src/main/java/pythagoras/d/Rectangle.java new file mode 100644 index 0000000..d014c1b --- /dev/null +++ b/src/main/java/pythagoras/d/Rectangle.java @@ -0,0 +1,188 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +import java.io.Serializable; + +/** + * Represents an area in two dimensions. + */ +public class Rectangle extends AbstractRectangle implements Serializable +{ + /** The x-coordinate of the rectangle's upper left corner. */ + public double x; + + /** The y-coordinate of the rectangle's upper left corner. */ + public double y; + + /** The width of the rectangle. */ + public double width; + + /** The height of the rectangle. */ + public double height; + + /** + * Constructs a rectangle at (0,0) and with dimensions (0,0). + */ + public Rectangle () { + } + + /** + * Constructs a rectangle with the supplied upper-left corner and dimensions (0,0). + */ + public Rectangle (IPoint p) { + setBounds(p.getX(), p.getY(), 0, 0); + } + + /** + * Constructs a rectangle with upper-left corner at (0,) and the supplied dimensions. + */ + public Rectangle (IDimension d) { + setBounds(0, 0, d.getWidth(), d.getHeight()); + } + + /** + * Constructs a rectangle with upper-left corner at the supplied point and with the supplied + * dimensions. + */ + public Rectangle (IPoint p, IDimension d) { + setBounds(p.getX(), p.getY(), d.getWidth(), d.getHeight()); + } + + /** + * Constructs a rectangle with the specified upper-left corner and dimensions. + */ + public Rectangle (double x, double y, double width, double height) { + setBounds(x, y, width, height); + } + + /** + * Constructs a rectangle with bounds equal to the supplied rectangle. + */ + public Rectangle (IRectangle r) { + setBounds(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + /** + * Sets the upper-left corner of this rectangle to the specified point. + */ + public void setLocation (double x, double y) { + this.x = x; + this.y = y; + } + + /** + * Sets the upper-left corner of this rectangle to the supplied point. + */ + public void setLocation (IPoint p) { + setLocation(p.getX(), p.getY()); + } + + /** + * Sets the size of this rectangle to the specified dimensions. + */ + public void setSize (double width, double height) { + this.width = width; + this.height = height; + } + + /** + * Sets the size of this rectangle to the supplied dimensions. + */ + public void setSize (Dimension d) { + setSize(d.width, d.height); + } + + /** + * Sets the bounds of this rectangle to the specified bounds. + */ + public void setBounds (double x, double y, double width, double height) { + this.x = x; + this.y = y; + this.height = height; + this.width = width; + } + + /** + * Sets the bounds of this rectangle to those of the supplied rectangle. + */ + public void setBounds (IRectangle r) { + setBounds(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + /** + * Grows the bounds of this rectangle by the specified amount (i.e. the upper-left corner moves + * by the specified amount in the negative x and y direction and the width and height grow by + * twice the specified amount). + */ + public void grow (double dx, double dy) { + x -= dx; + y -= dy; + width += dx + dx; + height += dy + dy; + } + + /** + * Translates the upper-left corner of this rectangle by the specified amount. + */ + public void translate (double mx, double my) { + x += mx; + y += my; + } + + /** + * Expands the bounds of this rectangle to contain the specified point. + */ + public void add (double px, double py) { + double x1 = Math.min(x, px); + double x2 = Math.max(x + width, px); + double y1 = Math.min(y, py); + double y2 = Math.max(y + height, py); + setBounds(x1, y1, x2 - x1, y2 - y1); + } + + /** + * Expands the bounds of this rectangle to contain the supplied point. + */ + public void add (IPoint p) { + add(p.getX(), p.getY()); + } + + /** + * Expands the bounds of this rectangle to contain the supplied rectangle. + */ + public void add (IRectangle r) { + double x1 = Math.min(x, r.getX()); + double x2 = Math.max(x + width, r.getX() + r.getWidth()); + double y1 = Math.min(y, r.getY()); + double y2 = Math.max(y + height, r.getY() + r.getHeight()); + setBounds(x1, y1, x2 - x1, y2 - y1); + } + + @Override // from interface IRectangularShape + public double getX () { + return x; + } + + @Override // from interface IRectangularShape + public double getY () { + return y; + } + + @Override // from interface IRectangularShape + public double getWidth () { + return width; + } + + @Override // from interface IRectangularShape + public double getHeight () { + return height; + } + + @Override // from RectangularShape + public void setFrame (double x, double y, double width, double height) { + setBounds(x, y, width, height); + } +} diff --git a/src/main/java/pythagoras/d/Rectangles.java b/src/main/java/pythagoras/d/Rectangles.java new file mode 100644 index 0000000..726d8aa --- /dev/null +++ b/src/main/java/pythagoras/d/Rectangles.java @@ -0,0 +1,33 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * Rectangle-related utility methods. + */ +public class Rectangles +{ + /** + * Intersects the supplied two rectangles, writing the result into {@link dst}. + */ + public static void intersect (IRectangle src1, IRectangle src2, Rectangle dst) { + double x1 = Math.max(src1.getMinX(), src2.getMinX()); + double y1 = Math.max(src1.getMinY(), src2.getMinY()); + double x2 = Math.min(src1.getMaxX(), src2.getMaxX()); + double y2 = Math.min(src1.getMaxY(), src2.getMaxY()); + dst.setBounds(x1, y1, x2 - x1, y2 - y1); + } + + /** + * Unions the supplied two rectangles, writing the result into {@link dst}. + */ + public static void union (IRectangle src1, IRectangle src2, Rectangle dst) { + double x1 = Math.min(src1.getMinX(), src2.getMinX()); + double y1 = Math.min(src1.getMinY(), src2.getMinY()); + double x2 = Math.max(src1.getMaxX(), src2.getMaxX()); + double y2 = Math.max(src1.getMaxY(), src2.getMaxY()); + dst.setBounds(x1, y1, x2 - x1, y2 - y1); + } +} diff --git a/src/main/java/pythagoras/d/RectangularShape.java b/src/main/java/pythagoras/d/RectangularShape.java new file mode 100644 index 0000000..7790279 --- /dev/null +++ b/src/main/java/pythagoras/d/RectangularShape.java @@ -0,0 +1,158 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * The base class for various {@link Shape} objects whose geometry is defined by a rectangular + * frame. + */ +public abstract class RectangularShape implements IRectangularShape +{ + /** + * Sets the location and size of the framing rectangle of this shape to the specified values. + */ + public abstract void setFrame (double x, double y, double width, double height); + + /** + * Sets the location and size of the framing rectangle of this shape to the supplied values. + */ + public void setFrame (IPoint loc, IDimension size) { + setFrame(loc.getX(), loc.getY(), size.getWidth(), size.getHeight()); + } + + /** + * Sets the location and size of the framing rectangle of this shape to be equal to the + * supplied rectangle. + */ + public void setFrame (IRectangle r) { + setFrame(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + /** + * Sets the location and size of the framing rectangle of this shape based on the specified + * diagonal line. + */ + public void setFrameFromDiagonal (double x1, double y1, double x2, double y2) { + double rx, ry, rw, rh; + if (x1 < x2) { + rx = x1; + rw = x2 - x1; + } else { + rx = x2; + rw = x1 - x2; + } + if (y1 < y2) { + ry = y1; + rh = y2 - y1; + } else { + ry = y2; + rh = y1 - y2; + } + setFrame(rx, ry, rw, rh); + } + + /** + * Sets the location and size of the framing rectangle of this shape based on the supplied + * diagonal line. + */ + public void setFrameFromDiagonal (IPoint p1, IPoint p2) { + setFrameFromDiagonal(p1.getX(), p1.getY(), p2.getX(), p2.getY()); + } + + /** + * Sets the location and size of the framing rectangle of this shape based on the specified + * center and corner points. + */ + public void setFrameFromCenter (double centerX, double centerY, + double cornerX, double cornerY) { + double width = Math.abs(cornerX - centerX); + double height = Math.abs(cornerY - centerY); + setFrame(centerX - width, centerY - height, width * 2, height * 2); + } + + /** + * Sets the location and size of the framing rectangle of this shape based on the supplied + * center and corner points. + */ + public void setFrameFromCenter (IPoint center, IPoint corner) { + setFrameFromCenter(center.getX(), center.getY(), corner.getX(), corner.getY()); + } + + @Override // from IRectangularShape + public double getMinX () { + return getX(); + } + + @Override // from IRectangularShape + public double getMinY () { + return getY(); + } + + @Override // from IRectangularShape + public double getMaxX () { + return getX() + getWidth(); + } + + @Override // from IRectangularShape + public double getMaxY () { + return getY() + getHeight(); + } + + @Override // from IRectangularShape + public double getCenterX () { + return getX() + getWidth() / 2; + } + + @Override // from IRectangularShape + public double getCenterY () { + return getY() + getHeight() / 2; + } + + @Override // from IRectangularShape + public Rectangle getFrame () { + return getBounds(); + } + + @Override // from IRectangularShape + public Rectangle getFrame (Rectangle target) { + return getBounds(target); + } + + @Override // from interface IShape + public boolean isEmpty () { + return getWidth() <= 0 || getHeight() <= 0; + } + + @Override // from interface IShape + public boolean contains (IPoint point) { + return contains(point.getX(), point.getY()); + } + + @Override // from interface IShape + public boolean contains (IRectangle rect) { + return contains(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight()); + } + + @Override // from interface IShape + public boolean intersects (IRectangle rect) { + return intersects(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight()); + } + + @Override // from interface IShape + public Rectangle getBounds () { + return getBounds(new Rectangle()); + } + + @Override // from interface IShape + public Rectangle getBounds (Rectangle target) { + target.setBounds(getX(), getY(), getWidth(), getHeight()); + return target; + } + + @Override // from interface IShape + public PathIterator getPathIterator (AffineTransform t, double flatness) { + return new FlatteningPathIterator(getPathIterator(t), flatness); + } +} diff --git a/src/main/java/pythagoras/d/RoundRectangle.java b/src/main/java/pythagoras/d/RoundRectangle.java new file mode 100644 index 0000000..fbdb2a5 --- /dev/null +++ b/src/main/java/pythagoras/d/RoundRectangle.java @@ -0,0 +1,102 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +import java.io.Serializable; + +/** + * Represents a rectangle with rounded corners, defined by an arc width and height. + */ +public class RoundRectangle extends AbstractRoundRectangle implements Serializable +{ + /** The x-coordinate of the framing rectangle. */ + public double x; + + /** The y-coordinate of the framing rectangle. */ + public double y; + + /** The width of the framing rectangle. */ + public double width; + + /** The height of the framing rectangle. */ + public double height; + + /** The width of the arc that defines the rounded corners. */ + public double arcwidth; + + /** The height of the arc that defines the rounded corners. */ + public double archeight; + + /** + * Creates a rounded rectangle with frame (0x0+0+0) and corners of size (0x0). + */ + public RoundRectangle () { + } + + /** + * Creates a rounded rectangle with the specified frame and corner dimensions. + */ + public RoundRectangle (double x, double y, double width, double height, + double arcwidth, double archeight) { + setRoundRect(x, y, width, height, arcwidth, archeight); + } + + /** + * Sets the frame and corner dimensions of this rectangle to the specified values. + */ + public void setRoundRect (double x, double y, double width, double height, + double arcwidth, double archeight) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.arcwidth = arcwidth; + this.archeight = archeight; + } + + /** + * Sets the frame and corner dimensions of this rectangle to be equal to those of the supplied + * rectangle. + */ + public void setRoundRect (IRoundRectangle rr) { + setRoundRect(rr.getX(), rr.getY(), rr.getWidth(), rr.getHeight(), + rr.getArcWidth(), rr.getArcHeight()); + } + + @Override // from interface IRoundRectangle + public double getArcWidth () { + return arcwidth; + } + + @Override // from interface IRoundRectangle + public double getArcHeight () { + return archeight; + } + + @Override // from interface IRectangularShape + public double getX () { + return x; + } + + @Override // from interface IRectangularShape + public double getY () { + return y; + } + + @Override // from interface IRectangularShape + public double getWidth () { + return width; + } + + @Override // from interface IRectangularShape + public double getHeight () { + return height; + } + + @Override // from RoundRectangle + public void setFrame (double x, double y, double width, double height) { + setRoundRect(x, y, width, height, arcwidth, archeight); + } +}