/******************************************************************************* * Copyright (c) 2012, 2016 itemis AG and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Matthias Wienand (itemis AG) - initial API and implementation * *******************************************************************************/ package org.eclipse.gef.geometry.planar; import java.util.Arrays; import java.util.List; import org.eclipse.gef.geometry.euclidean.Angle; import org.eclipse.gef.geometry.internal.utils.PrecisionUtils; /** * A {@link CurvedPolygon} is an {@link IShape} with {@link BezierCurve} edges. * * @author mwienand * */ public class CurvedPolygon extends AbstractGeometry implements IShape, ITranslatable<CurvedPolygon>, IRotatable<CurvedPolygon>, IScalable<CurvedPolygon> { private static final long serialVersionUID = 1L; private BezierCurve[] edges; /** * Constructs a new {@link CurvedPolygon} from the given {@link BezierCurve} * s. Subsequent {@link BezierCurve}s need to be connected with each other * and the closing segment has to be supplied, too, otherwise an * {@link IllegalArgumentException} is thrown. * * @param curvedSides * the {@link BezierCurve}s representing the edges of the new * {@link CurvedPolygon} */ public CurvedPolygon(BezierCurve... curvedSides) { edges = new BezierCurve[curvedSides.length]; for (int i = 0; i < edges.length; i++) { BezierCurve c = curvedSides[i]; // assure that the curves are connected if (i == edges.length - 1) { if (!c.getP2().equals(edges[0].getP1())) { throw new IllegalArgumentException( "The last passed-in BezierCurve is not a closing segment. new CurvedPolygon(" + Arrays.asList(curvedSides) + ")"); } } else if (!c.getP2().equals(curvedSides[i + 1].getP1())) { throw new IllegalArgumentException( "Subsequent BezierCurves used to construct a CurvedPolygon need to be connected with each other. The " + i + "th and " + (i + 1) + "th passed-in BezierCurves violate this condition. new CurvedPolygon(" + Arrays.asList(curvedSides) + ")"); } edges[i] = c.getCopy(); edges[i].setP2( curvedSides[i == edges.length - 1 ? 0 : i + 1].getP1()); } } /** * Constructs a new {@link CurvedPolygon} from the given list of * {@link BezierCurve}s. Subsequent {@link BezierCurve}s need to be * connected with each other and the closing segment has to be supplied, * too, otherwise an {@link IllegalArgumentException} is thrown. * * @param curvedSides * the {@link BezierCurve}s representing the edges of the new * {@link CurvedPolygon} * * @see #CurvedPolygon(BezierCurve...) */ public CurvedPolygon(List<BezierCurve> curvedSides) { this(curvedSides.toArray(new BezierCurve[] {})); } private int computeLineWindingNumber(BezierCurve seg, Point p) { // seg left of p? double sx = seg.getX1(); double ex = seg.getX2(); if (sx < p.x && ex < p.x) { return 0; } // seg below or above p? double sy = seg.getY1(); double ey = seg.getY2(); if (sy < p.y && ey < p.y) { return 0; } if (sy > p.y && ey > p.y) { return 0; } // static x or y? if (sx == ex) { return ey >= sy ? 1 : -1; } if (sy == ey) { return 0; } // compute intersection double m = (ey - sy) / (ex - sx); double xi = (p.y - sy + m * sx) / m; // intersection left of p? if (p.x > xi) { return 0; } return ey >= sy ? 1 : -1; } /* * Computes the winding number for the Point relative to the BezierCurve. A * ray is cast from the Point, to the right, parallel to the x-axis. If the * BezierCurve crosses that ray from top to bottom, the winding number is * +1. If it crosses the ray from bottom to top, the winding number is -1. * Otherwise, 0 is returned. * * The algorithm implemented here is a generalized version of the one * realized within the sun.awt.geom.Curve#pointCrossingsFor*() methods. */ private int computeWindingNumber(BezierCurve seg, Point p) { if (isLinear(seg)) { int lineWindingNumber = computeLineWindingNumber(seg, p); // System.out.println("lineWindingNumber = " + lineWindingNumber); return lineWindingNumber; } // if the BezierCurve is left of p, above, or below p, than we can // unworried return 0 if (isLeftOfP(seg, p) || isAboveP(seg, p) || isBelowP(seg, p)) { return 0; } // if the BezierCurve is right of p, we have to check for a crossing if (isRightEqualP(seg, p)) { if (p.y >= seg.getY1() && p.y < seg.getY2()) { return 1; } if (p.y < seg.getY1() && p.y >= seg.getY2()) { return -1; } return 0; } // none of the return conditions evaluates to true, so we have to // subdivide the BezierCurve and check the left and right parts // individually BezierCurve[] split = seg.split(0.5); return computeWindingNumber(split[0], p) + computeWindingNumber(split[1], p); } @Override public boolean contains(IGeometry g) { return ShapeUtils.contains(this, g); } @Override public boolean contains(Point p) { if (edges.length == 0) { return false; } else if (edges.length == 1) { return edges[0].contains(p); } // compute the winding number for the given Point int w = 0; for (BezierCurve seg : edges) { if (seg.contains(p)) { return true; } w += computeWindingNumber(seg, p); } // the winding number is 0 if the Point is outside of this CurvedPolygon return w != 0; } @Override public Rectangle getBounds() { if (edges == null || edges.length == 0) { return new Rectangle(); } Rectangle bounds = edges[0].getBounds(); for (int i = 1; i < edges.length; i++) { bounds.union(edges[i].getBounds()); } return bounds; } /** * Returns an approximated center {@link Point} of this * {@link CurvedPolygon}. * * @return an approximated center {@link Point} of this * {@link CurvedPolygon} */ public Point getCenter() { Point[] edgeCenters = new Point[edges.length]; for (int i = 0; i < edges.length; i++) { edgeCenters[i] = Point.getCentroid(edges[i].getPoints()); } return Point.getCentroid(edgeCenters); } @Override public CurvedPolygon getCopy() { return new CurvedPolygon(edges); } @Override public PolyBezier getOutline() { return new PolyBezier(edges); } @Override public BezierCurve[] getOutlineSegments() { return CurveUtils.getCopy(edges); } @Override public CurvedPolygon getRotatedCCW(Angle angle) { Point c = getCenter(); return getRotatedCCW(angle, c.x, c.y); } @Override public CurvedPolygon getRotatedCCW(Angle angle, double cx, double cy) { return getCopy().rotateCCW(angle, cx, cy); } @Override public CurvedPolygon getRotatedCCW(Angle angle, Point center) { return getRotatedCCW(angle, center.x, center.y); } @Override public CurvedPolygon getRotatedCW(Angle angle) { Point c = getCenter(); return getRotatedCW(angle, c.x, c.y); } @Override public CurvedPolygon getRotatedCW(Angle angle, double cx, double cy) { return getCopy().rotateCW(angle, cx, cy); } @Override public CurvedPolygon getRotatedCW(Angle angle, Point center) { return getRotatedCW(angle, center.x, center.y); } @Override public CurvedPolygon getScaled(double factor) { Point c = getCenter(); return getScaled(factor, factor, c.x, c.y); } @Override public CurvedPolygon getScaled(double fx, double fy) { Point c = getCenter(); return getScaled(fx, fy, c.x, c.y); } @Override public CurvedPolygon getScaled(double factor, double cx, double cy) { return getScaled(factor, factor, cx, cy); } @Override public CurvedPolygon getScaled(double fx, double fy, double cx, double cy) { return getCopy().scale(fx, fy, cx, cy); } @Override public CurvedPolygon getScaled(double fx, double fy, Point center) { return getScaled(fx, fy, center.x, center.y); } @Override public CurvedPolygon getScaled(double factor, Point center) { return getScaled(factor, factor, center.x, center.y); } /** * @see IGeometry#getTransformed(AffineTransform) */ @Override public CurvedPolygon getTransformed(AffineTransform t) { BezierCurve[] transformed = new BezierCurve[edges.length]; for (int i = 0; i < edges.length; i++) { transformed[i] = edges[i].getTransformed(t); } return new CurvedPolygon(transformed); } @Override public CurvedPolygon getTranslated(double dx, double dy) { return getCopy().translate(dx, dy); } @Override public CurvedPolygon getTranslated(Point d) { return getTranslated(d.x, d.y); } private boolean isAboveP(BezierCurve seg, Point p) { for (Point cp : seg.getPoints()) { if (cp.y >= p.y) { return false; } } return true; } private boolean isBelowP(BezierCurve seg, Point p) { for (Point cp : seg.getPoints()) { if (cp.y <= p.y) { return false; } } return true; } private boolean isLeftOfP(BezierCurve seg, Point p) { for (Point cp : seg.getPoints()) { if (cp.x >= p.x) { return false; } } return true; } private boolean isLinear(BezierCurve seg) { double d0 = seg.getP1().getDistance(seg.getP2()); double d1 = 0; Point[] points = seg.getPoints(); for (int i = 0; i < points.length - 1; i++) { d1 += points[i].getDistance(points[i + 1]); } return PrecisionUtils.greaterEqual(d0, d1); } private boolean isRightEqualP(BezierCurve seg, Point p) { for (Point cp : seg.getPoints()) { if (cp.x < p.x) { return false; } } return true; } /** * Rotates this {@link CurvedPolygon} counter-clockwise (CCW) by the given * {@link Angle} around the center {@link Point} of this * {@link CurvedPolygon}. * * @param angle * the rotation {@link Angle} * @return <code>this</code> for convenience */ public CurvedPolygon rotateCCW(Angle angle) { Point c = getCenter(); return rotateCCW(angle, c.x, c.y); } /** * Rotates this {@link CurvedPolygon} counter-clockwise (CCW) by the given * {@link Angle} around the {@link Point} specified by the given x and y * coordinates. * * @param angle * the rotation {@link Angle} * @param cx * the x coordinate of the {@link Point} to rotate around * @param cy * the y coordinate of the {@link Point} to rotate around * @return <code>this</code> for convenience */ public CurvedPolygon rotateCCW(Angle angle, double cx, double cy) { for (BezierCurve c : edges) { c.rotateCCW(angle, cx, cy); } return this; } /** * Rotates this {@link CurvedPolygon} counter-clockwise (CCW) by the given * {@link Angle} around the given {@link Point}. * * @param angle * the rotation {@link Angle} * @param center * the {@link Point} to rotate around * @return <code>this</code> for convenience */ public CurvedPolygon rotateCCW(Angle angle, Point center) { return rotateCCW(angle, center.x, center.y); } /** * Rotates this {@link CurvedPolygon} counter-clockwise (CCW) by the given * {@link Angle} around the center {@link Point} of this * {@link CurvedPolygon}. * * @param angle * the rotation {@link Angle} * @return <code>this</code> for convenience */ public CurvedPolygon rotateCW(Angle angle) { Point c = getCenter(); return rotateCW(angle, c.x, c.y); } /** * Rotates this {@link CurvedPolygon} clockwise (CW) by the given * {@link Angle} around the {@link Point} specified by the given x and y * coordinates. * * @param angle * the rotation {@link Angle} * @param cx * the x coordinate of the {@link Point} to rotate around * @param cy * the y coordinate of the {@link Point} to rotate around * @return <code>this</code> for convenience */ public CurvedPolygon rotateCW(Angle angle, double cx, double cy) { for (BezierCurve c : edges) { c.rotateCW(angle, cx, cy); } return this; } /** * Rotates this {@link CurvedPolygon} clockwise (CW) by the given * {@link Angle} around the given {@link Point}. * * @param angle * the rotation {@link Angle} * @param center * the {@link Point} to rotate around * @return <code>this</code> for convenience */ public CurvedPolygon rotateCW(Angle angle, Point center) { return rotateCW(angle, center.x, center.y); } @Override public CurvedPolygon scale(double factor) { Point c = getCenter(); return scale(factor, factor, c.x, c.y); } @Override public CurvedPolygon scale(double fx, double fy) { Point c = getCenter(); return scale(fx, fx, c.x, c.y); } @Override public CurvedPolygon scale(double factor, double cx, double cy) { return scale(factor, factor, cx, cy); } @Override public CurvedPolygon scale(double fx, double fy, double cx, double cy) { for (BezierCurve c : edges) { c.scale(fx, fy, cx, cy); } return this; } @Override public CurvedPolygon scale(double fx, double fy, Point center) { return scale(fx, fx, center.x, center.y); } @Override public CurvedPolygon scale(double factor, Point center) { return scale(factor, factor, center.x, center.y); } @Override public Path toPath() { return CurveUtils.toPath(edges).close(); } @Override public String toString() { String s = "CurvedPolygon("; for (int i = 0; i < edges.length; i++) { if (i == edges.length - 1) { s = s + edges[i]; } else { s = s + edges[i] + " -> "; } } return s + ")"; } @Override public CurvedPolygon translate(double dx, double dy) { for (BezierCurve c : edges) { c.translate(dx, dy); } return this; } @Override public CurvedPolygon translate(Point d) { return translate(d.x, d.y); } }