### Eclipse Workspace Patch 1.0 #P org.eclipse.gef4.geometry Index: src/org/eclipse/gef4/geometry/Angle.java =================================================================== RCS file: src/org/eclipse/gef4/geometry/Angle.java diff -N src/org/eclipse/gef4/geometry/Angle.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/gef4/geometry/Angle.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,214 @@ +/******************************************************************************* + * Copyright (c) 2011 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.gef4.geometry; + +import java.io.Serializable; + +import org.eclipse.gef4.geometry.utils.PrecisionUtils; + +/** + * An {@link Angle} object abstracts the angle's unit. It provides a simple + * interface to construct it from degrees or from radians. Additionally, some + * useful calculations are implemented, but for sine/cosine/tangent calculations + * you may use the Math package. + * + * The {@link AngleUnit} enumeration is used to differentiate between degrees + * and radians. For the sake of simplicity, the methods that need to + * differentiate between the angle's unit are available twice. Expecting degrees + * or radians. + * + * Every {@link Angle} object is normalized. That means, you will never + * encounter an {@link Angle} object beyond 360°/2pi or below 0°/0. + * + * @author Matthias Wienand + */ +public class Angle implements Cloneable, Serializable { + + /** + * The {@link Angle#Angle(double, AngleUnit)} constructor uses this + * enumeration to differentiate the unit of its first argument. + * + * @author wienand + */ + public enum AngleUnit { + /** + * Specifies that the angle is given in degrees. The range of an angle + * in degrees is from 0° to 360°. + */ + DEG, + + /** + * Specifies that the angle is given in radians. The range of an angle + * in radians is from 0 to 2pi. + */ + RAD, + } + + private static final long serialVersionUID = 1L; + private static final double DEG_TO_RAD = Math.PI / 180d; + private static final double RAD_TO_DEG = 180d / Math.PI; + private static final double RAD_180 = Math.PI; + private static final double RAD_360 = 2 * Math.PI; + + /** + * Constructs a new {@link Angle} object representing the given value. The + * value is interpreted as being in degrees. + * + * @param degrees + * The angle in degrees. + * @return An {@link Angle} object representing the given degrees-angle. + */ + public static Angle fromDeg(double degrees) { + return new Angle(degrees, AngleUnit.DEG); + } + + /** + * Constructs a new {@link Angle} object representing the given value. The + * value is interpreted as being in radians. + * + * @param radians + * The angle in radians. + * @return An {@link Angle} object representing the given radians-angle. + */ + public static Angle fromRad(double radians) { + return new Angle(radians, AngleUnit.RAD); + } + + private double rad = 0d; + + /** + * Constructs a new {@link Angle} object with the given value. The + * {@link AngleUnit} u is used to differentiate the value's unit. + * + * @param v + * The angle's value. + * @param u + * The angle's unit. + */ + public Angle(double v, AngleUnit u) { + if (u == AngleUnit.DEG) { + v *= DEG_TO_RAD; + } + setRad(v); + } + + /** + * Overwritten with public visibility as proposed in {@link Cloneable}. + */ + @Override + public Angle clone() { + return getCopy(); + } + + /** + * Returns the value of this {@link Angle} object in degrees. + * + * @return This {@link Angle}'s value in degrees. + */ + public double deg() { + return rad * RAD_TO_DEG; + } + + @Override + public boolean equals(Object otherObj) { + Angle other = (Angle) otherObj; + return PrecisionUtils.equal(other.rad, this.rad); + } + + /** + * Returns the sum of this and the given other {@link Angle} object as a new + * {@link Angle} object. + * + * @param other + * @return The sum of this and the given other {@link Angle} object as a new + * {@link Angle} object. + */ + public Angle getAdded(Angle other) { + return Angle.fromRad(this.rad + other.rad); + } + + /** + * Creates and returns a copy of this {@link Angle}. + * + * @return a copy of this {@link Angle} + */ + public Angle getCopy() { + return Angle.fromRad(this.rad); + } + + /** + * Returns the opposite {@link Angle} of this {@link Angle} in a full circle + * as a new {@link Angle} object. + * + * @return The opposite {@link Angle} of this {@link Angle} in a full circle + * as a new {@link Angle} object. + */ + public Angle getOppositeFull() { + return Angle.fromRad(RAD_360 - rad); + } + + /** + * Returns the opposite {@link Angle} of this {@link Angle} in a semi-circle + * as a new {@link Angle} object. + * + * @return The opposite {@link Angle} of this {@link Angle} in a semi-circle + * as a new {@link Angle} object. + */ + public Angle getOppositeSemi() { + return Angle.fromRad(RAD_180 - rad); + } + + private Angle normalize() { + rad -= RAD_360 * Math.floor(rad / RAD_360); + return this; + } + + /** + * Returns this {@link Angle}'s value in radians. + * + * @return This {@link Angle}'s value in radians. + */ + public double rad() { + return rad; + } + + /** + * Sets this {@link Angle}'s value to the given angle in degrees. + * + * @param degrees + * The angle in degrees. + */ + public void setDeg(double degrees) { + rad = degrees * DEG_TO_RAD; + normalize(); + } + + /** + * Sets this {@link Angle}'s value to the given angle in radians. + * + * @param radians + * The angle value in radians. + */ + public void setRad(double radians) { + rad = radians; + normalize(); + } + + /** + * @see Object#toString() + */ + @Override + public String toString() { + return super.toString() + ": " + Double.toString(rad) + "rad (" + + Double.toString(deg()) + "deg)"; + } +} Index: src/org/eclipse/gef4/geometry/Dimension.java =================================================================== RCS file: /cvsroot/tools/org.eclipse.gef/GEF4/plugins/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/Dimension.java,v retrieving revision 1.3 diff -u -r1.3 Dimension.java --- src/org/eclipse/gef4/geometry/Dimension.java 20 Oct 2011 21:01:58 -0000 1.3 +++ src/org/eclipse/gef4/geometry/Dimension.java 21 Oct 2011 12:49:29 -0000 @@ -177,6 +177,7 @@ * the Object being tested for equality * @return true if the given object is equal to this dimension */ + @Override public boolean equals(Object o) { if (o instanceof Dimension) { Dimension d = (Dimension) o; @@ -212,7 +213,7 @@ } /** - * Creates and returns a copy of this Dimension. + * Creates and returns a copy of this {@link Dimension}. * * @return a copy of this Dimension */ @@ -221,8 +222,8 @@ } /** - * Creates and returns a Dimension representing the sum of this Dimension - * and the one specified. + * Creates and returns a {@link Dimension} representing the sum of this + * {@link Dimension} and the one specified. * * @param d * the dimension providing the expansion width and height @@ -234,7 +235,7 @@ /** * Creates and returns a new Dimension representing the sum of this - * Dimension and the one specified. + * {@link Dimension} and the one specified. * * @param w * value by which the width of this is to be expanded @@ -331,6 +332,7 @@ /** * @see java.lang.Object#hashCode() */ + @Override public int hashCode() { return (int) (width * height) ^ (int) (width + height); } @@ -489,7 +491,7 @@ /** * @see Object#toString() */ - + @Override public String toString() { return "Dimension(" + //$NON-NLS-1$ width + ", " + //$NON-NLS-1$ Index: src/org/eclipse/gef4/geometry/Point.java =================================================================== RCS file: /cvsroot/tools/org.eclipse.gef/GEF4/plugins/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/Point.java,v retrieving revision 1.2 diff -u -r1.2 Point.java --- src/org/eclipse/gef4/geometry/Point.java 20 Oct 2011 21:01:58 -0000 1.2 +++ src/org/eclipse/gef4/geometry/Point.java 21 Oct 2011 12:49:29 -0000 @@ -153,6 +153,7 @@ * 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; @@ -261,6 +262,7 @@ /** * @see java.lang.Object#hashCode() */ + @Override public int hashCode() { return (int) (x * y) ^ (int) (x + y); } @@ -354,8 +356,9 @@ } /** - * @return String representation. + * @see Object#toString() */ + @Override public String toString() { return "Point(" + x + ", " + y + ")";//$NON-NLS-3$//$NON-NLS-2$//$NON-NLS-1$ } Index: src/org/eclipse/gef4/geometry/euclidean/Straight.java =================================================================== RCS file: /cvsroot/tools/org.eclipse.gef/GEF4/plugins/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/euclidean/Straight.java,v retrieving revision 1.3 diff -u -r1.3 Straight.java --- src/org/eclipse/gef4/geometry/euclidean/Straight.java 20 Oct 2011 21:01:22 -0000 1.3 +++ src/org/eclipse/gef4/geometry/euclidean/Straight.java 21 Oct 2011 12:49:29 -0000 @@ -12,6 +12,9 @@ *******************************************************************************/ package org.eclipse.gef4.geometry.euclidean; +import java.io.Serializable; + +import org.eclipse.gef4.geometry.Angle; import org.eclipse.gef4.geometry.Point; import org.eclipse.gef4.geometry.utils.PrecisionUtils; @@ -20,7 +23,9 @@ * * @author anyssen */ -public class Straight { +public class Straight implements Cloneable, Serializable { + + private static final long serialVersionUID = 1L; /** position vector of this straight */ public Vector position; @@ -39,8 +44,8 @@ throw new IllegalArgumentException( "direction has to be unequal to (0,0)"); //$NON-NLS-1$ } - this.position = position; - this.direction = direction; + this.position = position.clone(); + this.direction = direction.clone(); } /** @@ -55,9 +60,14 @@ this(new Vector(point1), new Vector(point1, point2)); } + @Override + public Straight clone() { + return new Straight(position, direction); + } + /** - * Checks whether this Straight and the provided one have a single - * intersection point. + * Checks whether this Straight and the provided one have a single point of + * intersection. * * @param other * The Straight to use for the calculation. @@ -111,6 +121,55 @@ } /** + * Converts a given {@link Vector} into a 3-dimensional vector in + * homogeneous coordinates represented as an array. + * + * @param v + * the {@link Vector} to convert + * @return an array representation of the 3-dimensional homogeneous vector + */ + private double[] toHomogeneousVector(Vector v) { + return new double[] { v.x, v.y, 1 }; + } + + /** + * Projects the given 3-dimensional homogeneous-coordinate vector, + * represented as an array, into the 2D space. + * + * @param v + * the array representation of a 3-dimensional + * homogeneous-coordinate vector to project + * @return the projected {@link Vector} + */ + private Vector fromHomogeneousVector(double[] v) { + if (v[2] == 0) { + return null; + } + return new Vector(v[0] / v[2], v[1] / v[2]); + } + + /** + * Calculates the cross product of two 3-dimensional vectors. + * + * Used in calculations based on 2D homogeneous coordinates. + * + * @param a + * an array representation of the first 3-dimensional input + * vector + * @param b + * an array representation of the second 3-deminsional input + * vector + * @return an array representation of the 3-dimensional cross product + */ + private double[] getCrossProduct(double[] a, double[] b) { + double[] r = new double[3]; + r[0] = a[1] * b[2] - a[2] * b[1]; + r[1] = a[2] * b[0] - a[0] * b[2]; + r[2] = a[0] * b[1] - a[1] * b[0]; + return r; + } + + /** * Computes the intersection point of this Straight and the provided one, if * it exists. * @@ -120,18 +179,16 @@ * if no intersection point exists (or the Straights are equal). */ public Vector getIntersection(Straight other) { - // first check if there is a single intersection point - if (!intersects(other)) { - return null; - } - // calculate intersection point - Vector s1 = direction.getMultiplied(other.position - .getDotProduct(other.direction.getOrthogonalComplement())); - Vector s2 = other.direction.getMultiplied(position - .getDotProduct(direction.getOrthogonalComplement())); - return s1.getSubtracted(s2).getDivided( - direction.getDotProduct(other.direction - .getOrthogonalComplement())); + // method using homogeneous coordinates + double[] p11 = toHomogeneousVector(position); + double[] p12 = toHomogeneousVector(position.getAdded(direction)); + double[] p21 = toHomogeneousVector(other.position); + double[] p22 = toHomogeneousVector(other.position + .getAdded(other.direction)); + double[] l1 = getCrossProduct(p11, p12); + double[] l2 = getCrossProduct(p21, p22); + double[] poi = getCrossProduct(l1, l2); + return fromHomogeneousVector(poi); } /** @@ -139,25 +196,62 @@ * * @param other * The Straight to be used for the calculation. - * @return The angle spanned between the two Straights. + * @return The angle spanned between the two {@link Straight}s. */ - public double getAngle(Straight other) { + public Angle getAngle(Straight other) { return direction.getAngle(other.direction); } /** - * Returns the projection of the given Vector onto this Straight, which is - * the point on this Straight with the minimal distance to the point, - * denoted by the provided Vector. + * Returns the clock-wise (CW) or negative angle spanned between the two + * {@link Straight}s. + * + * The returned angle is the opposite of the angle returned by the + * getAngleCCW(Straight other) method. + * + * @param other + * @return The clock-wise (CW) or negative angle spanned between the two + * {@link Straight}s. + */ + public Angle getAngleCW(Straight other) { + return getAngleCCW(other).getOppositeSemi(); + } + + /** + * Returns the counter-clock-wise (CCW) or positive {@link Angle} spanned + * between the two {@link Straight}s. + * + * The returned {@link Angle} is the opposite of the {@link Angle} returned + * by the getAngleCCW(Straight other) method. + * + * @param other + * @return The counter-clock-wise (CCW) or positive angle spanned between + * the two {@link Straight}s. + */ + public Angle getAngleCCW(Straight other) { + Angle angle = getAngle(other); + if (direction.getCrossProduct(other.direction) > 0) { + angle = angle.getOppositeSemi(); + } + return angle; + } + + /** + * Returns the projection of the given {@link Vector} onto this + * {@link Straight}, which is the point on this {@link Straight} with the + * minimal distance to the point, denoted by the provided {@link Vector}. * * @param vector - * The Vector whose projection should be determined. - * @return A new Vector representing the projection of the provided Vector - * onto this Straight. + * The {@link Vector} whose projection should be determined. + * @return A new {@link Vector} representing the projection of the provided + * {@link Vector} onto this {@link Straight}. */ public Vector getProjection(Vector vector) { - return getIntersection(new Straight(vector, - direction.getOrthogonalComplement())); + // calculate with a normalized direction vector to prevent rounding + // effects + Vector normalized = direction.getNormalized(); + return new Straight(position, normalized).getIntersection(new Straight( + vector, normalized.getOrthogonalComplement())); } /** @@ -174,6 +268,88 @@ } /** + * Returns the signed distance of the given {@link Vector} to this + * {@link Straight}. + * + * The signed distance indicates on which side of the {@link Straight} the + * {@link Vector} lies. If it lies on the right side of this + * {@link Straight}'s direction {@link Vector}, the signed distance is + * negative. If it is on the left side of this {@link Straight}'s direction + * Vector, the signed distance is positive. + * + * @param vector + * @return the signed distance of the given {@link Vector} to this Straight + */ + public double getSignedDistanceCCW(Vector vector) { + Vector projected = getProjection(vector); + Vector d = vector.getSubtracted(projected); + + double len = d.getLength(); + + if (!d.isNull()) { + Angle angleCCW = direction.getAngleCW(d); + + if (angleCCW.equals(Angle.fromDeg(90))) { + len = -len; + } + } + + return len; + } + + /** + * Returns the signed distance of the given {@link Vector} to this Straight. + * + * The signed distance indicates on which side of the Straight the Vector + * lies. If it is on the right side of this Straight's direction Vector, the + * signed distance is negative. If it is on the left side of this Straight's + * direction Vector, the signed distance is positive. + * + * @param vector + * @return the signed distance of the given {@link Vector} to this Straight + */ + public double getSignedDistanceCW(Vector vector) { + return -getSignedDistanceCCW(vector); + } + + /** + * Returns this {@link Straight}'s parameter value for the given + * {@link Point} p. + * + * This method is the reverse of the getPointAt(double parameter) method. + * + * @param p + * @return this {@link Straight}'s parameter value for the given + * {@link Point} p + */ + public double getParameterAt(Point p) { + if (direction.x != 0) { + return (p.x - position.x) / direction.x; + } + if (direction.y != 0) { + return (p.y - position.y) / direction.y; + } + return 0; + } + + /** + * Returns the {@link Point} on this {@link Straight} at parameter p. The + * {@link Point} that you get is calculated by multiplying this + * {@link Straight}'s direction {@link Vector} by the parameter value and + * translating that {@link Vector} by this {@link Straight}'s position + * {@link Vector}. + * + * This method is the reverse of the getPointAt(double parameter) method. + * + * @param parameter + * @return the {@link Point} on this {@link Straight} at parameter p + */ + public Point getPointAt(double parameter) { + return new Point(position.x + direction.x * parameter, position.y + + direction.y * parameter); + } + + /** * Calculates whether the point indicated by the provided Vector is a point * on this Straight. * Index: src/org/eclipse/gef4/geometry/euclidean/Vector.java =================================================================== RCS file: /cvsroot/tools/org.eclipse.gef/GEF4/plugins/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/euclidean/Vector.java,v retrieving revision 1.4 diff -u -r1.4 Vector.java --- src/org/eclipse/gef4/geometry/euclidean/Vector.java 20 Oct 2011 21:01:23 -0000 1.4 +++ src/org/eclipse/gef4/geometry/euclidean/Vector.java 21 Oct 2011 12:49:29 -0000 @@ -12,6 +12,9 @@ *******************************************************************************/ package org.eclipse.gef4.geometry.euclidean; +import java.io.Serializable; + +import org.eclipse.gef4.geometry.Angle; import org.eclipse.gef4.geometry.Point; import org.eclipse.gef4.geometry.utils.PrecisionUtils; @@ -24,7 +27,9 @@ * @author ahunter * @author anyssen */ -public class Vector { +public class Vector implements Cloneable, Serializable { + + private static final long serialVersionUID = 1L; /** the X value */ public double x; @@ -87,6 +92,14 @@ } /** + * Clones the given Vector object. + */ + @Override + public Vector clone() { + return new Vector(x, y); + } + + /** * Calculates the magnitude of the cross product of this Vector with * another. Represents the amount by which two Vectors are directionally * different. Parallel Vectors return a value of 0. @@ -158,17 +171,45 @@ } /** - * Returns the angle (in degrees) between this Vector and the provided - * Vector. + * Returns the smallest {@link Angle} between this {@link Vector} and the + * provided {@link Vector}. * * @param other - * Vector to calculate the angle. - * @return the angle between the two Vectors in degrees. + * {@link Vector} to calculate the {@link Angle}. + * @return the smallest {@link Angle} between the two Vectors. */ - public double getAngle(Vector other) { + public Angle getAngle(Vector other) { double cosAlpha = getDotProduct(other) / (getLength() * other.getLength()); - return Math.toDegrees(Math.acos(cosAlpha)); + return Angle.fromRad(Math.acos(cosAlpha)); + } + + /** + * Returns the clock-wise (mathematical negative) {@link Angle} between this + * {@link Vector} and the provided {@link Vector}. + * + * @param other + * {@link Vector} to calculate the {@link Angle}. + * @return the clock-wise {@link Angle} between the two Vectors. + */ + public Angle getAngleCW(Vector other) { + return getAngleCCW(other).getOppositeFull(); + } + + /** + * Returns the counter-clock-wise (mathematical positive) {@link Angle} + * between this {@link Vector} and the provided {@link Vector}. + * + * @param other + * {@link Vector} to calculate the {@link Angle}. + * @return the counter-clock-wise {@link Angle} between the two Vectors. + */ + public Angle getAngleCCW(Vector other) { + Angle angle = getAngle(other); + if (getCrossProduct(other) > 0) { + return angle.getOppositeFull(); + } + return angle; } /** @@ -218,6 +259,30 @@ } /** + * Returns a fresh rotated Vector object. The rotation is clock-wise (CW) by + * the given angle. + * + * @param angle + * the rotation angle + * @return the new rotated Vector + */ + public Vector getRotatedCW(Angle angle) { + return clone().rotateCW(angle); + } + + /** + * Returns a fresh rotated Vector object. The rotation is counter-clock-wise + * (CCW) by the given angle. + * + * @param angle + * the rotation angle + * @return the new rotated Vector + */ + public Vector getRotatedCCW(Angle angle) { + return clone().rotateCCW(angle); + } + + /** * Returns the length of this Vector. * * @return Length of this Vector @@ -320,4 +385,42 @@ return (int) x + (int) y; } + /** + * Rotates this {@link Vector} counter-clock-wise by the given {@link Angle} + * . + * + * @param angle + * The rotation {@link Angle}. + * @return This (rotated) {@link Vector} object. + */ + public Vector rotateCCW(Angle angle) { + return rotateCW(angle.getOppositeFull()); + } + + /** + * Rotates this {@link Vector} clock-wise by the given {@link Angle}. + * + * @param angle + * The rotation {@link Angle}. + * @return This (rotated) {@link Vector} object. + */ + public Vector rotateCW(Angle angle) { + double alpha = angle.rad(); + double nx = x * Math.cos(alpha) - y * Math.sin(alpha); + double ny = x * Math.sin(alpha) + y * Math.cos(alpha); + x = nx; + y = ny; + return this; + } + + /** + * Creates a new normalized {@link Vector} that has the same direction as + * this {@link Vector} but a length of 1. + * + * @return The normalized {@link Vector}. + */ + public Vector getNormalized() { + return clone().getMultiplied(1 / getLength()); + } + } Index: src/org/eclipse/gef4/geometry/shapes/CubicCurve.java =================================================================== RCS file: /cvsroot/tools/org.eclipse.gef/GEF4/plugins/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/shapes/CubicCurve.java,v retrieving revision 1.3 diff -u -r1.3 CubicCurve.java --- src/org/eclipse/gef4/geometry/shapes/CubicCurve.java 20 Oct 2011 21:04:44 -0000 1.3 +++ src/org/eclipse/gef4/geometry/shapes/CubicCurve.java 21 Oct 2011 12:49:29 -0000 @@ -11,8 +11,14 @@ *******************************************************************************/ package org.eclipse.gef4.geometry.shapes; +import java.io.Serializable; +import java.util.Arrays; +import java.util.HashSet; + import org.eclipse.gef4.geometry.Point; import org.eclipse.gef4.geometry.transform.AffineTransform; +import org.eclipse.gef4.geometry.utils.PolynomCalculations; +import org.eclipse.gef4.geometry.utils.PrecisionUtils; /** * Represents the geometric shape of a cubic Bézier curve. @@ -20,12 +26,33 @@ * @author anyssen * */ -public class CubicCurve implements Geometry { +public class CubicCurve implements Geometry, Serializable { private static final long serialVersionUID = 1L; private double x1, y1, ctrl1X, ctrl1Y, ctrl2X, ctrl2Y, x2, y2; + /** + * Constructs a new {@link CubicCurve} object with the given control point + * coordinates. + * + * @param x1 + * x-coordinate of the start point + * @param y1 + * y-coordinate of the start point + * @param ctrl1X + * x-coordinate of the first control point + * @param ctrl1Y + * y-coordinate of the first control point + * @param ctrl2X + * x-coordinate of the second control point + * @param ctrl2Y + * y-coordinate of the second control point + * @param x2 + * x-coordinate of the end point + * @param y2 + * y-coordinate of the end point + */ public CubicCurve(double x1, double y1, double ctrl1X, double ctrl1Y, double ctrl2X, double ctrl2Y, double x2, double y2) { this.x1 = x1; @@ -38,6 +65,19 @@ this.y2 = y2; } + /** + * Constructs a new {@link CubicCurve} object with the given control + * {@link Point}s. + * + * @param start + * the start point + * @param ctrl1 + * the first control point + * @param ctrl2 + * the second control point + * @param end + * the end point + */ public CubicCurve(Point start, Point ctrl1, Point ctrl2, Point end) { this.x1 = start.x; this.y1 = start.y; @@ -49,138 +89,522 @@ this.y2 = end.y; } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.gef4.geometry.shapes.Geometry#contains(org.eclipse.gef4.geometry - * .Point) + /** + * @see Geometry#contains(Point) */ public boolean contains(Point p) { - // TODO Auto-generated method stub + // find roots of the x(t) - p.x function: + double D = getX1() - p.x; + double C = 3 * (getCtrl1X() - getX1()); + double B = 3 * (getCtrl2X() - getCtrl1X()) - C; + double A = getX2() - getX1() - B - C; + double[] xts = PolynomCalculations.getCubicRoots(A, B, C, D); + + for (double t : xts) { + // t = PrecisionUtils.round(t); + if (PrecisionUtils.greaterEqual(t, 0) + && PrecisionUtils.smallerEqual(t, 1) + && PrecisionUtils.equal(get(t).y, p.y)) { + return true; + } + } return false; } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.gef4.geometry.shapes.Geometry#contains(org.eclipse.gef4.geometry - * .shapes.Rectangle) + /** + * @see Geometry#contains(Rectangle) */ public boolean contains(Rectangle r) { - // TODO Auto-generated method stub return false; } - /* - * (non-Javadoc) - * - * @see org.eclipse.gef4.geometry.shapes.Geometry#getBounds() + @Override + public boolean equals(Object other) { + CubicCurve o = (CubicCurve) other; + + Polygon myPoly = getControlPolygon(); + Polygon otherPoly = o.getControlPolygon(); + + return myPoly.equals(otherPoly); + } + + /** + * @see Geometry#getBounds() */ public Rectangle getBounds() { - // TODO Auto-generated method stub - return null; + // extremes of the x(t) and y(t) functions: + double[] xts; + try { + xts = PolynomCalculations.getQuadraticRoots(-3 * getX1() + 9 + * getCtrl1X() - 9 * getCtrl2X() + 3 * getX2(), 6 * getX1() + - 12 * getCtrl1X() + 6 * getCtrl2X(), 3 * getCtrl1X() - 3 + * getX1()); + } catch (ArithmeticException x) { + return new Rectangle(getP1(), getP2()); + } + + double xmin = getX1(), xmax = getX1(); + if (getX2() < xmin) { + xmin = getX2(); + } else { + xmax = getX2(); + } + + for (double t : xts) { + if (t >= 0 && t <= 1) { + double x = get(t).x; + if (x < xmin) { + xmin = x; + } else if (x > xmax) { + xmax = x; + } + } + } + + double[] yts; + try { + yts = PolynomCalculations.getQuadraticRoots(-3 * getY1() + 9 + * getCtrl1Y() - 9 * getCtrl2Y() + 3 * getY2(), 6 * getY1() + - 12 * getCtrl1Y() + 6 * getCtrl2Y(), 3 * getCtrl1Y() - 3 + * getY1()); + } catch (ArithmeticException x) { + return new Rectangle(new Point(xmin, getP1().y), new Point(xmax, + getP2().y)); + } + + double ymin = getY1(), ymax = getY1(); + if (getY2() < ymin) { + ymin = getY2(); + } else { + ymax = getY2(); + } + + for (double t : yts) { + if (t >= 0 && t <= 1) { + double y = get(t).y; + if (y < ymin) { + ymin = y; + } else if (y > ymax) { + ymax = y; + } + } + } + + return new Rectangle(new Point(xmin, ymin), new Point(xmax, ymax)); + } + + private Point ratioPoint(Point p, Point q, double ratio) { + return p.getTranslated(q.getTranslated(p.getNegated()).getScaled(ratio)); + } + + /** + * Subdivides this {@link CubicCurve} into two {@link CubicCurve}s on the + * intervals [0, t] and [t, 1] using the de-Casteljau-algorithm. + * + * @param t + * split point's parameter value + * @return the two {@link CubicCurve}s + */ + public CubicCurve[] split(double t) { + if (t < 0 || t > 1) { + throw new IllegalArgumentException( + "Paramter t is out of range! t = " + t + " !in_range(0,1)"); + } + + Point p10 = ratioPoint(getP1(), getCtrl1(), t); + Point p11 = ratioPoint(getCtrl1(), getCtrl2(), t); + Point p12 = ratioPoint(getCtrl2(), getP2(), t); + Point p20 = ratioPoint(p10, p11, t); + Point p21 = ratioPoint(p11, p12, t); + Point p30 = ratioPoint(p20, p21, t); + + CubicCurve left = new CubicCurve(getP1(), p10, p20, p30); + CubicCurve right = new CubicCurve(p30, p21, p12, getP2()); + + return new CubicCurve[] { left, right }; + } + + /** + * Clips this {@link CubicCurve} at parameter values t1 and t2 so that the + * resulting {@link CubicCurve} is the section of the original + * {@link CubicCurve} for the parameter interval [t1, t2]. + * + * @param t1 + * @param t2 + * @return the {@link CubicCurve} on the interval [t1, t2] + */ + public CubicCurve clip(double t1, double t2) { + if (t1 < 0 || t1 > 1) { + throw new IllegalArgumentException( + "Paramter t1 is out of range! t1 = " + t1 + + " !in_range(0,1)"); + } + if (t2 < 0 || t2 > 1) { + throw new IllegalArgumentException( + "Paramter t2 is out of range! t2 = " + t2 + + " !in_range(0,1)"); + } + + CubicCurve right = split(t1)[1]; + double rightT2 = (t2 - t1) / (1 - t1); + return right.split(rightT2)[0]; + } + + private static double getArea(Polygon p) { + Rectangle r = p.getBounds(); + return r.getWidth() * r.getHeight(); + } + + private Polygon getControlPolygon() { + return new Polygon(getP1(), getCtrl1(), getCtrl2(), getP2()); + } + + private static Point[] getIntersections(CubicCurve p, double ps, double pe, + Line l) { + // parameter convergence test + double pm = (ps + pe) / 2; + + if (PrecisionUtils.equal(ps, pe, -2)) { + return new Point[] { p.get(pm) }; + } + + // no parameter convergence + // clip the curve + CubicCurve pc = p.clip(ps, pe); + + // check the control polygon + Polygon polygon = pc.getControlPolygon(); + + if (polygon.intersects(l)) { + // area test + if (PrecisionUtils.equal(getArea(polygon), 0, -2)) { + // line/line intersection fallback for such small curves + Point poi = new Line(pc.getP1(), pc.getP2()).getIntersection(l); + if (poi != null) { + return new Point[] { poi }; + } + return new Point[] {}; + } + + // "split" the curve to get precise intersections + HashSet intersections = new HashSet(); + + intersections.addAll(Arrays.asList(getIntersections(p, ps, pm, l))); + intersections.addAll(Arrays.asList(getIntersections(p, pm, pe, l))); + + return intersections.toArray(new Point[] {}); + } + + // no intersections + return new Point[] {}; + } + + private static Point[] getIntersections(CubicCurve p, double ps, double pe, + CubicCurve q, double qs, double qe) { + double pm = (ps + pe) / 2; + double qm = (qs + qe) / 2; + + // point convergence test + Point pPoi = p.get(pm); + Point qPoi = q.get(qm); + + if (pPoi != null && qPoi != null && pPoi.equals(qPoi)) { + return new Point[] { pPoi }; + } + + // no point convergence yet + // clip to parameter ranges + CubicCurve pc = p.clip(ps, pe); + CubicCurve qc = q.clip(qs, qe); + + // check the control polygons + Polygon pPoly = pc.getControlPolygon(); + Polygon qPoly = qc.getControlPolygon(); + + if (pPoly.intersects(qPoly)) { + // check the polygon's areas + double pArea = getArea(pPoly); + double qArea = getArea(qPoly); + + if (PrecisionUtils.equal(pArea, 0, +2) + && PrecisionUtils.equal(qArea, 0, +2)) { + // return line/line intersection + Point poi = new Line(pc.getP1(), pc.getP2()) + .getIntersection(new Line(qc.getP1(), qc.getP2())); + if (poi != null) { + return new Point[] { poi }; + } + return new Point[] {}; + } + + // areas not small enough + + // do not try to find the intersections of two equal curves + if (pc.equals(qc)) { + return new Point[] {}; + } + + // "split" the curves and do the intersection test recursively + HashSet intersections = new HashSet(); + + intersections.addAll(Arrays.asList(getIntersections(p.clip(ps, pm), + 0, 1, q.clip(qs, qm), 0, 1))); + intersections.addAll(Arrays.asList(getIntersections(p.clip(ps, pm), + 0, 1, q.clip(qm, qe), 0, 1))); + intersections.addAll(Arrays.asList(getIntersections(p.clip(pm, pe), + 0, 1, q.clip(qs, qm), 0, 1))); + intersections.addAll(Arrays.asList(getIntersections(p.clip(pm, pe), + 0, 1, q.clip(qm, qe), 0, 1))); + + // intersections.addAll(Arrays.asList(getIntersections(p, ps, pm, q, + // qs, qm))); + // intersections.addAll(Arrays.asList(getIntersections(p, ps, pm, q, + // qm, qe))); + // intersections.addAll(Arrays.asList(getIntersections(p, pm, pe, q, + // qs, qm))); + // intersections.addAll(Arrays.asList(getIntersections(p, pm, pe, q, + // qm, qe))); + + return intersections.toArray(new Point[] {}); + } + + // no intersections + return new Point[] {}; + } + + /** + * Returns the points of intersection between this {@link CubicCurve} and + * the given other {@link CubicCurve}. + * + * @param other + * @return the points of intersection + */ + public Point[] getIntersections(CubicCurve other) { + return getIntersections(this, 0, 1, other, 0, 1); } + /** + * Returns the points of intersection between this {@link CubicCurve} and + * the given {@link Line} l. + * + * @param l + * @return the points of intersection + */ + public Point[] getIntersections(Line l) { + return getIntersections(this, 0, 1, l); + } + + /** + * Returns the first control {@link Point}. + * + * @return the first control {@link Point}. + */ public Point getCtrl1() { return new Point(ctrl1X, ctrl1Y); } + /** + * Returns the first control {@link Point}'s x-coordinate. + * + * @return the first control {@link Point}'s x-coordinate. + */ public double getCtrl1X() { return ctrl1X; } + /** + * Returns the first control {@link Point}'s y-coordinate. + * + * @return the first control {@link Point}'s y-coordinate. + */ public double getCtrl1Y() { return ctrl1Y; } + /** + * Returns the second control {@link Point}. + * + * @return the second control {@link Point}. + */ public Point getCtrl2() { return new Point(ctrl2X, ctrl2Y); } + /** + * Returns the second control {@link Point}'s x-coordinate. + * + * @return the second control {@link Point}'s x-coordinate. + */ public double getCtrl2X() { return ctrl2X; } + /** + * Returns the second control {@link Point}'s y-coordinate. + * + * @return the second control {@link Point}'s y-coordinate. + */ public double getCtrl2Y() { return ctrl2Y; } + /** + * Returns the start {@link Point}. + * + * @return the start {@link Point}. + */ public Point getP1() { return new Point(x1, y1); } + /** + * Returns the end {@link Point}. + * + * @return the end {@link Point}. + */ public Point getP2() { - return new Point(x1, y1); + return new Point(x2, y2); } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.gef4.geometry.shapes.Geometry#getTransformed(org.eclipse. - * gef4.geometry.transform.AffineTransform) + /** + * @see Geometry#getTransformed(AffineTransform) */ public Geometry getTransformed(AffineTransform t) { - // TODO Auto-generated method stub return null; } + /** + * Returns the start {@link Point}'s x-coordinate. + * + * @return the start {@link Point}'s x-coordinate. + */ public double getX1() { return x1; } + /** + * Returns the end {@link Point}'s x-coordinate. + * + * @return the end {@link Point}'s x-coordinate. + */ public double getX2() { return x2; } + /** + * Returns the start {@link Point}'s y-coordinate. + * + * @return the start {@link Point}'s y-coordinate. + */ public double getY1() { return y1; } + /** + * Returns the end {@link Point}'s y-coordinate. + * + * @return the end {@link Point}'s y-coordinate. + */ public double getY2() { return y2; } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.gef4.geometry.shapes.Geometry#intersects(org.eclipse.gef4 - * .geometry.shapes.Rectangle) + /** + * @see org.eclipse.gef4.geometry.shapes.Geometry#intersects(Rectangle) */ public boolean intersects(Rectangle r) { - // TODO Auto-generated method stub return false; } + /** + * Tests if this {@link CubicCurve} intersects the given {@link Line} r. + * + * @param r + * @return true if they intersect, false otherwise + */ + public boolean intersects(Line r) { + return getIntersections(r).length > 0; + } + + /** + * Sets the first control {@link Point} to the given {@link Point} ctrl1. + * + * @param ctrl1 + * the new first control {@link Point} + */ public void setCtrl1(Point ctrl1) { this.ctrl1X = ctrl1.x; this.ctrl1Y = ctrl1.y; } + /** + * Sets the first control {@link Point}'s x-coordinate to the given + * x-coordinate ctrl1x. + * + * @param ctrl1x + * the new first control {@link Point}'s x-coordinate + */ public void setCtrl1X(double ctrl1x) { ctrl1X = ctrl1x; } + /** + * Sets the first control {@link Point}'s y-coordinate to the given + * y-coordinate ctrl1y. + * + * @param ctrl1y + * the new first control {@link Point}'s y-coordinate + */ public void setCtrl1Y(double ctrl1y) { ctrl1Y = ctrl1y; } + /** + * Sets the second control {@link Point} to the given {@link Point} ctrl2. + * + * @param ctrl2 + * the new second control {@link Point} + */ public void setCtrl2(Point ctrl2) { this.ctrl2X = ctrl2.x; this.ctrl2Y = ctrl2.y; } + /** + * Sets the second control {@link Point}'s x-coordinate to the given + * x-coordinate ctrl2x. + * + * @param ctrl2x + * the new second control {@link Point}'s x-coordinate + */ public void setCtrl2X(double ctrl2x) { ctrl2X = ctrl2x; } + /** + * Sets the second control {@link Point}'s y-coordinate to the given + * y-coordinate ctrl2y. + * + * @param ctrl2y + * the new second control {@link Point}'s y-coordinate + */ public void setCtrl2Y(double ctrl2y) { ctrl2Y = ctrl2y; } + /** + * Sets all control points of this {@link CubicCurve} to the given control + * {@link Point}s. + * + * @param p1 + * the new start {@link Point} + * @param ctrl1 + * the new first control {@link Point} + * @param ctrl2 + * the new second control {@link Point} + * @param p2 + * the new end {@link Point} + */ public void setCurve(Point p1, Point ctrl1, Point ctrl2, Point p2) { setP1(p1); setCtrl1(ctrl1); @@ -188,35 +612,75 @@ setP2(p2); } + /** + * Sets the start {@link Point} of this {@link CubicCurve} to the given + * {@link Point} p1. + * + * @param p1 + * the new start {@link Point} + */ public void setP1(Point p1) { this.x1 = p1.x; this.y1 = p1.y; } + /** + * Sets the end {@link Point} of this {@link CubicCurve} to the given + * {@link Point} p2. + * + * @param p2 + * the new end {@link Point} + */ public void setP2(Point p2) { this.x2 = p2.x; this.y2 = p2.y; } + /** + * Sets the x-coordinate of the start {@link Point} of this + * {@link CubicCurve} to x1. + * + * @param x1 + * the new start {@link Point}'s x-coordinate + */ public void setX1(double x1) { this.x1 = x1; } + /** + * Sets the x-coordinate of the end {@link Point} of this {@link CubicCurve} + * to x2. + * + * @param x2 + * the new end {@link Point}'s x-coordinate + */ public void setX2(double x2) { this.x2 = x2; } + /** + * Sets the y-coordinate of the start {@link Point} of this + * {@link CubicCurve} to y1. + * + * @param y1 + * the new start {@link Point}'s y-coordinate + */ public void setY1(double y1) { this.y1 = y1; } + /** + * Sets the y-coordinate of the end {@link Point} of this {@link CubicCurve} + * to y2. + * + * @param y2 + * the new end {@link Point}'s y-coordinate + */ public void setY2(double y2) { this.y2 = y2; } - /* - * (non-Javadoc) - * + /** * @see org.eclipse.gef4.geometry.shapes.Geometry#toPath() */ public Path toPath() { @@ -226,4 +690,32 @@ return p; } + /** + * Get a single {@link Point} on this CubicCurve at parameter t. + * + * @param t + * in range [0,1] + * @return the {@link Point} at parameter t + */ + public Point get(double t) { + if (!(PrecisionUtils.greaterEqual(t, 0) && PrecisionUtils.smallerEqual( + t, 1))) { + throw new IllegalArgumentException( + "Paramter t is out of range! t = " + t + " !in_range(0,1)"); + } + + // compensate rounding effects + if (t < 0) { + t = 0; + } else if (t > 1) { + t = 1; + } + + double d = 1 - t; + return getP1().getScaled(d * d * d) + .getTranslated(getCtrl1().getScaled(3 * t * d * d)) + .getTranslated(getCtrl2().getScaled(3 * t * t * d)) + .getTranslated(getP2().getScaled(t * t * t)); + } + } Index: src/org/eclipse/gef4/geometry/shapes/Ellipse.java =================================================================== RCS file: /cvsroot/tools/org.eclipse.gef/GEF4/plugins/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/shapes/Ellipse.java,v retrieving revision 1.11 diff -u -r1.11 Ellipse.java --- src/org/eclipse/gef4/geometry/shapes/Ellipse.java 20 Oct 2011 21:04:44 -0000 1.11 +++ src/org/eclipse/gef4/geometry/shapes/Ellipse.java 21 Oct 2011 12:49:29 -0000 @@ -13,6 +13,8 @@ package org.eclipse.gef4.geometry.shapes; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -101,8 +103,8 @@ * @see Geometry#contains(Point) */ public boolean contains(Point p) { - // point has to fulfill (x/a)^2 + (y/b)^2 = 1, where a = width/2 and b = - // height/2, if ellipse is centered around origin, so we have to + // point has to fulfill (x/a)^2 + (y/b)^2 <= 1, where a = width/2 and b + // = height/2, if ellipse is centered around origin, so we have to // normalize point p by subtracting the center double normalizedX = p.x - (x + width / 2); double normalizedY = p.y - (y + height / 2); @@ -113,6 +115,37 @@ } /** + * Tests if this {@link Ellipse} contains the given {@link Polyline} + * polyline. + * + * @param polyline + * @return true if it is contained, false otherwise + */ + public boolean contains(Polyline polyline) { + for (Line segment : polyline.getSegments()) { + if (!contains(segment)) { + return false; + } + } + return true; + } + + /** + * Tests if this {@link Ellipse} contains the given {@link Polygon} polygon. + * + * @param polygon + * @return true if it is contained, false otherwise + */ + public boolean contains(Polygon polygon) { + for (Line segment : polygon.getSegments()) { + if (!contains(segment)) { + return false; + } + } + return true; + } + + /** * @see Geometry#contains(Rectangle) */ public boolean contains(Rectangle r) { @@ -305,13 +338,13 @@ double dx = x2 - x1; // special-case the vertical line - if (PrecisionUtils.equal(dx, 0)) { + if (PrecisionUtils.equal(dx, 0, +2)) { // vertical line if (PrecisionUtils.smallerEqual(-a, x1) && PrecisionUtils.smallerEqual(x1, a)) { // -a <= x1 <= a // inside the ellipse - double y = Math.sqrt(PrecisionUtils.round(bSq - * (1 - x1 * x1 / aSq))); + double rad = bSq * (1 - x1 * x1 / aSq); + double y = rad < 0 ? 0 : Math.sqrt(rad); if (PrecisionUtils.greaterEqual(y1, y)) { if (PrecisionUtils.smallerEqual(y2, y)) { @@ -348,16 +381,14 @@ // check if equation has at least one solution double d = p * p / 4 - q; - if (PrecisionUtils.smaller(d, 0)) { - // discriminant smaller zero, so no solutions possible - } else if (PrecisionUtils.equal(d, 0)) { + if (PrecisionUtils.equal(d, 0, +2)) { // discriminant equals zero, so one possible solution double px = -p / 2; double py = px * m + n; intersections.add(new Point(px, py)); - } else { + } else if (d > 0) { // discriminant greater than zero, so two possible solutions - double sqrt = Math.sqrt(PrecisionUtils.round(d)); + double sqrt = d < 0 ? 0 : Math.sqrt(d); // first double px = -p / 2 + sqrt; @@ -450,6 +481,38 @@ } /** + * Tests if this {@link Ellipse} intersects the given {@link Polyline} + * polyline. + * + * @param polyline + * @return true if they intersect, false otherwise + */ + public boolean intersects(Polyline polyline) { + for (Line segment : polyline.getSegments()) { + if (intersects(segment)) { + return true; + } + } + return false; + } + + /** + * Tests if this {@link Ellipse} intersects the given {@link Polygon} + * polygon. + * + * @param polygon + * @return true if they intersect, false otherwise + */ + public boolean intersects(Polygon polygon) { + for (Line segment : polygon.getSegments()) { + if (intersects(segment)) { + return true; + } + } + return false; + } + + /** * At least one common point, which includes containment (check * getIntersections() to check if this is a true intersection). * @@ -534,4 +597,86 @@ return "Ellipse: (" + x + ", " + y + ", " + //$NON-NLS-3$//$NON-NLS-2$//$NON-NLS-1$ width + ", " + height + ")";//$NON-NLS-2$//$NON-NLS-1$ } + + /** + * Calculates the intersections of this {@link Ellipse} with the given other + * {@link Ellipse}. + * + * @param e2 + * @return points of intersection + */ + public Point[] getIntersections(Ellipse e2) { + if (equals(e2)) { + return new Point[] {}; + } + + HashSet intersections = new HashSet(); + + for (CubicCurve seg : getBorderSegments()) { + intersections.addAll(Arrays.asList(e2.getIntersections(seg))); + } + + return intersections.toArray(new Point[] {}); + } + + /** + * Calculates the points of intersection of this {@link Ellipse} and the + * given {@link CubicCurve}. + * + * @param curve + * @return points of intersection + */ + public Point[] getIntersections(CubicCurve curve) { + HashSet intersections = new HashSet(); + + for (CubicCurve seg : getBorderSegments()) { + intersections.addAll(Arrays.asList(curve.getIntersections(seg))); + } + + return intersections.toArray(new Point[] {}); + } + + /** + * Calculates the border segments of this {@link Ellipse}. The + * border-segments are approximated by {@link CubicCurve}s. These curves are + * generated as in the {@link Ellipse#toPath()} method. + * + * @return border-segments + */ + public CubicCurve[] getBorderSegments() { + CubicCurve[] segs = new CubicCurve[4]; + // see http://whizkidtech.redprince.net/bezier/circle/kappa/ for details + // on the approximation used here + final double kappa = 4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d; + double a = width / 2; + double b = height / 2; + + double ox = x + a; + double oy = y; + + segs[0] = new CubicCurve(ox, oy, x + a + kappa * a, y, x + width, y + b + - kappa * b, x + width, y + b); + + ox = x + width; + oy = y + b; + + segs[1] = new CubicCurve(ox, oy, x + width, y + b + kappa * b, x + a + + kappa * a, y + height, x + a, y + height); + + ox = x + a; + oy = y + height; + + segs[2] = new CubicCurve(ox, oy, x + width / 2 - kappa * width / 2, y + + height, x, y + height / 2 + kappa * height / 2, x, y + height + / 2); + + ox = x; + oy = y + height / 2; + + segs[3] = new CubicCurve(ox, oy, x, + y + height / 2 - kappa * height / 2, x + width / 2 - kappa + * width / 2, y, x + width / 2, y); + + return segs; + } } Index: src/org/eclipse/gef4/geometry/shapes/Line.java =================================================================== RCS file: /cvsroot/tools/org.eclipse.gef/GEF4/plugins/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/shapes/Line.java,v retrieving revision 1.7 diff -u -r1.7 Line.java --- src/org/eclipse/gef4/geometry/shapes/Line.java 20 Oct 2011 21:04:44 -0000 1.7 +++ src/org/eclipse/gef4/geometry/shapes/Line.java 21 Oct 2011 12:49:29 -0000 @@ -75,6 +75,11 @@ // TODO: optimize w.r.t object creation Point p1 = getP1(); Point p2 = getP2(); + + if (p1.equals(p2)) { + return p.equals(p1); + } + return new Straight(p1, p2).containsWithinSegment(new Vector(p1), new Vector(p2), new Vector(p)); } @@ -272,9 +277,19 @@ // TODO: optimize w.r.t. object creation Point p1 = getP1(); Point p2 = getP2(); - Straight s1 = new Straight(p1, p2); + + if (p1.equals(p2)) { + return l.contains(p1); + } + Point lp1 = l.getP1(); Point lp2 = l.getP2(); + + if (lp1.equals(lp2)) { + return contains(lp1); + } + + Straight s1 = new Straight(p1, p2); Straight s2 = new Straight(lp1, lp2); Vector v1 = new Vector(p1); Vector v2 = new Vector(p2); Index: src/org/eclipse/gef4/geometry/shapes/Polygon.java =================================================================== RCS file: /cvsroot/tools/org.eclipse.gef/GEF4/plugins/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/shapes/Polygon.java,v retrieving revision 1.8 diff -u -r1.8 Polygon.java --- src/org/eclipse/gef4/geometry/shapes/Polygon.java 20 Oct 2011 21:04:44 -0000 1.8 +++ src/org/eclipse/gef4/geometry/shapes/Polygon.java 21 Oct 2011 12:49:29 -0000 @@ -11,6 +11,9 @@ *******************************************************************************/ package org.eclipse.gef4.geometry.shapes; +import java.util.ArrayList; + +import org.eclipse.gef4.geometry.Angle; import org.eclipse.gef4.geometry.Point; import org.eclipse.gef4.geometry.euclidean.Straight; import org.eclipse.gef4.geometry.euclidean.Vector; @@ -126,6 +129,94 @@ } /** + * Returns the points of intersection between this {@link Polygon} and the + * given {@link Line} l. + * + * @param l + * @return The points of intersection. + */ + public Point[] getIntersections(Line l) { + ArrayList intersections = new ArrayList(); + + for (Line segment : getSegments()) { + Point poi = segment.getIntersection(l); + if (poi != null) { + intersections.add(poi); + } + } + + return intersections.toArray(new Point[] {}); + } + + /** + * Returns the points of intersection between this {@link Polygon} and the + * given other {@link Polygon} polygon. + * + * @param polygon + * @return The points of intersection. + */ + public Point[] getIntersections(Polygon polygon) { + ArrayList intersections = new ArrayList(); + + for (Line segment : polygon.getSegments()) { + for (Point poi : getIntersections(segment)) { + intersections.add(poi); + } + } + + return intersections.toArray(new Point[] {}); + } + + /** + * Returns the points of intersection between this {@link Polygon} and the + * given {@link Polyline} polyline. + * + * @param polyline + * @return The points of intersection. + */ + public Point[] getIntersections(Polyline polyline) { + ArrayList intersections = new ArrayList(); + + for (Line segment : polyline.getSegments()) { + for (Point poi : getIntersections(segment)) { + intersections.add(poi); + } + } + + return intersections.toArray(new Point[] {}); + } + + /** + * Returns the points of intersection between this {@link Polygon} and the + * given {@link Rectangle} rect. + * + * @param rect + * @return The points of intersection. + */ + public Point[] getIntersections(Rectangle rect) { + ArrayList intersections = new ArrayList(); + + for (Line segment : rect.getSegments()) { + for (Point poi : getIntersections(segment)) { + intersections.add(poi); + } + } + + return intersections.toArray(new Point[] {}); + } + + /** + * Returns the points of intersection between this {@link Polygon} and the + * given {@link Ellipse} e. + * + * @param e + * @return The points of intersection. + */ + public Point[] getIntersections(Ellipse e) { + return e.getIntersections(this); + } + + /** * @see Geometry#contains(Point) */ public boolean contains(Point p) { @@ -177,6 +268,60 @@ } /** + * Tests if the given {@link QuadraticCurve} curve is contained in this + * {@link Polygon}. + * + * @param curve + * @return true if it is contained, false otherwise + */ + public boolean contains(QuadraticCurve curve) { + if (contains(curve.getP1()) && contains(curve.getP2())) { + for (Line seg : getSegments()) { + if (curve.intersects(seg)) { + return false; + } + } + return true; + } + return false; + } + + /** + * Tests if the given {@link CubicCurve} curve is contained in this + * {@link Polygon}. + * + * @param curve + * @return true if it is contained, false otherwise + */ + public boolean contains(CubicCurve curve) { + if (contains(curve.getP1()) && contains(curve.getP2())) { + for (Line seg : getSegments()) { + if (curve.intersects(seg)) { + return false; + } + } + return true; + } + return false; + } + + /** + * Tests if the given {@link Ellipse} e is contained in this {@link Polygon} + * . + * + * @param e + * @return true if it is contained, false otherwise + */ + public boolean contains(Ellipse e) { + for (CubicCurve curve : e.getBorderSegments()) { + if (!contains(curve)) { + return false; + } + } + return true; + } + + /** * Checks whether the given {@link Polygon} is fully contained within this * {@link Polygon}. * @@ -197,6 +342,34 @@ } /** + * Tests if the given {@link Polyline} p is contained in this + * {@link Polygon}. + * + * @param p + * @return true if it is contained, false otherwise + */ + public boolean contains(Polyline p) { + // all segments of the given polygon have to be contained + Line[] otherSegments = p.getSegments(); + for (int i = 0; i < otherSegments.length; i++) { + if (!contains(otherSegments[i])) { + return false; + } + } + return true; + } + + /** + * Tests if this {@link Polygon} intersects the given {@link Ellipse} e. + * + * @param e + * @return true if they intersect, false otherwise + */ + public boolean intersects(Ellipse e) { + return e.intersects(this); + } + + /** * @see Geometry#contains(Rectangle) */ public boolean contains(Rectangle rect) { @@ -277,6 +450,83 @@ } /** + * Tests if this {@link Polygon} intersects with the given {@link Polyline} + * p. + * + * @param p + * @return true if they intersect, false otherwise + */ + public boolean intersects(Polyline p) { + // reduce to segment intersection test + Line[] otherSegments = p.getSegments(); + for (int i = 0; i < otherSegments.length; i++) { + if (intersects(otherSegments[i])) { + return true; + } + } + // no intersection, so we still need to check for containment + return contains(p); + } + + /** + * Rotates this {@link Polygon} clock-wise by the given {@link Angle} alpha + * around the given {@link Point} center. + * + * The rotation is done by + *
    + *
  1. translating this {@link Polygon} by the negated {@link Point} center
  2. + *
  3. rotating each {@link Point} of this {@link Polygon} clock-wise by the + * given {@link Angle} angle
  4. + *
  5. translating this {@link Polygon} back by the {@link Point} center
  6. + *
+ * + * @param alpha + * The rotation {@link Angle}. + * @param center + * The {@link Point} to rotate around. + * @return This (clock-wise-rotated) {@link Polygon} object. + */ + public Polygon rotateCW(Angle alpha, Point center) { + translate(center.getNegated()); + for (Point p : points) { + Point np = new Vector(p).rotateCW(alpha).toPoint(); + p.x = np.x; + p.y = np.y; + } + translate(center); + return this; + } + + /** + * Rotates this {@link Polygon} counter-clock-wise by the given + * {@link Angle} alpha around the given {@link Point} center. + * + * The rotation is done by + *
    + *
  1. translating this {@link Polygon} by the negated {@link Point} center
  2. + *
  3. rotating each {@link Point} of this {@link Polygon} + * counter-clock-wise by the given {@link Angle} angle
  4. + *
  5. translating this {@link Polygon} back by the {@link Point} center
  6. + *
+ * + * @param alpha + * The rotation {@link Angle}. + * @param center + * The {@link Point} to rotate around. + * @return This (counter-clock-wise-rotated) {@link Polygon} object. + */ + public Polygon rotateCCW(Angle alpha, Point center) { + translate(center.getNegated()); + for (Point p : points) { + Point np = new Vector(p).rotateCCW(alpha).toPoint(); + p.x = np.x; + p.y = np.y; + } + translate(center); + return this; + } + + /** * Returns a copy of the points that make up this {@link Polygon}, where a * segment of the {@link Polygon} is represented between each two succeeding * {@link Point}s in the sequence, and from the last back to the first. @@ -447,6 +697,34 @@ } /** + * Scales this {@link Polygon} object by the given factor from the given + * center {@link Point}. + * + * The scaling is done by + *
    + *
  1. translating this {@link Polygon} by the negated center {@link Point}
  2. + *
  3. scaling the individual {@link Polygon} {@link Point}s
  4. + *
  5. translating this {@link Polygon} back
  6. + *
+ * + * @param factor + * The scale-factor. + * @param center + * The rotation {@link Point}. + * @return This (scaled) {@link Polygon} object. + */ + public Polygon scale(double factor, Point center) { + translate(center.getNegated()); + for (Point p : points) { + Point np = p.getScaled(factor); + p.x = np.x; + p.y = np.y; + } + translate(center); + return this; + } + + /** * Moves this Polygon horizontally by dx and vertically by dy, then returns * this Rectangle for convenience. * Index: src/org/eclipse/gef4/geometry/shapes/QuadraticCurve.java =================================================================== RCS file: /cvsroot/tools/org.eclipse.gef/GEF4/plugins/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/shapes/QuadraticCurve.java,v retrieving revision 1.3 diff -u -r1.3 QuadraticCurve.java --- src/org/eclipse/gef4/geometry/shapes/QuadraticCurve.java 20 Oct 2011 21:04:44 -0000 1.3 +++ src/org/eclipse/gef4/geometry/shapes/QuadraticCurve.java 21 Oct 2011 12:49:29 -0000 @@ -11,8 +11,13 @@ *******************************************************************************/ package org.eclipse.gef4.geometry.shapes; +import java.util.Arrays; +import java.util.HashSet; + import org.eclipse.gef4.geometry.Point; import org.eclipse.gef4.geometry.transform.AffineTransform; +import org.eclipse.gef4.geometry.utils.PolynomCalculations; +import org.eclipse.gef4.geometry.utils.PrecisionUtils; /** * Represents the geometric shape of a quadratic Bézier curve. @@ -23,42 +28,90 @@ public class QuadraticCurve implements Geometry { private static final long serialVersionUID = 1L; - private double x1, y1, ctrlX, ctrlY, x2, y2; - public boolean contains(Point p) { - // TODO Auto-generated method stub - return false; - } - - public boolean contains(Rectangle r) { - // TODO Auto-generated method stub - return false; - } - - public Rectangle getBounds() { - // TODO Auto-generated method stub - return null; - } - + /** + * Constructs a new QuadraticCurve object from the given points. + * + * @param p1 + * the start point + * @param pCtrl + * the control point + * @param p2 + * the end point + */ + public QuadraticCurve(Point p1, Point pCtrl, Point p2) { + setP1(p1); + setCtrl(pCtrl); + setP2(p2); + } + + /** + * Constructs a new QuadraticCurve object from the given point coordinates. + * + * @param x1 + * the start point's x-coordinate + * @param y1 + * the start point's y-coordinate + * @param xCtrl + * the control point's x-coordinate + * @param yCtrl + * the control point's y-coordinate + * @param x2 + * the end point's x-coordinate + * @param y2 + * the end point's y-coordinate + */ + public QuadraticCurve(double x1, double y1, double xCtrl, double yCtrl, + double x2, double y2) { + setP1(new Point(x1, y1)); + setCtrl(new Point(xCtrl, yCtrl)); + setP2(new Point(x2, y2)); + } + + /** + * Get the control point. + * + * @return a Point object representing the control point + */ public Point getCtrl() { return new Point(ctrlX, ctrlY); } + /** + * Get the control point's x-coordinate. + * + * @return the control point's x-coordinate + */ public double getCtrlX() { return ctrlX; } + /** + * Get the control point's y-coordinate. + * + * @return the control point's y-coordinate + */ public double getCtrlY() { return ctrlY; } + /** + * Get the curve's starting point. + * + * @return the curve's starting point + */ public Point getP1() { return new Point(x1, y1); } + /** + * Get the curve's ending point. + * + * @return the curve's ending point + */ public Point getP2() { - return new Point(x1, y1); + return new Point(x2, y2); } public Geometry getTransformed(AffineTransform t) { @@ -66,66 +119,132 @@ return null; } + /** + * Returns the x-coordinate of the curve's starting point. + * + * @return the x-coordinate of the curve's starting point + */ public double getX1() { return x1; } + /** + * Returns the x-coordinate of the curve's ending point. + * + * @return the x-coordinate of the curve's ending point + */ public double getX2() { return x2; } + /** + * Returns the y-coordinate of the curve's starting point. + * + * @return the y-coordinate of the curve's starting point + */ public double getY1() { return y1; } + /** + * Returns the y-coordinate of the curve's ending point. + * + * @return the y-coordinate of the curve's ending point + */ public double getY2() { return y2; } - public boolean intersects(Rectangle r) { - // TODO Auto-generated method stub - return false; - } - + /** + * Sets the curve's control point. + * + * @param ctrl + */ public void setCtrl(Point ctrl) { this.ctrlX = ctrl.x; this.ctrlY = ctrl.y; } + /** + * Sets the x-coordinate of the curve's control point. + * + * @param ctrlX + */ public void setCtrlX(double ctrlX) { this.ctrlX = ctrlX; } + /** + * Sets the y-coordinate of the curve's control point. + * + * @param ctrlY + */ public void setCtrlY(double ctrlY) { this.ctrlY = ctrlY; } + /** + * Sets the curve's starting point. + * + * @param p1 + */ public void setP1(Point p1) { this.x1 = p1.x; this.y1 = p1.y; } + /** + * Sets the curve's ending point. + * + * @param p2 + */ public void setP2(Point p2) { this.x2 = p2.x; this.y2 = p2.y; } + /** + * Sets the x-coordinate of the curve's starting point. + * + * @param x1 + */ public void setX1(double x1) { this.x1 = x1; } + /** + * Sets the x-coordinate of the curve's ending point. + * + * @param x2 + */ public void setX2(double x2) { this.x2 = x2; } + /** + * Sets the y-coordinate of the curve's starting point. + * + * @param y1 + */ public void setY1(double y1) { this.y1 = y1; } + /** + * Sets the y-coordinate of the curve's ending point. + * + * @param y2 + */ public void setY2(double y2) { this.y2 = y2; } + /** + * Transform the QuadraticCurve object to a {@link Path} object with the + * same shape. + * + * @return a {@link Path} object representing the curve + */ public Path toPath() { Path p = new Path(); p.moveTo(x1, y1); @@ -133,4 +252,383 @@ return p; } + /** + * Get a single {@link Point} on this QuadraticCurve at parameter t. + * + * @param t + * in range [0,1] + * @return the {@link Point} at parameter t + */ + public Point get(double t) { + if (!(PrecisionUtils.greaterEqual(t, 0) && PrecisionUtils.smallerEqual( + t, 1))) { + throw new IllegalArgumentException( + "Paramter t is out of range! t = " + t + " !in_range(0,1)"); + } + + // compensate rounding effects + if (t < 0) { + t = 0; + } else if (t > 1) { + t = 1; + } + + return new Point(t * (t * (getP1().x - 2 * getCtrl().x + getP2().x)) + + 2 * (t * (getCtrl().x - getP1().x)) + getP1().x, t + * (t * (getP1().y - 2 * getCtrl().y + getP2().y)) + 2 + * (t * (getCtrl().y - getP1().y)) + getP1().y); + } + + /** + * Check if the given {@link Point} (approximately) lies on the curve. + * + * @param p + * the {@link Point} in question + * @return true if p lies on the curve, false otherwise + */ + public boolean contains(Point p) { + // find roots of the x(t) - p.x function: + double[] xts = PolynomCalculations.getQuadraticRoots(getX1() - 2 + * getCtrlX() + getX2(), 2 * getCtrlX() - 2 * getX1(), getX1() + - p.x); + + for (double t : xts) { + // t = PrecisionUtils.round(t); + if (PrecisionUtils.greaterEqual(t, 0) + && PrecisionUtils.smallerEqual(t, 1)) { + return true; + } + } + return false; + } + + /** + * How can it possibly contain a {@link Rectangle}? + * + * @param r + * the {@link Rectangle} in question + * @return always false + */ + public boolean contains(Rectangle r) { + return false; + } + + public boolean equals(Object other) { + QuadraticCurve o = (QuadraticCurve) other; + + Polygon myPoly = getControlPolygon(); + Polygon otherPoly = o.getControlPolygon(); + + return myPoly.equals(otherPoly); + } + + /** + * Degree elevation: Returns a {@link CubicCurve} representation of this + * {@link QuadraticCurve}. + * + * @return A {@link CubicCurve} that represents this {@link QuadraticCurve}. + */ + public CubicCurve getElevated() { + Point[] controlPoints = new Point[4]; + + // "Curves and Surfaces for Computer Aided Geometric Design" by Farin, + // Gerald E., Academic Press 1988 + controlPoints[0] = getP1(); + controlPoints[1] = getP1().getScaled(1 / 3).getTranslated( + getCtrl().getScaled(2 / 3)); + controlPoints[2] = getCtrl().getScaled(2 / 3).getTranslated( + getP2().getScaled(1 / 3)); + controlPoints[3] = getP2(); + + return new CubicCurve(controlPoints[0], controlPoints[1], + controlPoints[2], controlPoints[3]); + } + + /** + * Returns the bounds of this QuadraticCurve. The bounds are calculated by + * examining the extreme points of the x(t) and y(t) function + * representations of this QuadraticCurve. + * + * @return the bounds {@link Rectangle} + */ + public Rectangle getBounds() { + // extremes of the x(t) and y(t) functions: + double[] xts; + try { + xts = PolynomCalculations.getLinearRoots(2 * (getX1() - 2 + * getCtrlX() + getX2()), 2 * (getCtrlX() - getX1())); + } catch (ArithmeticException x) { + return new Rectangle(getP1(), getP2()); + } + + double xmin = getX1(), xmax = getX1(); + if (getX2() < xmin) { + xmin = getX2(); + } else { + xmax = getX2(); + } + + for (double t : xts) { + if (t >= 0 && t <= 1) { + double x = get(t).x; + if (x < xmin) { + xmin = x; + } else if (x > xmax) { + xmax = x; + } + } + } + + double[] yts; + try { + yts = PolynomCalculations.getLinearRoots(2 * (getY1() - 2 + * getCtrlY() + getY2()), 2 * (getCtrlY() - getY1())); + } catch (ArithmeticException x) { + return new Rectangle(getP1(), getP2()); + } + + double ymin = getY1(), ymax = getY1(); + if (getY2() < ymin) { + ymin = getY2(); + } else { + ymax = getY2(); + } + + for (double t : yts) { + if (t >= 0 && t <= 1) { + double y = get(t).y; + if (y < ymin) { + ymin = y; + } else if (y > ymax) { + ymax = y; + } + } + } + + return new Rectangle(new Point(xmin, ymin), new Point(xmax, ymax)); + } + + private Polygon getControlPolygon() { + return new Polygon(getP1(), getCtrl(), getP2()); + } + + /** + * Returns the points of intersection between this {@link QuadraticCurve} + * and the given {@link Line} l. + * + * @param l + * @return The points of intersection. + */ + public Point[] getIntersections(Line l) { + return getIntersections(this, 0, 1, l); + } + + private static double getArea(Polygon p) { + Rectangle r = p.getBounds(); + return r.getWidth() * r.getHeight(); + } + + private static Point[] getIntersections(QuadraticCurve p, double ps, + double pe, Line l) { + // parameter convergence test + double pm = (ps + pe) / 2; + + if (PrecisionUtils.equal(ps, pe, +2)) { + return new Point[] { p.get(pm) }; + } + + // no parameter convergence + + // clip the curve + QuadraticCurve pc = p.clip(ps, pe); + + // check the control polygon + Polygon polygon = pc.getControlPolygon(); + + if (polygon.intersects(l)) { + // area test + if (PrecisionUtils.equal(getArea(polygon), 0, +2)) { + // line/line intersection fallback for such small curves + Point poi = new Line(pc.getP1(), pc.getP2()).getIntersection(l); + if (poi != null) { + return new Point[] { poi }; + } + return new Point[] {}; + } + + // individually test the curves left and right sides for points of + // intersection + HashSet intersections = new HashSet(); + + intersections.addAll(Arrays.asList(getIntersections(p, ps, pm, l))); + intersections.addAll(Arrays.asList(getIntersections(p, pm, pe, l))); + + return intersections.toArray(new Point[] {}); + } + + // no intersections + return new Point[] {}; + } + + private static Point[] getIntersections(QuadraticCurve p, double ps, + double pe, QuadraticCurve q, double qs, double qe) { + // parameter convergence test + double pm = (ps + pe) / 2; + double qm = (qs + qe) / 2; + + if (PrecisionUtils.equal(ps, pe)) { + return new Point[] { p.get(pm) }; + } + + if (PrecisionUtils.equal(qs, qe)) { + return new Point[] { q.get(qm) }; + } + + // no parameter convergence + + // clip to parameter ranges + QuadraticCurve pc = p.clip(ps, pe); + QuadraticCurve qc = q.clip(qs, qe); + + // check the control polygons + Polygon pPoly = pc.getControlPolygon(); + Polygon qPoly = qc.getControlPolygon(); + + if (pPoly.intersects(qPoly)) { + // check the polygon's areas + double pArea = getArea(pPoly); + double qArea = getArea(qPoly); + + if (PrecisionUtils.equal(pArea, 0, -2) + && PrecisionUtils.equal(qArea, 0, -2)) { + // return line/line intersection + Point poi = new Line(pc.getP1(), pc.getP2()) + .getIntersection(new Line(qc.getP1(), qc.getP2())); + if (poi != null) { + return new Point[] { poi }; + } + return new Point[] {}; + } + + // areas not small enough + + // individually test the left and right parts of the curves for + // points of intersection + HashSet intersections = new HashSet(); + + intersections.addAll(Arrays.asList(getIntersections(p, ps, pm, q, + qs, qm))); + intersections.addAll(Arrays.asList(getIntersections(p, ps, pm, q, + qm, qe))); + intersections.addAll(Arrays.asList(getIntersections(p, pm, pe, q, + qs, qm))); + intersections.addAll(Arrays.asList(getIntersections(p, pm, pe, q, + qm, qe))); + + return intersections.toArray(new Point[] {}); + } + + // no intersections + return new Point[] {}; + } + + /** + * Calculates the intersections of two {@link QuadraticCurve}s using the + * subdivision algorithm. + * + * @param other + * @return points of intersection + */ + public Point[] getIntersections(QuadraticCurve other) { + return getIntersections(this, 0, 1, other, 0, 1); + } + + /** + * Checks if two {@link QuadraticCurve}s intersect each other. (Costly) + * + * @param other + * @return true if the two curves intersect. false otherwise + */ + public boolean intersects(QuadraticCurve other) { + return getIntersections(other).length > 0; + } + + /** + * Checks if this {@link QuadraticCurve} intersects with the given line. + * (Costly) + * + * TODO: implement a faster algorithm for this intersection-test. + * + * @param l + * @return true if they intersect, false otherwise. + */ + public boolean intersects(Line l) { + return getIntersections(l).length > 0; + } + + public boolean intersects(Rectangle r) { + for (Line l : r.getSegments()) { + if (intersects(l)) { + return true; + } + } + return false; + } + + private Point ratioPoint(Point p, Point q, double ratio) { + return p.getTranslated(q.getTranslated(p.getNegated()).getScaled(ratio)); + } + + /** + * Splits this QuadraticCurve using the de Casteljau algorithm at parameter + * t into two separate QuadraticCurve objects. The returned + * {@link QuadraticCurve}s are the curves for [0, t] and [t, 1]. + * + * @param t + * in range [0,1] + * @return two QuadraticCurve objects constituting the original curve: 1. + * [0, t] 2. [t, 1] + */ + public QuadraticCurve[] split(double t) { + if (t < 0 || t > 1) { + throw new IllegalArgumentException( + "Paramter t is out of range! t = " + t + " !in_range(0,1)"); + } + + Point left1 = ratioPoint(getP1(), getCtrl(), t); + Point right1 = ratioPoint(getCtrl(), getP2(), t); + Point split = ratioPoint(left1, right1, t); + + QuadraticCurve left = new QuadraticCurve(getP1(), left1, split); + QuadraticCurve right = new QuadraticCurve(split, right1, getP2()); + + return new QuadraticCurve[] { left, right }; + } + + /** + * Clips this {@link QuadraticCurve} at parameter values t1 and t2 so that + * the resulting {@link QuadraticCurve} is the section of the original + * {@link QuadraticCurve} for the parameter interval [t1, t2]. + * + * @param t1 + * @param t2 + * @return the {@link QuadraticCurve} on the interval [t1, t2] + */ + public QuadraticCurve clip(double t1, double t2) { + if (t1 < 0 || t1 > 1) { + throw new IllegalArgumentException( + "Paramter t1 is out of range! t1 = " + t1 + + " !in_range(0,1)"); + } + if (t2 < 0 || t2 > 1) { + throw new IllegalArgumentException( + "Paramter t2 is out of range! t2 = " + t2 + + " !in_range(0,1)"); + } + + QuadraticCurve right = split(t1)[1]; + double rightT2 = (t2 - t1) / (1 - t1); + return right.split(rightT2)[0]; + } + } Index: src/org/eclipse/gef4/geometry/shapes/Rectangle.java =================================================================== RCS file: /cvsroot/tools/org.eclipse.gef/GEF4/plugins/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/shapes/Rectangle.java,v retrieving revision 1.10 diff -u -r1.10 Rectangle.java --- src/org/eclipse/gef4/geometry/shapes/Rectangle.java 20 Oct 2011 21:04:44 -0000 1.10 +++ src/org/eclipse/gef4/geometry/shapes/Rectangle.java 21 Oct 2011 12:49:29 -0000 @@ -12,6 +12,7 @@ *******************************************************************************/ package org.eclipse.gef4.geometry.shapes; +import org.eclipse.gef4.geometry.Angle; import org.eclipse.gef4.geometry.Dimension; import org.eclipse.gef4.geometry.Point; import org.eclipse.gef4.geometry.transform.AffineTransform; @@ -171,9 +172,8 @@ public boolean contains(double x, double y, double width, double height) { return PrecisionUtils.smallerEqual(this.x, x) && PrecisionUtils.smallerEqual(this.y, y) - && PrecisionUtils.greaterEqual(this.x + this.width, x + width) - && PrecisionUtils - .greaterEqual(this.y + this.height, y + height); + && PrecisionUtils.greaterEqual(this.width, width) + && PrecisionUtils.greaterEqual(this.height, height); } /** @@ -264,13 +264,13 @@ * and height of the given Insets, and returns this for convenience. * * @param left - * - the amount to shrink the left side + * - the amount to expand the left side * @param top - * - the amount to shrink the top side + * - the amount to expand the top side * @param right - * - the amount to shrink the right side + * - the amount to expand the right side * @param bottom - * - the amount to shrink the bottom side + * - the amount to expand the bottom side * @return this Rectangle for convenience * */ @@ -492,6 +492,42 @@ } /** + * Rotates this {@link Rectangle} clock-wise by the given {@link Angle} + * alpha around the {@link Point} center. + * + * If the rotation {@link Angle} is not an integer multiple 90°, the + * resulting figure cannot be expressed as a {@link Rectangle} object. + * That's why this method returns a {@link Polygon} instead. + * + * @param alpha + * the rotation angle + * @param center + * the center point of the rotation + * @return the rotated rectangle ({@link Polygon}) + */ + public Polygon getRotatedCW(Angle alpha, Point center) { + return toPolygon().rotateCW(alpha, center); + } + + /** + * Rotates this {@link Rectangle} counter-clock-wise by the given + * {@link Angle} alpha around the {@link Point} center. + * + * If the rotation {@link Angle} is not an integer multiple 90°, the + * resulting figure cannot be expressed as a {@link Rectangle} object. + * That's why this method returns a {@link Polygon} instead. + * + * @param alpha + * the rotation angle + * @param center + * the center point of the rotation + * @return the rotated rectangle ({@link Polygon}) + */ + public Polygon getRotatedCCW(Angle alpha, Point center) { + return toPolygon().rotateCCW(alpha, center); + } + + /** * Returns a new Rectangle, where the sides are shrinked by the horizontal * and vertical values supplied. The center of this Rectangle is kept * constant. @@ -500,7 +536,7 @@ * Horizontal reduction amount * @param v * Vertical reduction amount - * @return this for convenience + * @return the new, shrinked {@link Rectangle} */ public Rectangle getShrinked(double h, double v) { return getCopy().shrink(h, v); Index: src/org/eclipse/gef4/geometry/utils/PolynomCalculations.java =================================================================== RCS file: src/org/eclipse/gef4/geometry/utils/PolynomCalculations.java diff -N src/org/eclipse/gef4/geometry/utils/PolynomCalculations.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/gef4/geometry/utils/PolynomCalculations.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,138 @@ +/******************************************************************************* + * Copyright (c) 2011 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.gef4.geometry.utils; + +/** + * Utility class that implements common polynom calculations such as finding the + * roots of polynoms of degree 2 or 3. + * + * @author wienand + * + */ +public final class PolynomCalculations { + /** + * Solves a linear polynomial equation of the form a*x + b = 0. + * + * @param a + * the coefficient of x^1 + * @param b + * the absolute element + * @return the roots of this polynom + */ + public static final double[] getLinearRoots(double a, double b) { + if (a == 0) { + if (b == 0) { + throw new ArithmeticException( + "The given polynomial equation is always true."); + } + return new double[] {}; + } + return new double[] { -b / a }; + } + + /** + * Solves a quadratic polynomial equation of the form a*x^2 + b*x + c = 0. + * + * @param a + * @param b + * @param c + * @return all real solutions of the given equation. An empty array in the + * case of no solutions. + */ + public static final double[] getQuadraticRoots(double a, double b, double c) { + if (a == 0) { + return getLinearRoots(b, c); + } + + // p-q-formula + double p = b / a; + double q = c / a; + double D = p * p / 4 - q; + + if (PrecisionUtils.equal(D, 0, +4)) { + return new double[] { -p / 2 }; + } else if (D > 0) { + double sqrt = Math.sqrt(D); + return new double[] { sqrt - p / 2, -sqrt - p / 2 }; + } else { + return new double[] {}; + } + } + + /** + * Solves a cubic polynomial equation of the form Ax^3 + Bx^2 + Cx + D = 0. + * + * @param A + * @param B + * @param C + * @param D + * @return all real solutions of the given equation + */ + public static final double[] getCubicRoots(double A, double B, double C, + double D) { + if (A == 0) { + return getQuadraticRoots(B, C, D); + } + + double a = B / A; + double b = C / A; + double c = D / A; + + // reduce to z^3 + pz + q = 0 by substituting x = z - a/3 + // (http://en.wikipedia.org/wiki/Cubic_function#Cardano.27s_method) + + double p = b - a * a / 3; + double q = 2 * a * a * a / 27 - a * b / 3 + c; + + // short-cuts for p = 0, q = 0: + if (PrecisionUtils.equal(p, 0, +4)) { + // z^3 = -q => z = cbrt(-q) => x = cbrt(-q) - a / 3 + return new double[] { Math.cbrt(-q) - a / 3 }; + } + if (PrecisionUtils.equal(q, 0, +4)) { + // z^3 - pz = 0 <=> z(z^2 - p) = 0 => z = 0 v z^2 = p => z = + // +-sqrt(p), p >= 0 (p is not zero, because otherwise we would not + // be here) + if (p > 0) { + return new double[] { 0, Math.sqrt(p), -Math.sqrt(p) }; + } else { + return new double[] { 0 }; + } + } + + double p_3 = p / 3; + double q_2 = q / 2; + + D = q_2 * q_2 + p_3 * p_3 * p_3; + + if (PrecisionUtils.equal(D, 0, +4)) { + // two real solutions + return new double[] { 3 * q / p - a / 3, -3 * q / (2 * p) - a / 3 }; + } else if (D > 0) { + // one real solution + double u = Math.cbrt(-q_2 + Math.sqrt(D)); + double v = Math.cbrt(-q_2 - Math.sqrt(D)); + + return new double[] { u + v - a / 3 }; + } else { + // three real solutions + double r = Math.sqrt(-p_3 * p_3 * p_3); + double phi = Math.acos(-q / (2 * r)); + double co = 2 * Math.cbrt(r); + + // co * cos((phi + k * pi)/3) - a/3, k = 2n, n in N + return new double[] { co * Math.cos(phi / 3) - a / 3, + co * Math.cos((phi + 2 * Math.PI) / 3) - a / 3, + co * Math.cos((phi + 4 * Math.PI) / 3) - a / 3 }; + } + } +} Index: src/org/eclipse/gef4/geometry/utils/PrecisionUtils.java =================================================================== RCS file: /cvsroot/tools/org.eclipse.gef/GEF4/plugins/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/utils/PrecisionUtils.java,v retrieving revision 1.4 diff -u -r1.4 PrecisionUtils.java --- src/org/eclipse/gef4/geometry/utils/PrecisionUtils.java 20 Oct 2011 21:14:19 -0000 1.4 +++ src/org/eclipse/gef4/geometry/utils/PrecisionUtils.java 21 Oct 2011 12:49:31 -0000 @@ -11,8 +11,6 @@ *******************************************************************************/ package org.eclipse.gef4.geometry.utils; -import java.math.BigDecimal; - /** * A utility class for floating point calculations and comparisons that should * guarantee a precision of a given scale, and ignore differences beyond this @@ -27,104 +25,152 @@ * converting to 8 digits scale, so there are no undesired rounding effects * beyond this precision. */ - private static final int ROUNDING_MODE = BigDecimal.ROUND_DOWN; - private static final int SCALE = 8; - private static final double FRACTION = 1 / Math.pow(10, SCALE); + private static final int DEFAULT_SCALE = 6; + + private static final double calculateFraction(int shift) { + return 1 / Math.pow(10, DEFAULT_SCALE + shift); + } + + /** + * @see PrecisionUtils#equal(double, double, int) + * @param d1 + * @param d2 + * @return result of the comparison + */ + public static final boolean equal(double d1, double d2) { + return equal(d1, d2, 0); + } /** * Tests whether the two values are regarded to be equal w.r.t. the given - * scale. + * shift. * * @param d1 * the first value to test * @param d2 * the second value to test + * @param shift + * the delta shift used for this test * @return true in case the given two values are identical or * differ from each other by an amount that is smaller than what is - * recognizable by the given scale, false otherwise + * recognizable by the shifted delta, false otherwise */ - public static final boolean equal(double d1, double d2) { - return round(d1) == round(d2); + public static final boolean equal(double d1, double d2, int shift) { + return Math.abs(d1 - d2) <= calculateFraction(shift); + } + + /** + * @see PrecisionUtils#greater(double, double, int) + * @param d1 + * @param d2 + * @return result of the comparison + */ + public static final boolean greater(double d1, double d2) { + return greater(d1, d2, 0); } /** * Tests whether the first given value is regarded to be greater than the - * second value w.r.t. the given scale. + * second value w.r.t. the given shift. * * @param d1 * the first value to test * @param d2 * the second value to test + * @param shift + * the delta shift used for this test * @return true in case the first value is greater than the - * second value by an amount recognizable by the given scale, + * second value by an amount recognizable by the shifted delta, * false otherwise */ - public static final boolean greater(double d1, double d2) { - return round(d1) > round(d2); + public static final boolean greater(double d1, double d2, int shift) { + return d1 + calculateFraction(shift) > d2; + } + + /** + * @see PrecisionUtils#greaterEqual(double, double, int) + * @param d1 + * @param d2 + * @return result of the comparison + */ + public static final boolean greaterEqual(double d1, double d2) { + return greaterEqual(d1, d2, 0); } /** * Tests whether the first given value is regarded to be greater or equal - * than the second value w.r.t. the given scale. + * than the second value w.r.t. the given shift. * * @param d1 * the first value to test * @param d2 * the second value to test + * @param shift + * the delta shift used for this test * @return true in case the first value is greater than the * second value by an amount recognizable by the given scale or - * differs from it by an amount not recognizable by the given scale, - * false otherwise + * differs from it by an amount not recognizable by the shifted + * delta, false otherwise */ - public static final boolean greaterEqual(double d1, double d2) { - return round(d1) >= round(d2); + public static final boolean greaterEqual(double d1, double d2, int shift) { + return d1 + calculateFraction(shift) >= d2; } /** - * Rounds the given value w.r.t. to the given scale. - * - * @param d - * the value to round - * @return a rounded value that represents the recognizable fraction of the - * given value w.r.t. the given scale - */ - public static double round(double d) { - return BigDecimal - .valueOf(d < 0 ? d - 0.05 * FRACTION : d + 0.05 * FRACTION) - .setScale(SCALE, ROUNDING_MODE).doubleValue(); + * @see PrecisionUtils#smaller(double, double, int) + * @param d1 + * @param d2 + * @return result of the comparison + */ + public static final boolean smaller(double d1, double d2) { + return smaller(d1, d2, 0); } /** * Tests whether the first given value is regarded to be smaller than the - * second value w.r.t. the given scale. + * second value w.r.t. the given shift. * * @param d1 * the first value to test * @param d2 * the second value to test + * @param shift + * the delta shift used for this test * @return true in case the first value is smaller than the - * second value by an amount recognizable by the given scale, + * second value by an amount recognizable by the shifted delta, * false otherwise */ - public static final boolean smaller(double d1, double d2) { - return round(d1) < round(d2); + public static final boolean smaller(double d1, double d2, int shift) { + return d1 < d2 + calculateFraction(shift); + } + + /** + * @see PrecisionUtils#smallerEqual(double, double, int) + * @param d1 + * @param d2 + * @return result of the comparison + */ + public static final boolean smallerEqual(double d1, double d2) { + return smallerEqual(d1, d2, 0); } /** * Tests whether the first given value is regarded to be smaller or equal - * than the second value w.r.t. the given scale. + * than the second value w.r.t. the given shift. * * @param d1 * the first value to test * @param d2 * the second value to test + * @param shift + * the delta shift used for this test * @return true in case the first value is smaller than the * second value by an amount recognizable by the given scale or - * differs from it by an amount not recognizable by the given scale, - * false otherwise + * differs from it by an amount not recognizable by the shifted + * delta, false otherwise */ - public static final boolean smallerEqual(double d1, double d2) { - return round(d1) <= round(d2); + public static final boolean smallerEqual(double d1, double d2, int shift) { + return d1 <= d2 + calculateFraction(shift); } private PrecisionUtils() { #P org.eclipse.gef4.geometry.tests Index: src/org/eclipse/gef4/geometry/tests/AllTests.java =================================================================== RCS file: /cvsroot/tools/org.eclipse.gef/GEF4/plugins/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/AllTests.java,v retrieving revision 1.1 diff -u -r1.1 AllTests.java --- src/org/eclipse/gef4/geometry/tests/AllTests.java 30 Aug 2011 20:43:48 -0000 1.1 +++ src/org/eclipse/gef4/geometry/tests/AllTests.java 21 Oct 2011 12:49:32 -0000 @@ -6,7 +6,7 @@ * http://www.eclipse.org/legal/epl-v10.html * * Contributors: - * itemis AG - initial API and implementation + * Alexander Nyßen (itemis AG) - initial API and implementation * *******************************************************************************/ package org.eclipse.gef4.geometry.tests; @@ -16,10 +16,12 @@ import org.junit.runners.Suite.SuiteClasses; @RunWith(Suite.class) -@SuiteClasses({ PrecisionUtilsTest.class, PointTests.class, - DimensionTests.class, LineTests.class, RectangleTests.class, - VectorTests.class, StraightTests.class, PolylineTests.class, - PolygonTests.class, EllipseTests.class }) +@SuiteClasses({ CubicCurveTests.class, DimensionTests.class, + EllipseTests.class, LineTests.class, PointTests.class, + PolygonTests.class, PolylineTests.class, + PolynomCalculationsTests.class, PrecisionUtilsTest.class, + QuadraticCurveTests.class, RectangleTests.class, StraightTests.class, + VectorTests.class }) public class AllTests { } Index: src/org/eclipse/gef4/geometry/tests/CubicCurveTests.java =================================================================== RCS file: src/org/eclipse/gef4/geometry/tests/CubicCurveTests.java diff -N src/org/eclipse/gef4/geometry/tests/CubicCurveTests.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/gef4/geometry/tests/CubicCurveTests.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2011 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.gef4.geometry.tests; + +import junit.framework.TestCase; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.shapes.CubicCurve; + +public class CubicCurveTests extends TestCase { + + private final Point p = new Point(-10, -10), c1 = new Point(0, -10), + c2 = new Point(10, 0), q = new Point(0, 10); + + public void test_get() { + CubicCurve curve = new CubicCurve(p, c1, c2, q); + + assertEquals("curve.get(0) returns the curve's start point", p, + curve.get(0)); + assertEquals("curve.get(1) returns the curve's end point", q, + curve.get(1)); + + boolean thrown = false; + try { + curve.get(-0.1); + } catch (IllegalArgumentException x) { + thrown = true; + } + assertTrue("curve.get(t < 0) throws an IllegalArgumentException", + thrown); + + thrown = false; + try { + curve.get(1.1); + } catch (IllegalArgumentException x) { + thrown = true; + } + assertTrue("curve.get(t > 1) throws an IllegalArgumentException", + thrown); + } + + public void test_contains_Point() { + CubicCurve curve = new CubicCurve(p, c1, c2, q); + + // check fix points: + assertEquals(true, curve.contains(p)); + assertEquals(true, curve.contains(q)); + assertEquals(false, curve.contains(c1)); // not always true, but for our + // c1 it is + assertEquals(false, curve.contains(c2)); // not always true, but for our + // c2 it is + + // check 0 <= t <= 1: + for (double t = 0; t <= 1; t += 0.0123456789) { + assertEquals("curve.get(t = " + t + + " in range [0, 1]) lies on the curve", true, + curve.contains(curve.get(t))); + } + } + + public void test_get_Bounds() { + CubicCurve curve = new CubicCurve(p, c1, c2, q); + + // p is the top-left point: (y-coordinates are inverted) + assertEquals(curve.getBounds().getTopLeft(), p); + } + +} Index: src/org/eclipse/gef4/geometry/tests/EllipseTests.java =================================================================== RCS file: /cvsroot/tools/org.eclipse.gef/GEF4/plugins/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/EllipseTests.java,v retrieving revision 1.3 diff -u -r1.3 EllipseTests.java --- src/org/eclipse/gef4/geometry/tests/EllipseTests.java 31 Aug 2011 11:27:11 -0000 1.3 +++ src/org/eclipse/gef4/geometry/tests/EllipseTests.java 21 Oct 2011 12:49:32 -0000 @@ -6,7 +6,7 @@ * http://www.eclipse.org/legal/epl-v10.html * * Contributors: - * itemis AG - initial API and implementation + * Alexander Nyßen (itemis AG) - initial API and implementation * *******************************************************************************/ package org.eclipse.gef4.geometry.tests; @@ -76,4 +76,88 @@ assertTrue(e.intersects(l)); // line touches ellipse (tangent) } } + + public void test_get_intersections_with_Ellipse() { + Rectangle r = new Rectangle(34.3435, 56.458945, 123.3098, 146.578); + Ellipse e1 = new Ellipse(r); + Ellipse e2 = new Ellipse(r); + + // ellipses are identical = returns no intersections, user can check + // this via equals() + Point[] intersections = e1.getIntersections(e2); + assertEquals(0, intersections.length); + + // if we create an x-scaled ellipse at the same position as before, they + // should have 3 poi (the touching point and two crossing intersections) + Rectangle r2 = r.getExpanded(0, 0, 100, 0); + e2 = new Ellipse(r2); + intersections = e1.getIntersections(e2); + assertEquals(3, intersections.length); + + // if we create a y-scaled ellipse at the same position as before, they + // should have 3 poi (the touching point and two crossing intersections) + r2 = r.getExpanded(0, 0, 0, 100); + e2 = new Ellipse(r2); + intersections = e1.getIntersections(e2); + assertEquals(3, intersections.length); + + // if we create an x-scaled ellipse at the same y-position as before, + // the + // two should touch at two positions: + r2 = r.getExpanded(50, 0, 50, 0); + e2 = new Ellipse(r2); + intersections = e1.getIntersections(e2); + assertEquals(2, intersections.length); + + // the two poi are top and bottom border mid-points: + int equalsTop = 0; + int equalsBottom = 0; + + Rectangle bounds = e1.getBounds(); + for (Point poi : intersections) { + // we need to losen the equality test, because the points of + // intersection may be to unprecise + Point top = bounds.getTop(); + if (top.equals(poi)) { + equalsTop++; + } + if (bounds.getBottom().equals(poi)) { + equalsBottom++; + } + } + + assertEquals( + "The top border mid-point should be one of the two intersections.", + 1, equalsTop); + assertEquals( + "The bottom border mid-point should be one of the two intersections.", + 1, equalsBottom); + + // if we create a y-scaled ellipse at the same x-position as before, the + // two should touch at two positions: + r2 = r.getExpanded(0, 50, 0, 50); + e2 = new Ellipse(r2); + intersections = e1.getIntersections(e2); + assertEquals(2, intersections.length); + + // the two poi are left and right border mid-points: + int equalsLeft = 0; + int equalsRight = 0; + + for (Point poi : intersections) { + if (bounds.getLeft().equals(poi)) { + equalsLeft++; + } + if (bounds.getRight().equals(poi)) { + equalsRight++; + } + } + + assertEquals( + "The left border mid-point should be one of the two intersections.", + 1, equalsLeft); + assertEquals( + "The right border mid-point should be one of the two intersections.", + 1, equalsRight); + } } Index: src/org/eclipse/gef4/geometry/tests/PolynomCalculationsTests.java =================================================================== RCS file: src/org/eclipse/gef4/geometry/tests/PolynomCalculationsTests.java diff -N src/org/eclipse/gef4/geometry/tests/PolynomCalculationsTests.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/gef4/geometry/tests/PolynomCalculationsTests.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,151 @@ +/******************************************************************************* + * Copyright (c) 2011 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.gef4.geometry.tests; + +import java.util.Arrays; + +import junit.framework.TestCase; + +import org.eclipse.gef4.geometry.utils.PolynomCalculations; +import org.eclipse.gef4.geometry.utils.PrecisionUtils; + +public class PolynomCalculationsTests extends TestCase { + + public void test_get_roots_always_true() { + try { + PolynomCalculations.getLinearRoots(0, 0); + } catch (ArithmeticException x) { + assertTrue("0x + 0 = 0 throws an ArithmeticException", true); + } + + try { + PolynomCalculations.getQuadraticRoots(0, 0, 0); + } catch (ArithmeticException x) { + assertTrue("0x^2 + 0x + 0 = 0 throws an ArithmeticException", true); + } + + try { + PolynomCalculations.getCubicRoots(0, 0, 0, 0); + } catch (ArithmeticException x) { + assertTrue( + "0x^3 + 0x^2 + 0x + 0 = 0 throws an ArithmeticException", + true); + } + } + + public void test_get_linear_roots() { + double[] solutions = PolynomCalculations.getLinearRoots(1, 0); + assertEquals("one real solution", 1, solutions.length); + assertTrue("x = 0", PrecisionUtils.equal(0, solutions[0])); + + solutions = PolynomCalculations.getLinearRoots(0, 1); + assertEquals("1 != 0", 0, solutions.length); + + solutions = PolynomCalculations.getLinearRoots(1, -2); + assertEquals("one real solution", 1, solutions.length); + assertTrue("x - 2 = 0 <=> x = 2", PrecisionUtils.equal(2, solutions[0])); + + solutions = PolynomCalculations.getLinearRoots(7, -7); + assertEquals("one real solution", 1, solutions.length); + assertTrue("7x - 7 = 0 <=> x = 1", + PrecisionUtils.equal(1, solutions[0])); + + solutions = PolynomCalculations.getLinearRoots(0.5, -10); + assertEquals("one real solution", 1, solutions.length); + assertTrue("0.5x - 10 = 0 <=> x = 20", + PrecisionUtils.equal(20, solutions[0])); + + solutions = PolynomCalculations.getLinearRoots(5, -0.5); + assertEquals("one real solution", 1, solutions.length); + assertTrue("5x - 0.5 = 0 <=> x = 0.1", + PrecisionUtils.equal(0.1, solutions[0])); + + solutions = PolynomCalculations.getLinearRoots(1, 1); + assertEquals("one real solution", 1, solutions.length); + assertTrue("x + 1 = 0 <=> x = -1", + PrecisionUtils.equal(-1, solutions[0])); + } + + public void test_get_quadratic_roots() { + double[] solutions = PolynomCalculations.getQuadraticRoots(1, 1, 0); + Arrays.sort(solutions); + assertEquals("two real solution", 2, solutions.length); + assertTrue("x^2 + x = 0 <=> x(x + 1) = 0 => x = 0 v x = -1", + PrecisionUtils.equal(-1, solutions[0])); + assertTrue("x^2 + x + 0 = 0 <=> x(x + 1) = 0 => x = 0 v x = -1", + PrecisionUtils.equal(0, solutions[1])); + + solutions = PolynomCalculations.getQuadraticRoots(1, 0, 1); + assertEquals("x^2 + 1 = 0 => no real solutions", 0, solutions.length); + + solutions = PolynomCalculations.getQuadraticRoots(1, 0, -1); + Arrays.sort(solutions); + assertEquals("two real solution", 2, solutions.length); + assertTrue("x^2 - 1 = 0 => x = +-1", + PrecisionUtils.equal(-1, solutions[0])); + assertTrue("x^2 - 1 = 0 => x = +-1", + PrecisionUtils.equal(1, solutions[1])); + + solutions = PolynomCalculations.getQuadraticRoots(1, 0, 0); + assertEquals("one real solution", 1, solutions.length); + assertTrue("x^2 = 0 <=> x = 0", PrecisionUtils.equal(0, solutions[0])); + + solutions = PolynomCalculations.getQuadraticRoots(0, 1, 0); + assertEquals("one real solution", 1, solutions.length); + assertTrue("x = 0", PrecisionUtils.equal(0, solutions[0])); + + solutions = PolynomCalculations.getQuadraticRoots(2, 0, -8); + Arrays.sort(solutions); + assertEquals("two real solution", 2, solutions.length); + assertTrue("2x^2 - 8 = 0 <=> x^2 = 4 => x = +-2", + PrecisionUtils.equal(-2, solutions[0])); + assertTrue("2x^2 - 8 = 0 <=> x^2 = 4 => x = +-2", + PrecisionUtils.equal(2, solutions[1])); + + solutions = PolynomCalculations.getQuadraticRoots(1, -3, 2); + Arrays.sort(solutions); + assertEquals("two real solution", 2, solutions.length); + assertTrue("x^2 - 3x + 2 = 0 <=> (x - 2)(x - 1) = 0 => x = 2 v x = 1", + PrecisionUtils.equal(1, solutions[0])); + assertTrue("x^2 - 3x + 2 = 0 <=> (x - 2)(x - 1) = 0 => x = 2 v x = 1", + PrecisionUtils.equal(2, solutions[1])); + } + + public void test_get_cubic_roots() { + double[] solutions = PolynomCalculations.getCubicRoots(1, 1, 1, 0); + Arrays.sort(solutions); + assertEquals("one real solution", 1, solutions.length); + assertTrue("x^3 + x^2 + x = 0 <=> x(x^2 + x + 1) = 0 => x = 0", + PrecisionUtils.equal(0, solutions[0])); + + solutions = PolynomCalculations.getCubicRoots(1, -6, 12, -8); + assertEquals("one real solution", 1, solutions.length); + assertTrue("x = 2 solves the polynom", + PrecisionUtils.equal(2, solutions[0])); + + solutions = PolynomCalculations.getCubicRoots(1, 1, -33, 63); + Arrays.sort(solutions); + assertEquals("two real solutions", 2, solutions.length); + assertTrue("x1 = -7", PrecisionUtils.equal(-7, solutions[0])); + assertTrue("x2 = 3", PrecisionUtils.equal(3, solutions[1])); + + solutions = PolynomCalculations.getCubicRoots(1, 3, -6, -1); + Arrays.sort(solutions); + assertEquals("three real solutions", 3, solutions.length); + assertTrue("x1 = -4.33181031", + PrecisionUtils.equal(-4.331810310203, solutions[0])); + assertTrue("x2 = -0.15524041", + PrecisionUtils.equal(-0.15524041215, solutions[1])); + assertTrue("x3 = 1.48705072", + PrecisionUtils.equal(1.487050722353, solutions[2])); + } +} Index: src/org/eclipse/gef4/geometry/tests/PolynomTests.java =================================================================== RCS file: src/org/eclipse/gef4/geometry/tests/PolynomTests.java diff -N src/org/eclipse/gef4/geometry/tests/PolynomTests.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/gef4/geometry/tests/PolynomTests.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2011 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.gef4.geometry.tests; + +import junit.framework.TestCase; + +public class PolynomTests extends TestCase { + public void test_getNearest() { + assertTrue("nyi", false); + } +} Index: src/org/eclipse/gef4/geometry/tests/PrecisionUtilsTest.java =================================================================== RCS file: /cvsroot/tools/org.eclipse.gef/GEF4/plugins/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/PrecisionUtilsTest.java,v retrieving revision 1.3 diff -u -r1.3 PrecisionUtilsTest.java --- src/org/eclipse/gef4/geometry/tests/PrecisionUtilsTest.java 23 Sep 2011 14:44:46 -0000 1.3 +++ src/org/eclipse/gef4/geometry/tests/PrecisionUtilsTest.java 21 Oct 2011 12:49:33 -0000 @@ -6,7 +6,7 @@ * http://www.eclipse.org/legal/epl-v10.html * * Contributors: - * itemis AG - initial API and implementation + * Alexander Nyßen (itemis AG) - initial API and implementation *******************************************************************************/ package org.eclipse.gef4.geometry.tests; @@ -26,6 +26,12 @@ private static final double PRECISION_FRACTION = TestUtils .getPrecisionFraction(); + private static final double UNRECOGNIZABLE_FRACTION = PRECISION_FRACTION + - PRECISION_FRACTION / 10; + + private static final double RECOGNIZABLE_FRACTION = PRECISION_FRACTION + + PRECISION_FRACTION / 10; + /** * Tests the precision tolerance of * {@link PrecisionUtils#equal(double, double)}, by checking whether two @@ -36,34 +42,79 @@ public void test_equal() { // test equality is recognized in case we increase/decrease beyond the // given scale - double d1 = -9.486614173228347; - double d2 = -34.431496062992125; - double d3 = 41.99055118110236; - double d4 = 25.92755905511811; + double d1 = -9.48661417322834712; + double d2 = -34.431496062992123985; + double d3 = 41.99055118110236626; + double d4 = 25.927559055118116565; double d5 = Double.MIN_VALUE; double d6 = 1 - d5; - assertTrue(PrecisionUtils.equal(d1, d1 - PRECISION_FRACTION / 10)); - assertTrue(PrecisionUtils.equal(d2, d2 - PRECISION_FRACTION / 10)); - assertTrue(PrecisionUtils.equal(d3, d3 - PRECISION_FRACTION / 10)); - assertTrue(PrecisionUtils.equal(d4, d4 - PRECISION_FRACTION / 10)); - - assertTrue(PrecisionUtils.equal(d1, d1 + PRECISION_FRACTION / 10)); - assertTrue(PrecisionUtils.equal(d2, d2 + PRECISION_FRACTION / 10)); - assertTrue(PrecisionUtils.equal(d3, d3 + PRECISION_FRACTION / 10)); - assertTrue(PrecisionUtils.equal(d4, d4 + PRECISION_FRACTION / 10)); - - assertFalse(PrecisionUtils.equal(d1, d1 - PRECISION_FRACTION)); - assertFalse(PrecisionUtils.equal(d2, d2 - PRECISION_FRACTION)); - assertFalse(PrecisionUtils.equal(d3, d3 - PRECISION_FRACTION)); - assertFalse(PrecisionUtils.equal(d4, d4 - PRECISION_FRACTION)); - - assertFalse(PrecisionUtils.equal(d1, d1 + PRECISION_FRACTION)); - assertFalse(PrecisionUtils.equal(d2, d2 + PRECISION_FRACTION)); - assertFalse(PrecisionUtils.equal(d3, d3 + PRECISION_FRACTION)); - assertFalse(PrecisionUtils.equal(d4, d4 + PRECISION_FRACTION)); + assertTrue(PrecisionUtils.equal(d1, d1 - UNRECOGNIZABLE_FRACTION)); + assertTrue(PrecisionUtils.equal(d2, d2 - UNRECOGNIZABLE_FRACTION)); + assertTrue(PrecisionUtils.equal(d3, d3 - UNRECOGNIZABLE_FRACTION)); + assertTrue(PrecisionUtils.equal(d4, d4 - UNRECOGNIZABLE_FRACTION)); + + assertTrue(PrecisionUtils.equal(d1, d1 + UNRECOGNIZABLE_FRACTION)); + assertTrue(PrecisionUtils.equal(d2, d2 + UNRECOGNIZABLE_FRACTION)); + assertTrue(PrecisionUtils.equal(d3, d3 + UNRECOGNIZABLE_FRACTION)); + assertTrue(PrecisionUtils.equal(d4, d4 + UNRECOGNIZABLE_FRACTION)); + + assertFalse(PrecisionUtils.equal(d1, d1 - RECOGNIZABLE_FRACTION)); + assertFalse(PrecisionUtils.equal(d2, d2 - RECOGNIZABLE_FRACTION)); + assertFalse(PrecisionUtils.equal(d3, d3 - RECOGNIZABLE_FRACTION)); + assertFalse(PrecisionUtils.equal(d4, d4 - RECOGNIZABLE_FRACTION)); + + assertFalse(PrecisionUtils.equal(d1, d1 + RECOGNIZABLE_FRACTION)); + assertFalse(PrecisionUtils.equal(d2, d2 + RECOGNIZABLE_FRACTION)); + assertFalse(PrecisionUtils.equal(d3, d3 + RECOGNIZABLE_FRACTION)); + assertFalse(PrecisionUtils.equal(d4, d4 + RECOGNIZABLE_FRACTION)); + } + + public void test_greater() { + double unrec = UNRECOGNIZABLE_FRACTION; + + assertTrue(PrecisionUtils.greater(1, 0)); + assertTrue(PrecisionUtils.greater(RECOGNIZABLE_FRACTION, 0)); + assertTrue(PrecisionUtils.greater(unrec, 0)); + assertTrue(PrecisionUtils.greater(0, 0)); + assertTrue(PrecisionUtils.greater(-unrec, 0)); + assertTrue(PrecisionUtils.greater(unrec - PRECISION_FRACTION, 0)); + assertFalse(PrecisionUtils.greater(-1, 0)); + assertFalse(PrecisionUtils.greater(-PRECISION_FRACTION, 0)); + } + + public void test_smaller() { + double unrec = UNRECOGNIZABLE_FRACTION; + + assertTrue(PrecisionUtils.smaller(-1, 0)); + assertTrue(PrecisionUtils.smaller(-PRECISION_FRACTION, 0)); + assertTrue(PrecisionUtils.smaller(-unrec, 0)); + assertTrue(PrecisionUtils.smaller(0, 0)); + assertTrue(PrecisionUtils.smaller(unrec, 0)); + assertTrue(PrecisionUtils.smaller(PRECISION_FRACTION - unrec, 0)); + assertFalse(PrecisionUtils.smaller(1, 0)); + assertFalse(PrecisionUtils.smaller(PRECISION_FRACTION, 0)); + } + + public void test_greaterEqual() { + double unrec = UNRECOGNIZABLE_FRACTION; + + assertTrue(PrecisionUtils.greaterEqual(1, 0)); + assertTrue(PrecisionUtils.greaterEqual(0, 0)); + assertTrue(PrecisionUtils.greaterEqual(-unrec, 0)); + assertTrue(PrecisionUtils.greaterEqual(-PRECISION_FRACTION, 0)); + assertFalse(PrecisionUtils.greaterEqual(-1, 0)); + assertFalse(PrecisionUtils.greaterEqual(-PRECISION_FRACTION - unrec, 0)); + } + + public void test_smallerEqual() { + double unrec = UNRECOGNIZABLE_FRACTION; - assertEquals(0, PrecisionUtils.round(d5), 0); - assertEquals(1, PrecisionUtils.round(d6), 0); + assertTrue(PrecisionUtils.smallerEqual(-1, 0)); + assertTrue(PrecisionUtils.smallerEqual(0, 0)); + assertTrue(PrecisionUtils.smallerEqual(unrec, 0)); + assertTrue(PrecisionUtils.smallerEqual(PRECISION_FRACTION, 0)); + assertFalse(PrecisionUtils.smallerEqual(1, 0)); + assertFalse(PrecisionUtils.smallerEqual(PRECISION_FRACTION + unrec, 0)); } } Index: src/org/eclipse/gef4/geometry/tests/QuadraticCurveTests.java =================================================================== RCS file: src/org/eclipse/gef4/geometry/tests/QuadraticCurveTests.java diff -N src/org/eclipse/gef4/geometry/tests/QuadraticCurveTests.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/gef4/geometry/tests/QuadraticCurveTests.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,225 @@ +/******************************************************************************* + * Copyright (c) 2011 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.gef4.geometry.tests; + +import junit.framework.TestCase; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.shapes.QuadraticCurve; + +public class QuadraticCurveTests extends TestCase { + private final Point p = new Point(-10, -10), c = new Point(10, 0), + q = new Point(0, 10); + + public void test_get() { + QuadraticCurve curve = new QuadraticCurve(p, c, q); + + assertEquals("curve.get(0) returns the curve's start point", p, + curve.get(0)); + assertEquals("curve.get(1) returns the curve's end point", q, + curve.get(1)); + + boolean thrown = false; + try { + curve.get(-0.1); + } catch (IllegalArgumentException x) { + thrown = true; + } + assertTrue("curve.get(t < 0) throws an IllegalArgumentException", + thrown); + + thrown = false; + try { + curve.get(1.1); + } catch (IllegalArgumentException x) { + thrown = true; + } + assertTrue("curve.get(t > 1) throws an IllegalArgumentException", + thrown); + } + + public void test_contains_Point() { + QuadraticCurve curve = new QuadraticCurve(p, c, q); + + // check fix points: + assertEquals(curve.contains(p), true); + assertEquals(curve.contains(q), true); + assertEquals(curve.contains(c), false); // not always true, but for our + // c it is + + // check 0 <= t <= 1: + for (double t = 0; t <= 1; t += 0.0123456789) { + assertEquals("curve.get(t = " + t + + " in range [0, 1]) lies on the curve", true, + curve.contains(curve.get(t))); + } + } + + public void test_get_Bounds() { + QuadraticCurve curve = new QuadraticCurve(p, c, q); + + // p is the top-left point: (y-coordinates are inverted) + assertEquals(curve.getBounds().getTopLeft(), p); + } + + // public void test_get_FatLine() { + // QuadraticCurve curve = new QuadraticCurve(p, c, p); + // assertEquals(null, curve.getFatLine()); + // } + + // public void test_clip_FatLine() { + // QuadraticCurve curve = new QuadraticCurve(p, c, q); + // + // // FatLine L = new FatLine(new Straight(new Vector(0, -5), + // // new Vector(1, 0)), -10, 0); + // // QuadraticCurve clipped = curve.clip(L); + // // + // // assertTrue("clipped curve's endpoints lie on the FatLine", + // // L.contains(clipped.getP1())); + // // assertTrue("clipped curve's endpoints lie on the FatLine", + // // L.contains(clipped.getP2())); + // // + // // L = new FatLine(new Straight(new Vector(100, 100), new Vector(100, + // 0)), + // // 0, 1); + // // clipped = curve.clip(L); + // // assertEquals(null, clipped); + // // + // // L = new FatLine( + // // new Straight(new Vector(-100, -100), new Vector(100, 0)), -200, + // // 0); + // // clipped = curve.clip(L); + // // assertEquals(curve, clipped); + // } + + public void test_intersects_Line() { + // TODO! + } + + public void test_intersects_Rectangle() { + // TODO! + } + + public void test_getIntersections_QuadraticCurve() { + // some general cases + Point p1 = new Point(164.0, 43.0); + Point p2 = new Point(169.0, 165.0); + Point p3 = new Point(307.0, 239.0); + Point q1 = new Point(100.0, 100.0); + Point q2 = new Point(200.0, 200.0); + Point q3 = new Point(300.0, 100.0); + + QuadraticCurve p = new QuadraticCurve(p1, p2, p3); + QuadraticCurve q = new QuadraticCurve(q1, q2, q3); + + Point[] intersections = q.getIntersections(p); + assertEquals("p and q have exactly one intersection", 1, + intersections.length); + + for (Point poi : intersections) { + assertEquals("each point of intersection lies on p", true, + p.contains(poi)); + assertEquals("each point of intersection lies on q", true, + q.contains(poi)); + } + + p1 = new Point(200.0, 100.0); + p2 = new Point(304.0, 203.0); + p3 = new Point(300.0, 300.0); + + p = new QuadraticCurve(p1, p2, p3); + + intersections = q.getIntersections(p); + assertEquals("p and q have exactly one intersection", 1, + intersections.length); + + for (Point poi : intersections) { + assertEquals("each point of intersection lies on p", true, + p.contains(poi)); + assertEquals("each point of intersection lies on q", true, + q.contains(poi)); + } + + p1 = new Point(144.0, 59.0); + p2 = new Point(358.0, 130.0); + p3 = new Point(300.0, 300.0); + + p = new QuadraticCurve(p1, p2, p3); + + intersections = q.getIntersections(p); + assertEquals("p and q have exactly one intersection", 1, + intersections.length); + + for (Point poi : intersections) { + assertEquals("each point of intersection lies on p", true, + p.contains(poi)); + assertEquals("each point of intersection lies on q", true, + q.contains(poi)); + } + + p1 = new Point(151.0, 272.0); + p2 = new Point(101.0, 187.0); + p3 = new Point(205.0, 48.0); + + p = new QuadraticCurve(p1, p2, p3); + + intersections = q.getIntersections(p); + assertEquals("p and q have exactly one intersection", 1, + intersections.length); + + for (Point poi : intersections) { + assertEquals("each point of intersection lies on p", true, + p.contains(poi)); + assertEquals("each point of intersection lies on q", true, + q.contains(poi)); + } + + p1 = new Point(184.0, 83.0); + p2 = new Point(400.0, 200.0); + p3 = new Point(300.0, 300.0); + + p = new QuadraticCurve(p1, p2, p3); + + intersections = p.getIntersections(q); + assertEquals("p and q have exactly one intersection", 1, + intersections.length); + + for (Point poi : intersections) { + assertEquals("each point of intersection lies on p", true, + p.contains(poi)); + assertEquals("each point of intersection lies on q", true, + q.contains(poi)); + } + + p1 = new Point(196.0, 89.0); + p2 = new Point(335.0, 215.0); + p3 = new Point(300.0, 300.0); + + p = new QuadraticCurve(p1, p2, p3); + + intersections = q.getIntersections(p); + assertEquals("p and q have exactly one intersection", 1, + intersections.length); + + for (Point poi : intersections) { + assertEquals("each point of intersection lies on p", true, + p.contains(poi)); + assertEquals("each point of intersection lies on q", true, + q.contains(poi)); + } + + // special tangential cases + // TODO + + // special + } +} Index: src/org/eclipse/gef4/geometry/tests/RectangleTests.java =================================================================== RCS file: /cvsroot/tools/org.eclipse.gef/GEF4/plugins/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/RectangleTests.java,v retrieving revision 1.2 diff -u -r1.2 RectangleTests.java --- src/org/eclipse/gef4/geometry/tests/RectangleTests.java 31 Aug 2011 08:06:51 -0000 1.2 +++ src/org/eclipse/gef4/geometry/tests/RectangleTests.java 21 Oct 2011 12:49:34 -0000 @@ -31,6 +31,12 @@ private static final double PRECISION_FRACTION = TestUtils .getPrecisionFraction(); + private static final double UNRECOGNIZABLE_FRACTION = PRECISION_FRACTION + - PRECISION_FRACTION / 10; + + private static final double RECOGNIZABLE_FRACTION = PRECISION_FRACTION + + PRECISION_FRACTION / 10; + public void testBorderPointsCalculation() { Rectangle rect = new Rectangle(1, 2, 3, 4); assertEquals(rect.getTopLeft(), new Point(1, 2)); @@ -87,49 +93,49 @@ assertTrue(preciseRect.contains(topLeft)); assertTrue(preciseRect.contains(topLeft.x, topLeft.y)); assertFalse(preciseRect.contains(topLeft.getTranslated( - -PRECISION_FRACTION, -PRECISION_FRACTION))); + -RECOGNIZABLE_FRACTION, -RECOGNIZABLE_FRACTION))); Point top = preciseRect.getTop(); assertTrue(preciseRect.contains(top)); assertTrue(preciseRect.contains(top.x, top.y)); assertFalse(preciseRect.contains(top.getTranslated(0, - -PRECISION_FRACTION))); + -RECOGNIZABLE_FRACTION))); Point topRight = preciseRect.getTopRight(); assertTrue(preciseRect.contains(topRight)); assertTrue(preciseRect.contains(topRight.x, topRight.y)); assertFalse(preciseRect.contains(topRight.getTranslated( - PRECISION_FRACTION, -PRECISION_FRACTION))); + RECOGNIZABLE_FRACTION, -RECOGNIZABLE_FRACTION))); Point left = preciseRect.getLeft(); assertTrue(preciseRect.contains(left)); assertTrue(preciseRect.contains(left.x, left.y)); assertFalse(preciseRect.contains(left.getTranslated( - -PRECISION_FRACTION, 0))); + -RECOGNIZABLE_FRACTION, 0))); Point right = preciseRect.getRight(); assertTrue(preciseRect.contains(right)); assertTrue(preciseRect.contains(right.x, right.y)); assertFalse(preciseRect.contains(right.getTranslated( - PRECISION_FRACTION, 0))); + RECOGNIZABLE_FRACTION, 0))); Point bottomLeft = preciseRect.getBottomLeft(); assertTrue(preciseRect.contains(bottomLeft)); assertTrue(preciseRect.contains(bottomLeft.x, bottomLeft.y)); assertFalse(preciseRect.contains(bottomLeft.getTranslated( - -PRECISION_FRACTION, PRECISION_FRACTION))); + -RECOGNIZABLE_FRACTION, RECOGNIZABLE_FRACTION))); Point bottom = preciseRect.getBottom(); assertTrue(preciseRect.contains(bottom)); assertTrue(preciseRect.contains(bottom.x, bottom.y)); assertFalse(preciseRect.contains(bottom.getTranslated(0, - PRECISION_FRACTION))); + RECOGNIZABLE_FRACTION))); Point bottomRight = preciseRect.getBottomRight(); assertTrue(preciseRect.contains(bottomRight)); assertTrue(preciseRect.contains(bottomRight.x, bottomRight.y)); assertFalse(preciseRect.contains(bottomRight.getTranslated( - PRECISION_FRACTION, PRECISION_FRACTION))); + RECOGNIZABLE_FRACTION, RECOGNIZABLE_FRACTION))); } public void test_contains_Rectangle() { @@ -141,34 +147,34 @@ assertTrue(preciseRect.contains(preciseRect.getX(), preciseRect.getY(), preciseRect.getWidth(), preciseRect.getHeight())); assertFalse(preciseRect.contains(preciseRect.getExpanded( - PRECISION_FRACTION, PRECISION_FRACTION))); + RECOGNIZABLE_FRACTION, RECOGNIZABLE_FRACTION))); // test precision tolerance, therefore increment by an amount not // 'recognizable' - Rectangle expanded = preciseRect.getExpanded(PRECISION_FRACTION / 10, - PRECISION_FRACTION / 10, 0, 0); - Rectangle shrinked = preciseRect.getShrinked(0, 0, - PRECISION_FRACTION / 10, PRECISION_FRACTION / 10); + + Rectangle unrecognizableExpanded = preciseRect.getExpanded( + UNRECOGNIZABLE_FRACTION, UNRECOGNIZABLE_FRACTION, 0, 0); + Rectangle unrecognizableShrinked = preciseRect.getShrinked(0, 0, + UNRECOGNIZABLE_FRACTION, UNRECOGNIZABLE_FRACTION); + // contains should not recognized the changes - assertTrue(preciseRect.contains(expanded)); - assertTrue(preciseRect.contains(shrinked)); - assertTrue(expanded.contains(preciseRect)); - assertTrue(shrinked.contains(preciseRect)); - assertTrue(expanded.contains(shrinked)); - assertTrue(shrinked.contains(expanded)); + assertTrue(preciseRect.contains(unrecognizableExpanded)); + assertTrue(preciseRect.contains(unrecognizableShrinked)); + assertTrue(unrecognizableExpanded.contains(preciseRect)); + assertTrue(unrecognizableShrinked.contains(preciseRect)); + assertTrue(unrecognizableExpanded.contains(unrecognizableShrinked)); // now increment by an amount 'recognizable' - expanded = preciseRect.getExpanded(PRECISION_FRACTION, - PRECISION_FRACTION, 0, 0); - shrinked = preciseRect.getShrinked(0, 0, PRECISION_FRACTION, - PRECISION_FRACTION); + Rectangle recognizableExpanded = preciseRect.getExpanded( + RECOGNIZABLE_FRACTION, RECOGNIZABLE_FRACTION, 0, 0); + Rectangle recognizableShrinked = preciseRect.getShrinked(0, 0, + RECOGNIZABLE_FRACTION, RECOGNIZABLE_FRACTION); + // contains should now recognized the changes - assertFalse(preciseRect.contains(expanded)); - assertTrue(preciseRect.contains(shrinked)); - assertTrue(expanded.contains(preciseRect)); - assertFalse(shrinked.contains(preciseRect)); - assertTrue(expanded.contains(shrinked)); - assertFalse(shrinked.contains(expanded)); + assertFalse(preciseRect.contains(recognizableExpanded)); + assertTrue(recognizableExpanded.contains(preciseRect)); + assertFalse(recognizableShrinked.contains(preciseRect)); + assertFalse(recognizableShrinked.contains(recognizableExpanded)); } public void test_equals() { @@ -183,24 +189,23 @@ // test precision tolerance, therefore increment by an amount not // 'recognizable' - Rectangle expanded = preciseRect.getExpanded(PRECISION_FRACTION / 10, - PRECISION_FRACTION / 10, 0, 0); - Rectangle shrinked = preciseRect.getShrinked(0, 0, - PRECISION_FRACTION / 10, PRECISION_FRACTION / 10); + Rectangle unrecognizableExpanded = preciseRect.getExpanded( + UNRECOGNIZABLE_FRACTION, UNRECOGNIZABLE_FRACTION, 0, 0); + Rectangle unrecognizableShrinked = preciseRect.getShrinked(0, 0, + UNRECOGNIZABLE_FRACTION, UNRECOGNIZABLE_FRACTION); // equals should not recognize the changes - assertTrue(preciseRect.equals(expanded)); - assertTrue(preciseRect.equals(shrinked)); - assertTrue(expanded.equals(shrinked)); + assertTrue(preciseRect.equals(unrecognizableExpanded)); + assertTrue(preciseRect.equals(unrecognizableShrinked)); // increment by an amount 'recognizable' - expanded = preciseRect.getExpanded(PRECISION_FRACTION, - PRECISION_FRACTION, 0, 0); - shrinked = preciseRect.getShrinked(0, 0, PRECISION_FRACTION, - PRECISION_FRACTION); + Rectangle recognizableExpanded = preciseRect.getExpanded( + RECOGNIZABLE_FRACTION, RECOGNIZABLE_FRACTION, 0, 0); + Rectangle recognizableShrinked = preciseRect.getShrinked(0, 0, + RECOGNIZABLE_FRACTION, RECOGNIZABLE_FRACTION); // equals should now recognize the changes - assertFalse(preciseRect.equals(expanded)); - assertFalse(preciseRect.equals(shrinked)); - assertFalse(expanded.equals(shrinked)); + assertFalse(preciseRect.equals(recognizableExpanded)); + assertFalse(preciseRect.equals(recognizableShrinked)); + assertFalse(recognizableExpanded.equals(recognizableShrinked)); } public void test_scale() { @@ -238,17 +243,19 @@ public void test_shrink_AND_expand() { Rectangle preciseRect = new Rectangle(-9.486614173228347, -34.431496062992125, 41.99055118110236, 25.92755905511811); - Rectangle expanded = preciseRect.getExpanded(PRECISION_FRACTION, - PRECISION_FRACTION); - Rectangle shrinked = preciseRect.getShrinked(PRECISION_FRACTION, - PRECISION_FRACTION); - assertFalse(preciseRect.equals(expanded)); - assertFalse(preciseRect.equals(shrinked)); - assertFalse(expanded.equals(shrinked)); - expanded.shrink(PRECISION_FRACTION, PRECISION_FRACTION); - shrinked.expand(PRECISION_FRACTION, PRECISION_FRACTION); - assertEquals(preciseRect, expanded); - assertEquals(preciseRect, shrinked); + Rectangle recognizableExpanded = preciseRect.getExpanded( + RECOGNIZABLE_FRACTION, RECOGNIZABLE_FRACTION); + Rectangle recognizableShrinked = preciseRect.getShrinked( + RECOGNIZABLE_FRACTION, RECOGNIZABLE_FRACTION); + assertFalse(preciseRect.equals(recognizableExpanded)); + assertFalse(preciseRect.equals(recognizableShrinked)); + assertFalse(recognizableExpanded.equals(recognizableShrinked)); + recognizableExpanded.shrink(RECOGNIZABLE_FRACTION, + RECOGNIZABLE_FRACTION); + recognizableShrinked.expand(RECOGNIZABLE_FRACTION, + RECOGNIZABLE_FRACTION); + assertEquals(preciseRect, recognizableExpanded); + assertEquals(preciseRect, recognizableShrinked); } public void test_toSWTRectangle() { Index: src/org/eclipse/gef4/geometry/tests/StraightTests.java =================================================================== RCS file: /cvsroot/tools/org.eclipse.gef/GEF4/plugins/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/StraightTests.java,v retrieving revision 1.1 diff -u -r1.1 StraightTests.java --- src/org/eclipse/gef4/geometry/tests/StraightTests.java 30 Aug 2011 20:43:48 -0000 1.1 +++ src/org/eclipse/gef4/geometry/tests/StraightTests.java 21 Oct 2011 12:49:34 -0000 @@ -1,19 +1,21 @@ /******************************************************************************* - * Copyright (c) 2010 IBM Corporation and others. + * Copyright (c) 2010 Research Group Software Construction, + * RWTH Aachen University 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: - * Research Group Software Construction, - * RWTH Aachen University, Germany - Contribution for Bugzilla 245182 + * Alexander Nyßen (Research Group Software Construction, + * RWTH Aachen University) - initial API and implementation * *******************************************************************************/ package org.eclipse.gef4.geometry.tests; import junit.framework.TestCase; +import org.eclipse.gef4.geometry.Angle; import org.eclipse.gef4.geometry.Point; import org.eclipse.gef4.geometry.euclidean.Straight; import org.eclipse.gef4.geometry.euclidean.Vector; @@ -93,11 +95,12 @@ public void test_getAngle_withStraight() { Straight s1 = new Straight(new Vector(0, 0), new Vector(3, 3)); Straight s2 = new Straight(new Vector(0, 4), new Vector(2, 2)); - assertTrue(s1.getAngle(s2) == 0.0); + assertTrue(s1.getAngle(s2).equals(Angle.fromDeg(0))); s1 = new Straight(new Vector(0, 0), new Vector(5, 5)); s2 = new Straight(new Vector(0, 5), new Vector(0, 5)); - assertTrue((float) s1.getAngle(s2) == 45.0); // rounding effects + assertTrue(s1.getAngle(s2).equals(Angle.fromDeg(45))); // rounding + // effects } public void test_equals() { Index: src/org/eclipse/gef4/geometry/tests/TestUtils.java =================================================================== RCS file: /cvsroot/tools/org.eclipse.gef/GEF4/plugins/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/TestUtils.java,v retrieving revision 1.2 diff -u -r1.2 TestUtils.java --- src/org/eclipse/gef4/geometry/tests/TestUtils.java 31 Aug 2011 08:06:51 -0000 1.2 +++ src/org/eclipse/gef4/geometry/tests/TestUtils.java 21 Oct 2011 12:49:34 -0000 @@ -11,7 +11,7 @@ *******************************************************************************/ package org.eclipse.gef4.geometry.tests; -import java.lang.reflect.Field; +import java.lang.reflect.Method; import org.eclipse.gef4.geometry.utils.PrecisionUtils; @@ -27,16 +27,13 @@ // this class should not be instantiated by clients } - protected static double getPrecisionFraction() { - return getPrecisionUtilsConstant("FRACTION"); - } - - private static double getPrecisionUtilsConstant(String name) { - Field f; + public static double getPrecisionFraction() { + Method f; try { - f = PrecisionUtils.class.getDeclaredField(name); + f = PrecisionUtils.class.getDeclaredMethod("calculateFraction", + int.class); f.setAccessible(true); - return f.getDouble(PrecisionUtils.class); + return ((Double) f.invoke(PrecisionUtils.class, 0)).doubleValue(); } catch (Exception e) { throw new IllegalArgumentException(e); } Index: src/org/eclipse/gef4/geometry/tests/VectorTests.java =================================================================== RCS file: /cvsroot/tools/org.eclipse.gef/GEF4/plugins/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/VectorTests.java,v retrieving revision 1.3 diff -u -r1.3 VectorTests.java --- src/org/eclipse/gef4/geometry/tests/VectorTests.java 31 Aug 2011 08:06:51 -0000 1.3 +++ src/org/eclipse/gef4/geometry/tests/VectorTests.java 21 Oct 2011 12:49:34 -0000 @@ -28,16 +28,21 @@ private static final double PRECISION_FRACTION = TestUtils .getPrecisionFraction(); + private static final double UNRECOGNIZABLE_FRACTION = PRECISION_FRACTION + - PRECISION_FRACTION / 10; + private static final double RECOGNIZABLE_FRACTION = PRECISION_FRACTION + + PRECISION_FRACTION / 10; + public void test_Equals() { Vector a = new Vector(3, 2); Vector b = new Vector(2, -2); assertTrue(a.equals(a)); assertFalse(a.equals(b)); assertFalse(a.equals(new Point(3, 2))); - assertTrue(a.equals(a.getAdded(new Vector(PRECISION_FRACTION / 10, - PRECISION_FRACTION / 10)))); - assertFalse(a.equals(a.getAdded(new Vector(PRECISION_FRACTION, - PRECISION_FRACTION)))); + assertTrue(a.equals(a.getAdded(new Vector(UNRECOGNIZABLE_FRACTION / 10, + UNRECOGNIZABLE_FRACTION / 10)))); + assertFalse(a.equals(a.getAdded(new Vector(RECOGNIZABLE_FRACTION, + RECOGNIZABLE_FRACTION)))); } public void test_getLength() {