/*******************************************************************************
* Copyright (c) 2000, 2016 IBM Corporation 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:
* IBM Corporation - initial API and implementation
* Alexander Nyßen (itemis AG) - migration do double precision
* Matthias Wienand (itemis AG) - contribution for Bugzilla #355997
* Colin Sharples - contribution for Bugzilla #460569, #491403
*
*******************************************************************************/
package org.eclipse.gef.geometry.planar;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import org.eclipse.gef.geometry.euclidean.Angle;
import org.eclipse.gef.geometry.euclidean.Straight;
import org.eclipse.gef.geometry.euclidean.Vector;
import org.eclipse.gef.geometry.internal.utils.PrecisionUtils;
/**
* Represents a point (x, y) in 2-dimensional space. This class provides various
* methods for manipulating this point or creating new derived geometrical
* objects.
*
* @author ebordeau
* @author rhudson
* @author pshah
* @author ahunter
* @author anyssen
* @author mwienand
*
*/
public class Point implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
private static Point[] eliminateDuplicates(Point... points) {
// sort points by x and y
Arrays.sort(points, 0, points.length, new Comparator<Point>() {
@Override
public int compare(Point p1, Point p2) {
if (p1.x < p2.x) {
return -1;
} else if (p1.x == p2.x && p1.y < p2.y) {
return -1;
} else if (p1.x == p2.x && p1.y == p2.y) {
return 0;
}
return 1;
}
});
// filter points
List<Point> uniquePoints = new ArrayList<>(points.length);
for (int i = 0; i < points.length - 1; i++) {
if (!points[i].equals(points[i + 1])) {
uniquePoints.add(points[i]);
}
}
uniquePoints.add(points[points.length - 1]);
return uniquePoints.toArray(new Point[] {});
}
/**
* Returns the smallest {@link Rectangle} that encloses all {@link Point}s
* in the given sequence. Note that the right and bottom borders of a
* {@link Rectangle} are regarded as being part of the {@link Rectangle}.
*
* @param points
* a sequence of {@link Point}s which should all be contained in
* the to be computed {@link Rectangle}
* @return a new {@link Rectangle}, which is the smallest {@link Rectangle}
* that contains all given {@link Point}s
*/
public static Rectangle getBounds(Point... points) {
// compute the top left and bottom right points by means of the maximum
// and minimum x and y coordinates
if (points.length == 0) {
return new Rectangle();
}
// calculate bounds
Point topLeft = points[0];
Point bottomRight = points[0];
for (Point p : points) {
topLeft = Point.min(topLeft, p);
bottomRight = Point.max(bottomRight, p);
}
return new Rectangle(topLeft, bottomRight);
}
/**
* Computes the centroid of the given {@link Point}s. The centroid is the
* "center of gravity", i.e. assuming the {@link Polygon} spanned by the
* {@link Point}s is made of a material of constant density, it will be in a
* balanced state, if you put it on a pin that is placed exactly on its
* centroid.
*
* @param points
* The array of {@link Point}s for which the centroid is
* computed.
* @return the center {@link Point} (or centroid) of the given {@link Point}
* s
*/
public static Point getCentroid(Point... points) {
if (points.length == 0) {
return null;
} else if (points.length == 1) {
return points[0].getCopy();
}
double cx = 0, cy = 0, a, sa = 0;
for (int i = 0; i < points.length - 1; i++) {
a = points[i].x * points[i + 1].y - points[i].y * points[i + 1].x;
sa += a;
cx += (points[i].x + points[i + 1].x) * a;
cy += (points[i].y + points[i + 1].y) * a;
}
// closing segment
a = points[points.length - 1].x * points[0].y
- points[points.length - 1].y * points[0].x;
sa += a;
cx += (points[points.length - 1].x + points[0].x) * a;
cy += (points[points.length - 1].y + points[0].y) * a;
if (sa == 0) {
return new Point(cx, cy);
} else {
return new Point(cx / (3 * sa), cy / (3 * sa));
}
}
/**
* Computes the convex hull of the given set of {@link Point}s using the
* Graham scan algorithm.
*
* @param points
* the set of {@link Point}s to calculate the convex hull for
* @return the convex hull of the given set of {@link Point}s
*/
public static Point[] getConvexHull(Point... points) {
// if we have up to three points, no calculation has to be performed, as
// there may be no inner points.
if (points.length <= 3) {
return Point.getCopy(points);
}
// Remove duplicate points from the given point list in order to be able
// to apply the Graham scan.
points = eliminateDuplicates(points);
// do a graham scan to find the convex hull of the given set of points
// move point with lowest y coordinate to first position
int minIdx = 0;
Point min = points[minIdx];
for (int i = 1; i < points.length; i++) {
if (points[i].y < min.y
|| points[i].y == min.y && points[i].x < min.x) {
min = points[i];
minIdx = i;
}
}
Point tmp = points[0];
points[0] = points[minIdx];
points[minIdx] = tmp;
// sort all but first points by the angle they have compared to the
// first position
final Point p0 = points[0];
Arrays.sort(points, 1, points.length, new Comparator<Point>() {
@Override
public int compare(Point p1, Point p2) {
double d = Straight.getSignedDistanceCCW(p0, p1, p2);
if (PrecisionUtils.equal(d, 0)) {
return 0;
}
return d < 0 ? -1 : 1;
}
});
// initialize stack with first three points
ArrayList<Point> convexHull = new ArrayList<>();
convexHull.add(points[2]);
convexHull.add(points[1]);
convexHull.add(points[0]);
// expand initial stack to full convex hull
for (int i = 3; i < points.length; i++) {
// do always turn right
while (convexHull.size() > 2
&& Straight.getSignedDistanceCCW(convexHull.get(1),
convexHull.get(0), points[i]) > 0) {
convexHull.remove(0);
}
convexHull.add(0, points[i]);
}
return convexHull.toArray(new Point[] {});
}
/**
* Copies an array of points, by copying each point contained in the array.
*
* @param points
* the array of {@link Point}s to copy
* @return a new array, which contains copies of the given {@link Point}s at
* the respective index positions
*/
public static final Point[] getCopy(Point... points) {
if (points == null) {
throw new IllegalArgumentException("points may not be null.");
}
Point[] copy = new Point[points.length];
for (int i = 0; i < points.length; i++) {
copy[i] = points[i].getCopy();
}
return copy;
}
/**
* Returns a copy of the given array of points, where the points are placed
* in reversed order.
*
* @param points
* the array of {@link Point}s to reverse
* @return a new array, which contains a copy of each {@link Point} of the
* given array of points at the respective reverse index
*/
public static final Point[] getReverseCopy(Point... points) {
Point[] reversed = new Point[points.length];
for (int i = 0; i < points.length; i++) {
reversed[i] = points[points.length - i - 1].getCopy();
}
return reversed;
}
/**
* Creates a new Point representing the MAX of two provided Points.
*
* @param p1
* first point
* @param p2
* second point
* @return A new Point representing the Max()
*/
public static Point max(Point p1, Point p2) {
return new Point(Math.max(p1.x, p2.x), Math.max(p1.y, p2.y));
}
/**
* Creates a new Point representing the MIN of two provided Points.
*
* @param p1
* first point
* @param p2
* second point
* @return A new Point representing the Min()
*/
public static Point min(Point p1, Point p2) {
return new Point(Math.min(p1.x, p2.x), Math.min(p1.y, p2.y));
}
/**
* Returns (one of) the candidate(s) with minimal distance to the given
* reference point.
*
* @param referencePoint
* The reference point, to which distance has to be minimal.
* @param candidates
* The points from which to choose the one with minimal distance.
* @return One of the candidates with minimal distance.
*/
public static Point nearest(Point referencePoint, Point... candidates) {
if (candidates.length == 0) {
return null;
}
// find nearest point
Point nearest = candidates[0];
double minDistance = referencePoint.getDistance(nearest);
for (int i = 1; i < candidates.length; i++) {
double d = referencePoint.getDistance(candidates[i]);
if (d < minDistance) {
minDistance = d;
nearest = candidates[i];
}
}
return nearest;
}
/**
* Rotates (in-place) the given {@link Point}s counter-clock-wise (CCW) by
* the specified {@link Angle} around the given center {@link Point}.
*
* @param points
* The array of {@link Point}s to rotate.
* @param angle
* The rotation {@link Angle}.
* @param cx
* The x-coordinate of the rotation's pivot point.
* @param cy
* The y-coordinate of the rotation's pivot point.
*/
public static void rotateCCW(Point[] points, Angle angle, double cx,
double cy) {
translate(points, -cx, -cy);
for (Point p : points) {
Point np = new Vector(p).rotateCCW(angle).toPoint();
p.x = np.x;
p.y = np.y;
}
translate(points, cx, cy);
}
/**
* Rotates (in-place) the given {@link Point}s clock-wise (CW) by the
* specified {@link Angle} around the given center {@link Point}.
*
* @param points
* The array of {@link Point}s to rotate.
* @param angle
* The rotation {@link Angle}.
* @param cx
* The x-coordinate of the rotation's pivot point.
* @param cy
* The y-coordinate of the rotation's pivot point.
*/
public static void rotateCW(Point[] points, Angle angle, double cx,
double cy) {
translate(points, -cx, -cy);
for (Point p : points) {
Point np = new Vector(p).rotateCW(angle).toPoint();
p.x = np.x;
p.y = np.y;
}
translate(points, cx, cy);
}
/**
* Scales the given array of {@link Point}s by the given x and y scale
* factors around the given center {@link Point} (cx, cy).
*
* @param points
* The array of {@link Point}s to scale.
* @param fx
* The horizontal scale factor.
* @param fy
* Ther vertical scale factor.
* @param cx
* The x-coordinate of the scale's pivot point.
* @param cy
* The y-coordinate of the scale's pivot point.
*/
public static void scale(Point[] points, double fx, double fy, double cx,
double cy) {
translate(points, -cx, -cy);
for (Point p : points) {
p.scale(fx, fy);
}
translate(points, cx, cy);
}
/**
* Translates an array of {@link Point}s by translating each individual
* point by a given x and y offset.
*
* @param points
* an array of points to translate
* @param dx
* the x offset to translate each {@link Point} by
* @param dy
* the y offset to translate each {@link Point} by
*/
public static void translate(Point[] points, double dx, double dy) {
for (int i = 0; i < points.length; i++) {
points[i].x += dx;
points[i].y += dy;
}
}
/**
* The x value.
*/
public double x;
/**
* The y value.
*/
public double y;
/**
* Constructs a Point at location (0,0).
*
*/
public Point() {
}
/**
* Constructs a Point at the specified x and y locations.
*
* @param x
* x value
* @param y
* y value
*/
public Point(double x, double y) {
if (Double.isNaN(x)) {
throw new IllegalArgumentException(
"x coordinate has to be different from NaN.");
}
if (Double.isNaN(y)) {
throw new IllegalArgumentException(
"y coordinate has to be different from NaN.");
}
this.x = x;
this.y = y;
}
/**
* Constructs a Point at the same location as the given Point.
*
* @param p
* Point from which the initial values are taken.
*/
public Point(Point p) {
this(p.x, p.y);
}
/**
* Overwritten with public visibility as proposed in {@link Cloneable}.
*/
@Override
public Point clone() {
return getCopy();
}
/**
* Returns <code>true</code> if this Points x and y are equal to the given x
* and y.
*
* @param x
* the x value
* @param y
* the y value
* @return <code>true</code> if this point's x and y are equal to those
* given.
*/
public boolean equals(double x, double y) {
return PrecisionUtils.equal(this.x, x)
&& PrecisionUtils.equal(this.y, y);
}
/**
* Test for equality.
*
* @param o
* Object being tested for equality
* @return true if both x and y values are equal
*/
@Override
public boolean equals(Object o) {
if (o instanceof Point) {
Point p = (Point) o;
return equals(p.x, p.y);
}
return false;
}
/**
* @return a copy of this Point
*/
public Point getCopy() {
return new Point(this);
}
/**
* Returns the translation required to move this {@link Point} to the other
* {@link Point}
*
* @param other
* the {@link Point} to get the difference from
* @return the translation required to move this {@link Point} to the other
* {@link Point}
*/
public Point getDifference(Point other) {
return new Point(other.x - x, other.y - y);
}
/**
* Calculates the distance from this Point to the one specified.
*
* @param p
* The Point being compared to this
* @return The distance
*/
public double getDistance(Point p) {
double i = p.x - x;
double j = p.y - y;
return Math.sqrt(i * i + j * j);
}
/**
* Creates a Point with negated x and y values.
*
* @return A new Point
*/
public Point getNegated() {
return getCopy().negate();
}
/**
* Creates a new Point from this Point by scaling by the specified amount.
*
* @param factor
* scale factor
* @return A new Point
*/
public Point getScaled(double factor) {
return getCopy().scale(factor);
}
/**
* Creates a new Point from this Point by scaling by the specified values.
*
* @param xFactor
* horizontal scale factor
* @param yFactor
* vertical scale factor
* @return A new Point
*/
public Point getScaled(double xFactor, double yFactor) {
return getCopy().scale(xFactor, yFactor);
}
/**
* Returns a new {@link Point} scaled by the given scale-factors. The
* scaling is performed relative to the given {@link Point} center.
*
* @param factorX
* The horizontal scale-factor
* @param factorY
* The vertical scale-factor
* @param center
* The relative {@link Point} for the scaling
* @return The new, scaled {@link Point}
*/
public Point getScaled(double factorX, double factorY, Point center) {
return getCopy().scale(factorX, factorY, center);
}
/**
* Transforms a copy of this {@link Point} using the given
* {@link AffineTransform}.
*
* @param transformation
* The {@link AffineTransform} to apply.
* @return A copy of this {@link Point}, transformed by the given
* {@link AffineTransform}.
*/
public Point getTransformed(AffineTransform transformation) {
return transformation.getTransformed(this);
}
/**
* Creates a new Point which is translated by the values of the input
* Dimension.
*
* @param d
* Dimension which provides the translation amounts.
* @return A new Point
*/
public Point getTranslated(Dimension d) {
return getCopy().translate(d);
}
/**
* Creates a new Point which is translated by the specified x and y values
*
* @param x
* horizontal component
* @param y
* vertical component
* @return A new Point
*/
public Point getTranslated(double x, double y) {
return getCopy().translate(x, y);
}
/**
* Creates a new Point which is translated by the values of the provided
* Point.
*
* @param p
* Point which provides the translation amounts.
* @return A new Point
*/
public Point getTranslated(Point p) {
return getCopy().translate(p);
}
/**
* Creates a new Point with the transposed values of this Point. Can be
* useful in orientation change calculations.
*
* @return A new Point
*/
public Point getTransposed() {
return getCopy().transpose();
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
// calculating a better hashCode is not possible, because due to the
// imprecision, equals() is no longer transitive
return 0;
}
/**
* Negates the x and y values of this Point.
*
* @return <code>this</code> for convenience
*/
public Point negate() {
scale(-1.0d);
return this;
}
/**
* Scales this Point by the specified amount.
*
* @return <code>this</code> for convenience
* @param factor
* scale factor
*/
public Point scale(double factor) {
return scale(factor, factor);
}
/**
* Scales this Point by the specified values.
*
* @param xFactor
* horizontal scale factor
* @param yFactor
* vertical scale factor
* @return <code>this</code> for convenience
*/
public Point scale(double xFactor, double yFactor) {
x *= xFactor;
y *= yFactor;
return this;
}
/**
* Scales this {@link Point} by the given scale-factors. The scaling is
* performed relative to the given {@link Point} center.
*
* @param factorX
* The horizontal scale-factor
* @param factorY
* The vertical scale-factor
* @param center
* The relative {@link Point} for the scaling
* @return <code>this</code> for convenience
*/
public Point scale(double factorX, double factorY, Point center) {
translate(center.getNegated());
scale(factorX, factorY);
translate(center);
return this;
}
/**
* Sets the location of this Point to the provided x and y locations.
*
* @return <code>this</code> for convenience
* @param x
* the x location
* @param y
* the y location
*/
public Point setLocation(double x, double y) {
this.x = x;
this.y = y;
return this;
}
/**
* Sets the location of this Point to the specified Point.
*
* @return <code>this</code> for convenience
* @param p
* the Location
*/
public Point setLocation(Point p) {
x = p.x;
y = p.y;
return this;
}
/**
* Sets the x value of this Point to the given value.
*
* @param x
* The new x value
* @return this for convenience
*/
public Point setX(double x) {
this.x = x;
return this;
}
/**
* Sets the y value of this Point to the given value;
*
* @param y
* The new y value
* @return this for convenience
*/
public Point setY(double y) {
this.y = y;
return this;
}
/**
* @see Object#toString()
*/
@Override
public String toString() {
return "Point(" + x + ", " + y + ")";//$NON-NLS-3$//$NON-NLS-2$//$NON-NLS-1$
}
/**
* Transforms this {@link Point} in-place using the given
* {@link AffineTransform}.
*
* @param transformation
* The {@link AffineTransform} to apply.
* @return <code>this</code> for convenience.
*/
public Point transform(AffineTransform transformation) {
Point transformed = transformation.getTransformed(this);
x = transformed.x;
y = transformed.y;
return this;
}
/**
* Shifts this Point by the values of the Dimension along each axis, and
* returns this for convenience.
*
* @param d
* Dimension by which the origin is being shifted.
* @return <code>this</code> for convenience
*/
public Point translate(Dimension d) {
return translate(d.width, d.height);
}
/**
* Shifts this Point by the values supplied along each axes, and returns
* this for convenience.
*
* @param dx
* Amount by which point is shifted along X axis.
* @param dy
* Amount by which point is shifted along Y axis.
* @return <code>this</code> for convenience
*/
public Point translate(double dx, double dy) {
x += dx;
y += dy;
return this;
}
/**
* Shifts the location of this Point by the location of the input Point
* along each of the axes, and returns this for convenience.
*
* @param p
* Point to which the origin is being shifted.
* @return <code>this</code> for convenience
*/
public Point translate(Point p) {
return translate(p.x, p.y);
}
/**
* Transposes this object. X and Y values are exchanged.
*
* @return <code>this</code> for convenience
*/
public Point transpose() {
double temp = x;
x = y;
y = temp;
return this;
}
/**
* Returns the x value of this Point.
*
* @return The current x value
*/
public double x() {
return x;
}
/**
* Returns the y value of this Point.
*
* @return The current y value
*/
public double y() {
return y;
}
}