/*******************************************************************************
* Copyright (c) 2011, 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:
* Alexander Nyßen (itemis AG) - initial API and implementation
* Matthias Wienand (itemis AG) - contribution for Bugzilla #355997
*
*******************************************************************************/
package org.eclipse.gef.geometry.planar;
import java.util.Arrays;
import org.eclipse.gef.geometry.internal.utils.PointListUtils;
import org.eclipse.gef.geometry.internal.utils.PrecisionUtils;
/**
* Represents the geometric shape of a polyline.
*
* Note that while all manipulations (e.g. within shrink, expand) within this
* class are based on double precision, all comparisons (e.g. within contains,
* intersects, equals, etc.) are based on a limited precision (with an accuracy
* defined within {@link PrecisionUtils}) to compensate for rounding effects.
*
* @author anyssen
* @author mwienand
*
*/
public class Polyline extends AbstractPointListBasedGeometry<Polyline>
implements ICurve {
private static final long serialVersionUID = 1L;
/**
* Constructs a new {@link Polyline} from a even-numbered sequence of
* coordinates. Similar to {@link Polyline#Polyline(Point...)}, only that
* coordinates of points rather than {@link Point}s are provided.
*
* @param coordinates
* an alternating, even-numbered sequence of x- and
* y-coordinates, representing the points from which the
* {@link Polyline} is to be created
*/
public Polyline(double... coordinates) {
super(coordinates);
}
/**
* Constructs a new {@link Polyline} from the given array of {@link Line}
* segments.
*
* @param segmentsArray
* The array of {@link Line}s from which this {@link Polyline} is
* constructed.
*/
public Polyline(Line[] segmentsArray) {
super(PointListUtils.toPointsArray(segmentsArray, false));
}
/**
* Constructs a new {@link Polyline} from the given sequence of
* {@link Point} s. The {@link Polyline} that is created will be
* automatically closed, i.e. it will not only contain a segment between
* succeeding points of the sequence but as well back from the last to the
* first point.
*
* @param points
* a sequence of points, from which the {@link Polyline} is to be
* created
*/
public Polyline(Point... points) {
super(points);
}
/**
* Checks whether the point that is represented by its x- and y-coordinates
* is contained within this {@link Polyline}.
*
* @param x
* the x coordinate of the point to test
* @param y
* the y coordinate of the point to test
* @return <code>true</code> if the point represented by its coordinates if
* contained within this {@link Polyline}, otherwise
* <code>false</code>
*/
public boolean contains(double x, double y) {
return contains(new Point(x, y));
}
@Override
public boolean contains(Point p) {
for (int i = 0; i + 1 < points.length; i++) {
Point p1 = points[i];
Point p2 = points[i + 1];
if (new Line(p1, p2).contains(p)) {
return true;
}
}
return false;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o instanceof Polyline) {
Polyline p = (Polyline) o;
return equals(p.getPoints());
}
return false;
}
/**
* Checks whether this {@link Polyline} and the one that is indirectly given
* via the sequence of points are regarded to be equal. The {@link Polyline}
* s will be regarded equal, if they are characterized by the same segments.
*
* @param points
* an array of {@link Point} characterizing a {@link Polyline} to
* be checked for equality
* @return <code>true</code> if the sequence of points that characterize
* this {@link Polyline} and the {@link Polyline} indirectly given
* via the array of points are regarded to form the same segments
*/
public boolean equals(Point... points) {
if (points.length != this.points.length) {
return false;
}
return Arrays.equals(this.points, points)
|| Arrays.equals(this.points, Point.getReverseCopy(points));
}
@Override
public Polyline getCopy() {
return new Polyline(getPoints());
}
/**
* Returns a sequence of {@link Line}s, representing the segments that are
* obtained by linking each two successive point of this {@link Polyline}
* (including the last and the first one).
*
* @return an array of {@link Line}s, representing the segments that make up
* this {@link Polyline}
*/
public Line[] getCurves() {
return PointListUtils.toSegmentsArray(points, false);
}
@Override
public Point[] getIntersections(ICurve c) {
return CurveUtils.getIntersections(this, c);
}
/**
* Returns the length of this {@link Polyline} by adding up the lengths of
* the individual {@link Line}s.
*
* @return The length of this {@link Polyline}.
*/
// TODO: tests
public double getLength() {
double length = 0;
for (Line line : getCurves()) {
length += line.getLength();
}
return length;
}
@Override
public ICurve[] getOverlaps(ICurve c) {
return CurveUtils.getOverlaps(this, c);
}
@Override
public Point getP1() {
return points[0].getCopy();
}
@Override
public Point getP2() {
return points[points.length - 1].getCopy();
}
@Override
public Point getProjection(Point reference) {
double minDistance = 0;
Point minProjection = null;
for (BezierCurve bc : toBezier()) {
Point projection = bc.getProjection(reference);
double distance = projection.getDistance(reference);
if (minProjection == null || distance < minDistance) {
minProjection = projection;
minDistance = distance;
}
}
return minProjection;
}
@Override
public Polyline getTransformed(AffineTransform t) {
return new Polyline(t.getTransformed(points));
}
@Override
public double getX1() {
return getP1().x;
}
@Override
public double getX2() {
return getP2().x;
}
@Override
public double getY1() {
return getP1().y;
}
@Override
public double getY2() {
return getP2().y;
}
@Override
public boolean intersects(ICurve c) {
return CurveUtils.intersect(c, this);
}
@Override
public boolean overlaps(ICurve c) {
return CurveUtils.overlap(c, this);
}
@Override
public Line[] toBezier() {
return PointListUtils.toSegmentsArray(points, false);
}
@Override
public Path toPath() {
Path path = new Path();
if (points.length > 0) {
path.moveTo(points[0].x, points[0].y);
for (int i = 1; i < points.length; i++) {
path.lineTo(points[i].x, points[i].y);
}
}
return path;
}
/**
* Transforms this {@link Polyline} into a {@link PolyBezier}.
*
* @return a {@link PolyBezier} representing this {@link Polyline}
*/
public PolyBezier toPolyBezier() {
Line[] segments = PointListUtils.toSegmentsArray(points, false);
return new PolyBezier(segments);
}
@Override
public String toString() {
StringBuffer stringBuffer = new StringBuffer("Polyline: ");
if (points.length > 0) {
for (int i = 0; i < points.length; i++) {
stringBuffer
.append("(" + points[i].x + ", " + points[i].y + ")");
if (i < points.length - 1) {
stringBuffer.append(" -> ");
}
}
} else {
stringBuffer.append("<no points>");
}
return stringBuffer.toString();
}
}