From 56f02f2b4f0b77147559ea851db38e1ed4d9c702 Mon Sep 17 00:00:00 2001 From: mwienand Date: Fri, 16 Mar 2012 10:32:05 +0100 Subject: [PATCH] [355997] Complete refactoring of the geometry API. Adjusted abstraction interfaces with respect to intersection and containment tests. You can test two IGeometry objects for at least one point in common, by using the IGeometry.touches(IGeometry) method. For ICurve objects, intersects() and overlaps() exists to test for a finite and infinite number of intersections, as well as getIntersections(ICurve) which computes the individual points of intersection. For IShape objects, contains(IGeometry) serves to test if an IShape fully contains an arbitrary IGeometry object. The IPolyCurve does now extend the ICurve interface, so that you can use its methods for an IPolyCurve, too. The PolyBezier is used as the generic outline of any IShape. Removed a lot of duplicate code by generic abstract base classes and generic utility methods. Rotation, translation and scaling short-cut methods are lifted to the AbstractRectangleBasedGeometry and AbstractPointListBasedGeometry classes. The CurveUtils package provides utility methods to compute intersection points, and to evaluate the intersects() and contains() tests. Arbitrary Bezier curves are available using the BezierCurve class. Enhanced the javadoc documentation. Added more tests. Added examples. The examples shipped with this patch are only embryonic, but you can already use them to visualize the functionality of the geometry API. --- .../reference/tutorial/tutorial.html | 19 + .../reference/tutorial/tutorial.mediawiki | 89 + org.eclipse.gef4.geometry.examples/.classpath | 7 + org.eclipse.gef4.geometry.examples/.gitignore | 1 + org.eclipse.gef4.geometry.examples/.project | 29 + .../.settings/org.eclipse.jdt.core.prefs | 12 + .../.settings/org.eclipse.jdt.ui.prefs | 55 + .../org.eclipse.ltk.core.refactoring.prefs | 3 + .../META-INF/MANIFEST.MF | 8 + .../build.properties | 4 + .../containment/AbstractContainmentExample.java | 367 ++++ .../AbstractEllipseContainmentExample.java | 48 + .../AbstractPolygonContainmentExample.java | 60 + .../containment/EllipseLineContainment.java | 76 + .../containment/EllipsePolygonContainment.java | 72 + .../containment/EllipsePolylineContainment.java | 81 + .../containment/EllipseRectangleContainment.java | 72 + .../containment/PolygonCubicCurveContainment.java | 82 + .../containment/PolygonEllipseContainment.java | 78 + .../containment/PolygonLineContainment.java | 76 + .../containment/PolygonPolygonContainment.java | 71 + .../containment/PolygonPolylineContainment.java | 81 + .../containment/PolygonRectangleContainment.java | 73 + .../AbstractEllipseIntersectionExample.java | 48 + .../intersection/AbstractIntersectionExample.java | 378 ++++ .../AbstractPolygonIntersectionExample.java | 58 + .../intersection/BezierApproximationExample.java | 80 + .../examples/intersection/ConvexHullExample.java | 82 + .../intersection/CubicCurveDeCasteljauExample.java | 134 ++ .../intersection/CubicCurvesIntersection.java | 77 + .../EllipseCubicCurveIntersection.java | 64 + .../intersection/EllipseEllipseIntersection.java | 70 + .../intersection/EllipseLineIntersection.java | 64 + .../intersection/EllipsePolygonIntersection.java | 63 + .../intersection/EllipsePolylineIntersection.java | 66 + .../EllipseQuadraticCurveIntersection.java | 63 + .../intersection/EllipseRectangleIntersection.java | 62 + .../PolygonCubicCurveIntersection.java | 74 + .../intersection/PolygonEllipseIntersection.java | 71 + .../intersection/PolygonLineIntersection.java | 65 + .../intersection/PolygonPolygonIntersection.java | 62 + .../intersection/PolygonPolylineIntersection.java | 63 + .../PolygonQuadraticCurveIntersection.java | 70 + .../intersection/PolygonRectangleIntersection.java | 59 + .../intersection/QuadraticCurvesIntersection.java | 74 + .../scalerotate/AbstractScaleRotateExample.java | 160 ++ .../examples/scalerotate/PolygonScaleRotate.java | 66 + .../gef4/geometry/tests/CubicCurveTests.java | 5 + .../gef4/geometry/tests/CurveUtilsTests.java | 292 +++- .../eclipse/gef4/geometry/tests/EllipseTests.java | 107 +- .../org/eclipse/gef4/geometry/tests/LineTests.java | 144 ++- .../gef4/geometry/tests/PointListUtilsTests.java | 2 +- .../eclipse/gef4/geometry/tests/PolygonTests.java | 127 +- .../gef4/geometry/tests/RectangleTests.java | 67 +- .../eclipse/gef4/geometry/tests/VectorTests.java | 29 +- org.eclipse.gef4.geometry/README.txt | 11 +- .../src/org/eclipse/gef4/geometry/Point.java | 67 +- .../eclipse/gef4/geometry/euclidean/Straight.java | 96 +- .../eclipse/gef4/geometry/euclidean/Vector.java | 6 +- .../gef4/geometry/planar/AbstractGeometry.java | 33 +- .../planar/AbstractPointListBasedGeometry.java | 332 +++- .../planar/AbstractRectangleBasedGeometry.java | 187 ++- .../src/org/eclipse/gef4/geometry/planar/Arc.java | 263 +-- .../eclipse/gef4/geometry/planar/BezierCurve.java | 2093 ++++++++++++++++++-- .../eclipse/gef4/geometry/planar/BezierSpline.java | 75 +- .../eclipse/gef4/geometry/planar/CubicCurve.java | 250 +--- .../org/eclipse/gef4/geometry/planar/Ellipse.java | 339 +--- .../org/eclipse/gef4/geometry/planar/ICurve.java | 47 + .../eclipse/gef4/geometry/planar/IGeometry.java | 37 +- .../eclipse/gef4/geometry/planar/IPolyCurve.java | 12 +- .../eclipse/gef4/geometry/planar/IPolyShape.java | 13 + .../org/eclipse/gef4/geometry/planar/IShape.java | 33 +- .../src/org/eclipse/gef4/geometry/planar/Line.java | 299 ++-- .../src/org/eclipse/gef4/geometry/planar/Path.java | 24 +- .../src/org/eclipse/gef4/geometry/planar/Pie.java | 8 - .../eclipse/gef4/geometry/planar/PolyBezier.java | 120 ++ .../org/eclipse/gef4/geometry/planar/Polygon.java | 732 ++------ .../org/eclipse/gef4/geometry/planar/Polyline.java | 83 +- .../gef4/geometry/planar/QuadraticCurve.java | 203 +-- .../eclipse/gef4/geometry/planar/Rectangle.java | 312 ++-- .../org/eclipse/gef4/geometry/planar/Region.java | 2 +- .../src/org/eclipse/gef4/geometry/planar/Ring.java | 3 +- .../gef4/geometry/planar/RoundedRectangle.java | 64 +- .../gef4/geometry/projective/Straight3D.java | 116 ++ .../eclipse/gef4/geometry/projective/Vector3D.java | 195 ++ .../gef4/geometry/transform/AffineTransform.java | 108 + .../eclipse/gef4/geometry/utils/CurveUtils.java | 1478 ++++----------- .../gef4/geometry/utils/PointListUtils.java | 41 +- 88 files changed, 8586 insertions(+), 3406 deletions(-) create mode 100644 org.eclipse.gef4.geometry.doc/reference/tutorial/tutorial.html create mode 100644 org.eclipse.gef4.geometry.doc/reference/tutorial/tutorial.mediawiki create mode 100644 org.eclipse.gef4.geometry.examples/.classpath create mode 100644 org.eclipse.gef4.geometry.examples/.gitignore create mode 100644 org.eclipse.gef4.geometry.examples/.project create mode 100644 org.eclipse.gef4.geometry.examples/.settings/org.eclipse.jdt.core.prefs create mode 100644 org.eclipse.gef4.geometry.examples/.settings/org.eclipse.jdt.ui.prefs create mode 100644 org.eclipse.gef4.geometry.examples/.settings/org.eclipse.ltk.core.refactoring.prefs create mode 100644 org.eclipse.gef4.geometry.examples/META-INF/MANIFEST.MF create mode 100644 org.eclipse.gef4.geometry.examples/build.properties create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/AbstractContainmentExample.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/AbstractEllipseContainmentExample.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/AbstractPolygonContainmentExample.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/EllipseLineContainment.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/EllipsePolygonContainment.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/EllipsePolylineContainment.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/EllipseRectangleContainment.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/PolygonCubicCurveContainment.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/PolygonEllipseContainment.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/PolygonLineContainment.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/PolygonPolygonContainment.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/PolygonPolylineContainment.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/PolygonRectangleContainment.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/AbstractEllipseIntersectionExample.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/AbstractIntersectionExample.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/AbstractPolygonIntersectionExample.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/BezierApproximationExample.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/ConvexHullExample.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/CubicCurveDeCasteljauExample.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/CubicCurvesIntersection.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipseCubicCurveIntersection.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipseEllipseIntersection.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipseLineIntersection.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipsePolygonIntersection.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipsePolylineIntersection.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipseQuadraticCurveIntersection.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipseRectangleIntersection.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonCubicCurveIntersection.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonEllipseIntersection.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonLineIntersection.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonPolygonIntersection.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonPolylineIntersection.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonQuadraticCurveIntersection.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonRectangleIntersection.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/QuadraticCurvesIntersection.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/scalerotate/AbstractScaleRotateExample.java create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/scalerotate/PolygonScaleRotate.java create mode 100644 org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/PolyBezier.java create mode 100644 org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/projective/Straight3D.java create mode 100644 org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/projective/Vector3D.java diff --git a/org.eclipse.gef4.geometry.doc/reference/tutorial/tutorial.html b/org.eclipse.gef4.geometry.doc/reference/tutorial/tutorial.html new file mode 100644 index 0000000..89760f9 --- /dev/null +++ b/org.eclipse.gef4.geometry.doc/reference/tutorial/tutorial.html @@ -0,0 +1,19 @@ +

Description

This is a small tutorial for the GEF4 Geometry API.

REPL

The GEF 4 Geometry API ships with an examples packages (org.eclipse.gef4.geometry.examples) which includes a graphical REPL (Read-Evaluate-Print-Loop). This tutorial explains the API with small code snippets which can be executed directly in the REPL.

Where is the REPL? +REPL on start +Ellipse visualization

You can drag the blue control points to change the underlying figures.

1. Class hierarchy

The GEF 4 Geometry API is based on a skeleton of interfaces and implementation categories which classify the individual figures into groups according to their dimensionality and multiplicity (interface hierarchy) and their construction method (inheritance hierarchy) as follows:

2. General behaviour

You can find all the figures in the org.eclipse.gef4.geometry.planar package. In the majority of cases, you will get along with those predeclared figures. If you feel the need of another one that is not provided there, you can draw the joker (Path) which can be used to work with complicated composed shapes and curves. But we will begin by demonstrating the other classes first.

Object construction

The constructor methods of the curves and shapes are overloaded to provide either raw data to create the object from:

   Point p1 = new Point(100, 100);
+   Point p2 = new Point(200, 200);
+   Line line = new Line(100, 100, 200, 200);
+

Or to create it from existing curves/shapes:

   Line line = new Line(p1, p2);
+

Each shape can bake a copy of its own via its getCopy() method.

   Line line2 = line.getCopy();
+

Transformations

If you want to transform a shape, there are several ways to do this. You may use...

You can easily rotate a Rectangle by calling its getRotatedCCW(angle) method:

   Rectangle quad = new Rectangle(100, 100, 100, 100);
+   Polygon rhomb = quad.getRotatedCCW(Angle.fromDeg(45));
+

As you can see, the resulting shape is not a Rectangle anymore. This is due to the fact, that Rectangles are always parallel to the x- and y-axis.

If you want to rotate a Rectangle by an integer multiple of 90° and get a Rectangle back, you have to use the polygon.getBounds() method on the Polygon that the getRotatedCCW(angle) method returns:

   Rectangle rect = new Rectangle(100, 100, 200, 50);
+   Rectangle rotated = rect.getRotatedCCW(Angle.fromDeg(90)).getBounds();
+

You may wonder why the rotation method is called getRotatedCCW() and not just getRotated(). This is to assure, that the direction of rotation is correct. The postfix indicates whether you want to rotate counter-clock-wise (CCW) or clock-wise (CW). Another small assistance is the Angle class. It prevents you from mixing up degrees and radients in rotation calculations.

Translation and scaling are directly available on each form, too:

   Rectangle rect = new Rectangle(100, 100, 200, 50);
+   Rectangle big = rect.getScaled(2);
+   Rectangle small = rect.getScaled(0.5);
+   Rectangle translated = rect.getTranslated(-50, 50);
+   //...
+

Rotation and scaling do always use a relative point to rotate around and scale away from, respectively. If you omit it, the transformation will use the mid-point of the IGeometry as the relative point for the transformation. This behaviour is considered to be least surprising, although some people might expect the scaling and the rotation to be relative to (0, 0) which is true for Points and Vectors.

Interaction of geometric objects

Curves and shapes can interact with each other. You can test them for intersection or overlap, and you can compute the points of intersection of two curves/shapes and the overlapping section of two curves/shapes.

To do so, the ICurve and IShape interfaces provide various methods, namely touches(), contains(), overlaps(), getIntersections() and getOverlap() which return information on the relationship of two shapes.

For example, you may wish to compute the points of intersection between an ellipse an a line:

   Point[] intersections = ellipse.getIntersections(line);
+

Or you are dealing with two overlapping curves and you want to get the overlap:

   CubicCurve overlap = c1.getOverlap(c2);
+

Regions

TODO... delegates to the AWT functionality.

\ No newline at end of file diff --git a/org.eclipse.gef4.geometry.doc/reference/tutorial/tutorial.mediawiki b/org.eclipse.gef4.geometry.doc/reference/tutorial/tutorial.mediawiki new file mode 100644 index 0000000..9f076d1 --- /dev/null +++ b/org.eclipse.gef4.geometry.doc/reference/tutorial/tutorial.mediawiki @@ -0,0 +1,89 @@ +== Description == + +This is a small tutorial for the GEF4 Geometry API. + +== REPL == + +The GEF 4 Geometry API ships with an examples packages (org.eclipse.gef4.geometry.examples) which includes a graphical REPL (Read-Evaluate-Print-Loop). This tutorial explains the API with small code snippets which can be executed directly in the REPL. + +[[File:where-is-the-repl.png|Where is the REPL?]] +[[File:repl-start|REPL on start]] +[[File:repl-example|Ellipse visualization]] + +You can drag the blue control points to change the underlying figures. + +== 1. Class hierarchy == + +The GEF 4 Geometry API is based on a skeleton of interfaces and implementation categories which classify the individual figures into groups according to their dimensionality and multiplicity (interface hierarchy) and their construction method (inheritance hierarchy) as follows: + + + + +== 2. General behaviour == + +You can find all the figures in the [[GEF/GEF4/Geometry#Planar | org.eclipse.gef4.geometry.planar]] package. In the majority of cases, you will get along with those predeclared figures. If you feel the need of another one that is not provided there, you can draw the joker ([[GEF/GEF4/Geometry#Path |Path]]) which can be used to work with complicated composed shapes and curves. But we will begin by demonstrating the other classes first. + +=== Object construction === + +The constructor methods of the curves and shapes are overloaded to provide either raw data to create the object from: + + Point p1 = new Point(100, 100); + Point p2 = new Point(200, 200); + Line line = new Line(100, 100, 200, 200); + +Or to create it from existing curves/shapes: + + Line line = new Line(p1, p2); + +Each shape can bake a copy of its own via its getCopy() method. + + Line line2 = line.getCopy(); + +=== Transformations === + +If you want to transform a shape, there are several ways to do this. You may use... + +* ...the general [[GEF/GEF4/Geometry#AffineTransform |AffineTransform]] class using the shape.getTransformed(transformation) method +* ...short-cut methods for the individual transformations + +You can easily rotate a [[GEF/GEF4/Geometry#Rectangle |Rectangle]] by calling its getRotatedCCW(angle) method: + + Rectangle quad = new Rectangle(100, 100, 100, 100); + Polygon rhomb = quad.getRotatedCCW(Angle.fromDeg(45)); + +As you can see, the resulting shape is not a [[GEF/GEF4/Geometry#Rectangle |Rectangle]] anymore. This is due to the fact, that [[GEF/GEF4/Geometry#Rectangle |Rectangles]] are always parallel to the x- and y-axis. + +If you want to rotate a Rectangle by an integer multiple of 90° and get a [[GEF/GEF4/Geometry#Rectangle |Rectangle]] back, you have to use the polygon.getBounds() method on the [[GEF/GEF4/Geometry#Polygon |Polygon]] that the getRotatedCCW(angle) method returns: + + Rectangle rect = new Rectangle(100, 100, 200, 50); + Rectangle rotated = rect.getRotatedCCW(Angle.fromDeg(90)).getBounds(); + +You may wonder why the rotation method is called getRotatedCCW() and not just getRotated(). This is to assure, that the direction of rotation is correct. The postfix indicates whether you want to rotate counter-clock-wise (CCW) or clock-wise (CW). Another small assistance is the [[GEF/GEF4/Geometry#Angle |Angle]] class. It prevents you from mixing up degrees and radians in rotation calculations. + +Translation and scaling are directly available on each form, too: + + Rectangle rect = new Rectangle(100, 100, 200, 50); + Rectangle big = rect.getScaled(2); + Rectangle small = rect.getScaled(0.5); + Rectangle translated = rect.getTranslated(-50, 50); + //... + +Rotation and scaling do always use a relative point to rotate around and scale away from, respectively. If you omit it, the transformation will use the mid-point of the [[GEF/GEF4/Geometry#IGeometry| IGeometry]] as the relative point for the transformation. This behavior is considered to be least surprising, although some people might expect the scaling and the rotation to be relative to (0, 0) which is the default behavior for Points and Vectors. + +=== Interaction of geometric objects === + +Curves and shapes can interact with each other. You can test them for intersection or overlap, and you can compute the points of intersection of two curves/shapes and the overlapping section of two curves/shapes. + +To do so, the [[GEF/GEF4/Geometry#ICurve| ICurve]] and [[GEF/GEF4/Geometry#IShape| IShape]] interfaces provide various methods, namely touches(), contains(), overlaps(), getIntersections() and getOverlap() which return information on the relationship of two shapes. + +For example, you may wish to compute the points of intersection between and ellipse an a line: + + Point[] intersections = ellipse.getIntersections(line); + +Or you are dealing with two overlapping curves and you want to get the overlap: + + CubicCurve overlap = c1.getOverlap(c2); + +=== Regions === + +TODO... delegates to the AWT functionality. diff --git a/org.eclipse.gef4.geometry.examples/.classpath b/org.eclipse.gef4.geometry.examples/.classpath new file mode 100644 index 0000000..121e527 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.eclipse.gef4.geometry.examples/.gitignore b/org.eclipse.gef4.geometry.examples/.gitignore new file mode 100644 index 0000000..5e56e04 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/.gitignore @@ -0,0 +1 @@ +/bin diff --git a/org.eclipse.gef4.geometry.examples/.project b/org.eclipse.gef4.geometry.examples/.project new file mode 100644 index 0000000..8ddf5d9 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/.project @@ -0,0 +1,29 @@ + + + org.eclipse.gef4.geometry.examples + + + org.eclipse.gef4.geometry + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.pde.PluginNature + + diff --git a/org.eclipse.gef4.geometry.examples/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.gef4.geometry.examples/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..8a93973 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,12 @@ +#Tue Aug 30 13:06:30 CEST 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/org.eclipse.gef4.geometry.examples/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.gef4.geometry.examples/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 0000000..911f7db --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,55 @@ +#Tue Aug 30 15:29:00 CEST 2011 +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=true +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_missing_override_annotations_interface_methods=true +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.correct_indentation=false +sp_cleanup.format_source_code=true +sp_cleanup.format_source_code_changes_only=false +sp_cleanup.make_local_variable_final=false +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=true +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.on_save_use_additional_actions=false +sp_cleanup.organize_imports=true +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_trailing_whitespaces=false +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_blocks=false +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff --git a/org.eclipse.gef4.geometry.examples/.settings/org.eclipse.ltk.core.refactoring.prefs b/org.eclipse.gef4.geometry.examples/.settings/org.eclipse.ltk.core.refactoring.prefs new file mode 100644 index 0000000..76c2ba2 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/.settings/org.eclipse.ltk.core.refactoring.prefs @@ -0,0 +1,3 @@ +#Thu Nov 03 10:57:56 CET 2011 +eclipse.preferences.version=1 +org.eclipse.ltk.core.refactoring.enable.project.refactoring.history=false diff --git a/org.eclipse.gef4.geometry.examples/META-INF/MANIFEST.MF b/org.eclipse.gef4.geometry.examples/META-INF/MANIFEST.MF new file mode 100644 index 0000000..dbe6e4b --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/META-INF/MANIFEST.MF @@ -0,0 +1,8 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Examples +Bundle-SymbolicName: org.eclipse.gef4.geometry.examples +Bundle-Version: 1.0.0.qualifier +Require-Bundle: org.eclipse.swt;bundle-version="3.7.0", + org.eclipse.gef4.geometry;bundle-version="0.1.0", + org.eclipse.gef4.geometry diff --git a/org.eclipse.gef4.geometry.examples/build.properties b/org.eclipse.gef4.geometry.examples/build.properties new file mode 100644 index 0000000..4c5760f --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +bin.includes = META-INF/,\ + .,\ + src/ diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/AbstractContainmentExample.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/AbstractContainmentExample.java new file mode 100644 index 0000000..b87bacb --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/AbstractContainmentExample.java @@ -0,0 +1,367 @@ +/******************************************************************************* + * 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.examples.containment; + +import java.util.ArrayList; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.Ellipse; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Rectangle; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; + +public abstract class AbstractContainmentExample implements PaintListener { + + abstract public class AbstractControllableShape { + + private static final int CONTROL_POINT_COLOR = SWT.COLOR_BLUE; + private static final int CONTROL_POINT_RADIUS = 5; + + private Canvas canvas; + private ArrayList points; + + public AbstractControllableShape(Canvas canvas) { + this.canvas = canvas; + points = new ArrayList(); + createControlPoints(); + } + + public ControlPoint addControlPoint(Point p) { + return addControlPoint(p, CONTROL_POINT_COLOR); + } + + public ControlPoint addControlPoint(Point p, int color) { + return addControlPoint(p, color, CONTROL_POINT_RADIUS); + } + + public ControlPoint addControlPoint(Point p, int color, double radius) { + ControlPoint cp = new ControlPoint(canvas, p, radius, color); + for (ControlPoint ocp : points) { + ocp.addForbiddenArea(cp); + cp.addForbiddenArea(ocp); + } + points.add(cp); + return cp; + } + + abstract public void createControlPoints(); + + abstract public IGeometry createGeometry(); + + public void drawControlPoints(GC gc) { + for (ControlPoint cp : points) { + cp.draw(gc); + } + } + + abstract public void drawShape(GC gc); + + public void fillShape(GC gc) { + drawShape(gc); + } + + public Canvas getCanvas() { + return canvas; + } + + public Point[] getControlPoints() { + Point[] points = new Point[this.points.size()]; + + int i = 0; + for (ControlPoint cp : this.points) { + points[i++] = cp.getPoint(); + } + + return points; + } + } + + /** + * A draggable point. On the screen it is represented as an ellipse. + * + * @author wienand + * + */ + class ControlPoint implements MouseListener, MouseMoveListener, Listener { + private Canvas canvas; + private int color = SWT.COLOR_BLUE; + + private Ellipse ellipse; + private boolean isDragged = false; + + // to rescale points on resize + private double oldShellHeight; + private double oldShellWidth; + + private ArrayList forbidden; + private ArrayList updateLinks; + private ControlPoint xLink, yLink; + private double relX, relY; + + private Point p; + private double radius = 5; + + /** + * Creates a new ControlPoint object. Adds event listeners to the given + * Canvas object, so that the user can drag the control point with the + * mouse. + * + * @param canvas + * Drawing area + */ + public ControlPoint(Canvas canvas) { + this.canvas = canvas; + canvas.addMouseListener(this); + canvas.addMouseMoveListener(this); + canvas.addListener(SWT.Resize, this); + oldShellWidth = canvas.getClientArea().width; + oldShellHeight = canvas.getClientArea().height; + p = new Point(0, 0); + updateLinks = new ArrayList(); + forbidden = new ArrayList(); + update(); + } + + /** + * Creates a new ControlPoint object. Adds event listeners to the given + * Canvas object, so that the user can drag the control point with the + * mouse. + * + * @param canvas + * Drawing area + * @param p + * Exact point + * @param radius + * Of the ellipse that represents the point + * @param color + * Of the ellipse that represents the point + */ + public ControlPoint(Canvas canvas, Point p, double radius, int color) { + this(canvas); + this.p = p; + this.radius = radius; + this.color = color; + update(); + } + + public void addForbiddenArea(ControlPoint cp) { + forbidden.add(cp); + } + + /** + * Draws an ellipse with the given GC at the control points location. + * + * @param gc + */ + public void draw(GC gc) { + // System.out.println(ellipse.toString()); + gc.setBackground(Display.getCurrent().getSystemColor(color)); + gc.fillOval((int) ellipse.getX(), (int) ellipse.getY(), + (int) ellipse.getWidth(), (int) ellipse.getHeight()); + } + + /** + * Returns the exact Point of this ControlPoint object. + * + * @return The exact Point of this ControlPoint object. + */ + public Point getPoint() { + return p; + } + + @Override + public void handleEvent(Event e) { + switch (e.type) { + case SWT.Resize: + Rectangle bounds = new Rectangle(canvas.getBounds()); + p.scale(bounds.getWidth() / oldShellWidth, bounds.getHeight() + / oldShellHeight); + oldShellWidth = bounds.getWidth(); + oldShellHeight = bounds.getHeight(); + update(); + break; + } + } + + private double inRange(double low, double value, double high) { + if (value < low) { + return low; + } else if (value > high) { + return high; + } + return value; + } + + @Override + public void mouseDoubleClick(MouseEvent e) { + } + + @Override + public void mouseDown(MouseEvent e) { + if (ellipse.contains(new Point(e.x, e.y))) { + isDragged = true; + } + } + + @Override + public void mouseMove(MouseEvent e) { + if (isDragged) { + relX = e.x - p.x; + relY = e.y - p.y; + p.x = e.x; + p.y = e.y; + update(); + canvas.redraw(); + } + } + + @Override + public void mouseUp(MouseEvent e) { + isDragged = false; + } + + public void setXLink(ControlPoint cp) { + xLink = cp; + cp.updateLinks.add(this); + } + + public void setYLink(ControlPoint cp) { + yLink = cp; + cp.updateLinks.add(this); + } + + private void update() { + double oldX = p.x, oldY = p.y; + + // check canvas pane: + p.x = inRange(canvas.getClientArea().x + radius, p.x, + canvas.getClientArea().x + canvas.getClientArea().width + - radius); + p.y = inRange(canvas.getClientArea().y + radius, p.y, + canvas.getClientArea().y + canvas.getClientArea().height + - radius); + + // check links: + if (xLink != null) { + p.x = xLink.p.x; + p.y += xLink.relY; + } else if (yLink != null) { // no need to link both x and y + p.x += yLink.relX; + p.y = yLink.p.y; + } + + // check forbidden areas: + for (ControlPoint cp : forbidden) { + double minDistance = radius + cp.radius; + if (p.getDistance(cp.p) < minDistance) { + if (relX > 0) { + p.x = cp.p.x - minDistance; + } else if (relX < 0) { + p.x = cp.p.x + minDistance; + } else if (relY > 0) { + p.y = cp.p.y - minDistance; + } else { + p.y = cp.p.y + minDistance; + } + } + } + + relX += p.x - oldX; + relY += p.y - oldY; + + for (ControlPoint cp : updateLinks) { + cp.update(); + } + + ellipse = new Ellipse(p.x - radius, p.y - radius, radius * 2, + radius * 2); + } + } + + private static final int INTERSECTS_COLOR = SWT.COLOR_YELLOW; + + private static final int CONTAINS_COLOR = SWT.COLOR_GREEN; + + private AbstractControllableShape controllableShape1, controllableShape2; + + private Shell shell; + + /** + * + */ + public AbstractContainmentExample(String title) { + Display display = new Display(); + shell = new Shell(display, SWT.SHELL_TRIM | SWT.DOUBLE_BUFFERED); + shell.setText(title); + + shell.setBounds(0, 0, 640, 480); + + controllableShape1 = createControllableShape1(shell); + controllableShape2 = createControllableShape2(shell); + + shell.addPaintListener(this); + shell.open(); + + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) { + display.sleep(); + } + } + } + + protected abstract boolean computeIntersects(IGeometry g1, IGeometry g2); + + protected abstract boolean computeContains(IGeometry g1, IGeometry g2); + + protected abstract AbstractControllableShape createControllableShape1( + Canvas canvas); + + protected abstract AbstractControllableShape createControllableShape2( + Canvas canvas); + + @Override + public void paintControl(PaintEvent e) { + e.gc.setAntialias(SWT.ON); + + IGeometry cs1geometry = controllableShape1.createGeometry(); + IGeometry cs2geometry = controllableShape2.createGeometry(); + + if (computeIntersects(cs1geometry, cs2geometry)) { + e.gc.setBackground(Display.getCurrent().getSystemColor( + INTERSECTS_COLOR)); + controllableShape2.fillShape(e.gc); + } + + if (computeContains(cs1geometry, cs2geometry)) { + e.gc.setBackground(Display.getCurrent().getSystemColor( + CONTAINS_COLOR)); + controllableShape2.fillShape(e.gc); + } + + controllableShape1.drawShape(e.gc); + controllableShape2.drawShape(e.gc); + + controllableShape1.drawControlPoints(e.gc); + controllableShape2.drawControlPoints(e.gc); + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/AbstractEllipseContainmentExample.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/AbstractEllipseContainmentExample.java new file mode 100644 index 0000000..e8e28bf --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/AbstractEllipseContainmentExample.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * 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.examples.containment; + +import org.eclipse.gef4.geometry.planar.Ellipse; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; + +public abstract class AbstractEllipseContainmentExample extends + AbstractContainmentExample { + + public AbstractEllipseContainmentExample(String title) { + super(title); + } + + protected AbstractControllableShape createControllableShape1(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + // the ellipse does not have any control points + } + + @Override + public Ellipse createGeometry() { + double w5 = getCanvas().getClientArea().width / 5; + double h5 = getCanvas().getClientArea().height / 5; + return new Ellipse(w5, h5, 3 * w5, 3 * h5); + } + + @Override + public void drawShape(GC gc) { + Ellipse ellipse = (Ellipse) createGeometry(); + gc.drawOval((int) ellipse.getX(), (int) ellipse.getY(), + (int) ellipse.getWidth(), (int) ellipse.getHeight()); + } + }; + } + +} \ No newline at end of file diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/AbstractPolygonContainmentExample.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/AbstractPolygonContainmentExample.java new file mode 100644 index 0000000..bb5e43b --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/AbstractPolygonContainmentExample.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * 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.examples.containment; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.Line; +import org.eclipse.gef4.geometry.planar.Polygon; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Canvas; + +public abstract class AbstractPolygonContainmentExample extends + AbstractContainmentExample { + + public AbstractPolygonContainmentExample(String title) { + super(title); + } + + @Override + protected AbstractControllableShape createControllableShape1(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + // no control points => user cannot change it + } + + @Override + public Polygon createGeometry() { + Rectangle ca = getCanvas().getClientArea(); + double w = ca.width; + double wg = w / 6; + double h = ca.height; + double hg = h / 6; + + return new Polygon(new Point[] { new Point(wg, hg), + new Point(w - wg, h - hg), new Point(wg, h - hg), + new Point(w - wg, hg) }); + } + + @Override + public void drawShape(GC gc) { + Polygon polygon = createGeometry(); + for (Line segment : polygon.getOutlineSegments()) { + gc.drawLine((int) segment.getX1(), (int) segment.getY1(), + (int) segment.getX2(), (int) segment.getY2()); + } + } + }; + } + +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/EllipseLineContainment.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/EllipseLineContainment.java new file mode 100644 index 0000000..3cf8be2 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/EllipseLineContainment.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * 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.examples.containment; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.Ellipse; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Line; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; + +public class EllipseLineContainment extends AbstractEllipseContainmentExample { + public static void main(String[] args) { + new EllipseLineContainment(); + } + + public EllipseLineContainment() { + super("Ellipse/Line Containment"); + } + + @Override + protected boolean computeIntersects(IGeometry g1, IGeometry g2) { + return ((Ellipse) g1).touches((Line) g2); + } + + @Override + protected boolean computeContains(IGeometry g1, IGeometry g2) { + return ((Ellipse) g1).contains((Line) g2); + } + + @Override + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + addControlPoint(new Point(100, 100)); + addControlPoint(new Point(300, 300)); + } + + @Override + public Line createGeometry() { + Point[] points = getControlPoints(); + return new Line(points[0], points[1]); + } + + @Override + public void drawShape(GC gc) { + Line line = createGeometry(); + gc.drawPolyline(line.toSWTPointArray()); + } + + @Override + public void fillShape(GC gc) { + int lineWidth = gc.getLineWidth(); + Color fg = gc.getForeground(); + + gc.setLineWidth(3); + gc.setForeground(gc.getBackground()); + drawShape(gc); + + gc.setForeground(fg); + gc.setLineWidth(lineWidth); + } + }; + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/EllipsePolygonContainment.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/EllipsePolygonContainment.java new file mode 100644 index 0000000..bb04c10 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/EllipsePolygonContainment.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * 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.examples.containment; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.Ellipse; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Polygon; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; + +public class EllipsePolygonContainment extends + AbstractEllipseContainmentExample { + + public static void main(String[] args) { + new EllipsePolygonContainment(); + } + + public EllipsePolygonContainment() { + super("Ellipse/Polygon Containment"); + } + + @Override + protected boolean computeIntersects(IGeometry g1, IGeometry g2) { + return ((Ellipse) g1).touches((Polygon) g2); + } + + @Override + protected boolean computeContains(IGeometry g1, IGeometry g2) { + return ((Ellipse) g1).contains((Polygon) g2); + } + + @Override + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + addControlPoint(new Point(200, 200)); + } + + @Override + public Polygon createGeometry() { + Point[] points = getControlPoints(); + Polygon polygon = new Polygon(points[0].x - 50, + points[0].y - 50, points[0].x + 100, points[0].y - 30, + points[0].x - 40, points[0].y + 60); + return polygon; + } + + @Override + public void drawShape(GC gc) { + Polygon polygon = createGeometry(); + gc.drawPolygon(polygon.toSWTPointArray()); + } + + @Override + public void fillShape(GC gc) { + Polygon polygon = createGeometry(); + gc.fillPolygon(polygon.toSWTPointArray()); + } + }; + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/EllipsePolylineContainment.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/EllipsePolylineContainment.java new file mode 100644 index 0000000..f40fa92 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/EllipsePolylineContainment.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * 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.examples.containment; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.Ellipse; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Polyline; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; + +public class EllipsePolylineContainment extends + AbstractEllipseContainmentExample { + + public static void main(String[] args) { + new EllipsePolylineContainment(); + } + + public EllipsePolylineContainment() { + super("Ellipse/Polyline Containment"); + } + + @Override + protected boolean computeIntersects(IGeometry g1, IGeometry g2) { + return ((Ellipse) g1).touches((Polyline) g2); + } + + @Override + protected boolean computeContains(IGeometry g1, IGeometry g2) { + return ((Ellipse) g1).contains((Polyline) g2); + } + + @Override + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + addControlPoint(new Point(100, 100)); + addControlPoint(new Point(500, 400)); + addControlPoint(new Point(100, 400)); + addControlPoint(new Point(500, 100)); + } + + @Override + public Polyline createGeometry() { + Point[] points = getControlPoints(); + Polyline polyline = new Polyline(points); + return polyline; + } + + @Override + public void drawShape(GC gc) { + Polyline polyline = createGeometry(); + gc.drawPolyline(polyline.toSWTPointArray()); + } + + @Override + public void fillShape(GC gc) { + int lineWidth = gc.getLineWidth(); + Color fg = gc.getForeground(); + + gc.setLineWidth(3); + gc.setForeground(gc.getBackground()); + drawShape(gc); + + gc.setLineWidth(lineWidth); + gc.setForeground(fg); + } + }; + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/EllipseRectangleContainment.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/EllipseRectangleContainment.java new file mode 100644 index 0000000..a6f7b01 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/EllipseRectangleContainment.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * 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.examples.containment; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.Ellipse; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Rectangle; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; + +public class EllipseRectangleContainment extends + AbstractEllipseContainmentExample { + public static void main(String[] args) { + new EllipseRectangleContainment(); + } + + public EllipseRectangleContainment() { + super("Ellipse/Rectangle containment"); + } + + @Override + protected boolean computeIntersects(IGeometry g1, IGeometry g2) { + return ((Ellipse) g1).touches((Rectangle) g2); + } + + @Override + protected boolean computeContains(IGeometry g1, IGeometry g2) { + return ((Ellipse) g1).contains((Rectangle) g2); + } + + @Override + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + private final double WIDTH = 50; + private final double HEIGHT = 75; + + @Override + public void createControlPoints() { + addControlPoint(new Point(110, 70)); + } + + @Override + public Rectangle createGeometry() { + Point[] points = getControlPoints(); + return new Rectangle(points[0].x - WIDTH / 2, points[0].y + - HEIGHT / 2, WIDTH, HEIGHT); + } + + @Override + public void drawShape(GC gc) { + Rectangle rect = createGeometry(); + gc.drawRectangle(rect.toSWTRectangle()); + } + + @Override + public void fillShape(GC gc) { + Rectangle rect = createGeometry(); + gc.fillRectangle(rect.toSWTRectangle()); + } + }; + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/PolygonCubicCurveContainment.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/PolygonCubicCurveContainment.java new file mode 100644 index 0000000..faa4cde --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/PolygonCubicCurveContainment.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * 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.examples.containment; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.CubicCurve; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Polygon; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Display; + +public class PolygonCubicCurveContainment extends + AbstractPolygonContainmentExample { + + public static void main(String[] args) { + new PolygonCubicCurveContainment("Polygon / Cubic Curve - Containment"); + } + + public PolygonCubicCurveContainment(String title) { + super(title); + } + + @Override + protected boolean computeIntersects(IGeometry g1, IGeometry g2) { + return ((Polygon) g1).touches((CubicCurve) g2); + } + + @Override + protected boolean computeContains(IGeometry g1, IGeometry g2) { + return ((Polygon) g1).contains((CubicCurve) g2); + } + + @Override + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + addControlPoint(new Point(200, 100)); + addControlPoint(new Point(190, 310)); + addControlPoint(new Point(410, 90)); + addControlPoint(new Point(400, 300)); + } + + @Override + public CubicCurve createGeometry() { + return new CubicCurve(getControlPoints()); + } + + @Override + public void drawShape(GC gc) { + CubicCurve c = createGeometry(); + gc.drawPath(new org.eclipse.swt.graphics.Path(Display + .getCurrent(), c.toPath().toSWTPathData())); + } + + @Override + public void fillShape(GC gc) { + int lineWidth = gc.getLineWidth(); + Color fg = gc.getForeground(); + + gc.setLineWidth(3); + gc.setForeground(gc.getBackground()); + drawShape(gc); + + gc.setForeground(fg); + gc.setLineWidth(lineWidth); + } + }; + } + +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/PolygonEllipseContainment.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/PolygonEllipseContainment.java new file mode 100644 index 0000000..4a6ce2f --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/PolygonEllipseContainment.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * 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.examples.containment; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.Ellipse; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Polygon; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; + +public class PolygonEllipseContainment extends + AbstractPolygonContainmentExample { + + public static void main(String[] args) { + new PolygonEllipseContainment(); + } + + public PolygonEllipseContainment() { + super("Polygon/Ellipse Containment"); + } + + @Override + protected boolean computeIntersects(IGeometry g1, IGeometry g2) { + return ((Polygon) g1).touches((Ellipse) g2); + } + + @Override + protected boolean computeContains(IGeometry g1, IGeometry g2) { + return ((Polygon) g1).contains((Ellipse) g2); + } + + @Override + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + ControlPoint center = addControlPoint(new Point(300, 300)); + ControlPoint a = addControlPoint(new Point(400, 300)); + ControlPoint b = addControlPoint(new Point(300, 200)); + a.setYLink(center); + b.setXLink(center); + } + + @Override + public Ellipse createGeometry() { + Point[] points = getControlPoints(); + double a = Math.abs(points[0].x - points[1].x); + double b = Math.abs(points[0].y - points[2].y); + return new Ellipse(points[0].x - a, points[0].y - b, 2 * a, + 2 * b); + } + + @Override + public void drawShape(GC gc) { + Ellipse ellipse = createGeometry(); + gc.drawOval((int) ellipse.getX(), (int) ellipse.getY(), + (int) ellipse.getWidth(), (int) ellipse.getHeight()); + } + + @Override + public void fillShape(GC gc) { + Ellipse ellipse = createGeometry(); + gc.fillOval((int) ellipse.getX(), (int) ellipse.getY(), + (int) ellipse.getWidth(), (int) ellipse.getHeight()); + } + }; + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/PolygonLineContainment.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/PolygonLineContainment.java new file mode 100644 index 0000000..40611bb --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/PolygonLineContainment.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * 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.examples.containment; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Line; +import org.eclipse.gef4.geometry.planar.Polygon; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; + +public class PolygonLineContainment extends AbstractPolygonContainmentExample { + public static void main(String[] args) { + new PolygonLineContainment(); + } + + public PolygonLineContainment() { + super("Polygon/Line Containment"); + } + + @Override + protected boolean computeIntersects(IGeometry g1, IGeometry g2) { + return ((Polygon) g1).touches((Line) g2); + } + + @Override + protected boolean computeContains(IGeometry g1, IGeometry g2) { + return ((Polygon) g1).contains((Line) g2); + } + + @Override + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + addControlPoint(new Point(100, 100)); + addControlPoint(new Point(300, 300)); + } + + @Override + public Line createGeometry() { + Point[] points = getControlPoints(); + return new Line(points[0], points[1]); + } + + @Override + public void drawShape(GC gc) { + Line line = createGeometry(); + gc.drawPolyline(line.toSWTPointArray()); + } + + @Override + public void fillShape(GC gc) { + int lineWidth = gc.getLineWidth(); + Color fg = gc.getForeground(); + + gc.setLineWidth(3); + gc.setForeground(gc.getBackground()); + drawShape(gc); + + gc.setForeground(fg); + gc.setLineWidth(lineWidth); + } + }; + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/PolygonPolygonContainment.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/PolygonPolygonContainment.java new file mode 100644 index 0000000..6dff778 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/PolygonPolygonContainment.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * 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.examples.containment; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Polygon; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; + +public class PolygonPolygonContainment extends + AbstractPolygonContainmentExample { + + public static void main(String[] args) { + new PolygonPolygonContainment(); + } + + public PolygonPolygonContainment() { + super("Polygon/Polygon Containment"); + } + + @Override + protected boolean computeIntersects(IGeometry g1, IGeometry g2) { + return ((Polygon) g1).touches((Polygon) g2); + } + + @Override + protected boolean computeContains(IGeometry g1, IGeometry g2) { + return ((Polygon) g1).contains((Polygon) g2); + } + + @Override + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + addControlPoint(new Point(200, 200)); + } + + @Override + public Polygon createGeometry() { + Point[] points = getControlPoints(); + Polygon polygon = new Polygon(points[0].x - 30, + points[0].y - 30, points[0].x + 80, points[0].y - 20, + points[0].x - 20, points[0].y + 40); + return polygon; + } + + @Override + public void drawShape(GC gc) { + Polygon polygon = createGeometry(); + gc.drawPolygon(polygon.toSWTPointArray()); + } + + @Override + public void fillShape(GC gc) { + Polygon polygon = createGeometry(); + gc.fillPolygon(polygon.toSWTPointArray()); + } + }; + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/PolygonPolylineContainment.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/PolygonPolylineContainment.java new file mode 100644 index 0000000..ae7f0cc --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/PolygonPolylineContainment.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * 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.examples.containment; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Polygon; +import org.eclipse.gef4.geometry.planar.Polyline; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; + +public class PolygonPolylineContainment extends + AbstractPolygonContainmentExample { + + public static void main(String[] args) { + new PolygonPolylineContainment(); + } + + public PolygonPolylineContainment() { + super("Polygon/Polyline Containment"); + } + + @Override + protected boolean computeIntersects(IGeometry g1, IGeometry g2) { + return ((Polygon) g1).touches((Polyline) g2); + } + + @Override + protected boolean computeContains(IGeometry g1, IGeometry g2) { + return ((Polygon) g1).contains((Polyline) g2); + } + + @Override + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + addControlPoint(new Point(100, 100)); + addControlPoint(new Point(100, 300)); + addControlPoint(new Point(300, 300)); + addControlPoint(new Point(300, 100)); + } + + @Override + public Polyline createGeometry() { + Point[] points = getControlPoints(); + Polyline polyline = new Polyline(points); + return polyline; + } + + @Override + public void drawShape(GC gc) { + Polyline polyline = createGeometry(); + gc.drawPolyline(polyline.toSWTPointArray()); + } + + @Override + public void fillShape(GC gc) { + int lineWidth = gc.getLineWidth(); + Color fg = gc.getForeground(); + + gc.setLineWidth(3); + gc.setForeground(gc.getBackground()); + drawShape(gc); + + gc.setLineWidth(lineWidth); + gc.setForeground(fg); + } + }; + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/PolygonRectangleContainment.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/PolygonRectangleContainment.java new file mode 100644 index 0000000..79eb80e --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/containment/PolygonRectangleContainment.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * 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.examples.containment; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Polygon; +import org.eclipse.gef4.geometry.planar.Rectangle; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; + +public class PolygonRectangleContainment extends + AbstractPolygonContainmentExample { + + public static void main(String[] args) { + new PolygonRectangleContainment(); + } + + public PolygonRectangleContainment() { + super("Polygon/Rectangle containment"); + } + + @Override + protected boolean computeIntersects(IGeometry g1, IGeometry g2) { + return ((Polygon) g1).touches((Rectangle) g2); + } + + @Override + protected boolean computeContains(IGeometry g1, IGeometry g2) { + return ((Polygon) g1).contains((Rectangle) g2); + } + + @Override + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + private final double WIDTH = 50; + private final double HEIGHT = 75; + + @Override + public void createControlPoints() { + addControlPoint(new Point(110, 70)); + } + + @Override + public Rectangle createGeometry() { + Point[] points = getControlPoints(); + return new Rectangle(points[0].x - WIDTH / 2, points[0].y + - HEIGHT / 2, WIDTH, HEIGHT); + } + + @Override + public void drawShape(GC gc) { + Rectangle rect = createGeometry(); + gc.drawRectangle(rect.toSWTRectangle()); + } + + @Override + public void fillShape(GC gc) { + Rectangle rect = createGeometry(); + gc.fillRectangle(rect.toSWTRectangle()); + } + }; + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/AbstractEllipseIntersectionExample.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/AbstractEllipseIntersectionExample.java new file mode 100644 index 0000000..8f51b9c --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/AbstractEllipseIntersectionExample.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * 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.examples.intersection; + +import org.eclipse.gef4.geometry.planar.Ellipse; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; + +public abstract class AbstractEllipseIntersectionExample extends + AbstractIntersectionExample { + + public AbstractEllipseIntersectionExample(String title) { + super(title); + } + + protected AbstractControllableShape createControllableShape1(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + // the ellipse does not have any control points + } + + @Override + public Ellipse createGeometry() { + double w5 = getCanvas().getClientArea().width / 5; + double h5 = getCanvas().getClientArea().height / 5; + return new Ellipse(w5, h5, 3 * w5, 3 * h5); + } + + @Override + public void drawShape(GC gc) { + Ellipse ellipse = (Ellipse) createGeometry(); + gc.drawOval((int) ellipse.getX(), (int) ellipse.getY(), + (int) ellipse.getWidth(), (int) ellipse.getHeight()); + } + }; + } + +} \ No newline at end of file diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/AbstractIntersectionExample.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/AbstractIntersectionExample.java new file mode 100644 index 0000000..5137201 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/AbstractIntersectionExample.java @@ -0,0 +1,378 @@ +/******************************************************************************* + * 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.examples.intersection; + +import java.util.ArrayList; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.Ellipse; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Rectangle; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.layout.FormAttachment; +import org.eclipse.swt.layout.FormData; +import org.eclipse.swt.layout.FormLayout; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; + +public abstract class AbstractIntersectionExample implements PaintListener { + + abstract public class AbstractControllableShape { + + private static final int CONTROL_POINT_COLOR = SWT.COLOR_BLUE; + private static final int CONTROL_POINT_RADIUS = 5; + + private Canvas canvas; + private ArrayList points; + + public AbstractControllableShape(Canvas canvas) { + this.canvas = canvas; + points = new ArrayList(); + createControlPoints(); + } + + public ControlPoint addControlPoint(Point p) { + return addControlPoint(p, CONTROL_POINT_COLOR); + } + + public ControlPoint addControlPoint(Point p, int color) { + return addControlPoint(p, color, CONTROL_POINT_RADIUS); + } + + public ControlPoint addControlPoint(Point p, int color, double radius) { + ControlPoint cp = new ControlPoint(canvas, p, radius, color); + for (ControlPoint ocp : points) { + ocp.addForbiddenArea(cp); + cp.addForbiddenArea(ocp); + } + points.add(cp); + return cp; + } + + abstract public void createControlPoints(); + + abstract public IGeometry createGeometry(); + + public void drawControlPoints(GC gc) { + for (ControlPoint cp : points) { + cp.draw(gc); + } + } + + abstract public void drawShape(GC gc); + + public Canvas getCanvas() { + return canvas; + } + + public Point[] getControlPoints() { + Point[] points = new Point[this.points.size()]; + + int i = 0; + for (ControlPoint cp : this.points) { + points[i++] = cp.getPoint(); + } + + return points; + } + } + + /** + * A draggable point. On the screen it is represented as an ellipse. + * + * @author wienand + * + */ + class ControlPoint implements MouseListener, MouseMoveListener, Listener { + private Canvas canvas; + private int color = SWT.COLOR_BLUE; + + private Ellipse ellipse; + private boolean isDragged = false; + + // to rescale points on resize + private double oldShellHeight; + private double oldShellWidth; + + private ArrayList forbidden; + private ArrayList updateLinks; + private ControlPoint xLink, yLink; + private double relX, relY; + + private Point p; + private double radius = 5; + + /** + * Creates a new ControlPoint object. Adds event listeners to the given + * Canvas object, so that the user can drag the control point with the + * mouse. + * + * @param canvas + * Drawing area + */ + public ControlPoint(Canvas canvas) { + this.canvas = canvas; + canvas.addMouseListener(this); + canvas.addMouseMoveListener(this); + canvas.addListener(SWT.Resize, this); + oldShellWidth = canvas.getClientArea().width; + oldShellHeight = canvas.getClientArea().height; + p = new Point(0, 0); + updateLinks = new ArrayList(); + forbidden = new ArrayList(); + update(); + } + + /** + * Creates a new ControlPoint object. Adds event listeners to the given + * Canvas object, so that the user can drag the control point with the + * mouse. + * + * @param canvas + * Drawing area + * @param p + * Exact point + * @param radius + * Of the ellipse that represents the point + * @param color + * Of the ellipse that represents the point + */ + public ControlPoint(Canvas canvas, Point p, double radius, int color) { + this(canvas); + this.p = p; + this.radius = radius; + this.color = color; + update(); + } + + public void addForbiddenArea(ControlPoint cp) { + forbidden.add(cp); + } + + /** + * Draws an ellipse with the given GC at the control points location. + * + * @param gc + */ + public void draw(GC gc) { + // System.out.println(ellipse.toString()); + gc.setBackground(Display.getCurrent().getSystemColor(color)); + gc.fillOval((int) ellipse.getX(), (int) ellipse.getY(), + (int) ellipse.getWidth(), (int) ellipse.getHeight()); + } + + /** + * Returns the exact Point of this ControlPoint object. + * + * @return The exact Point of this ControlPoint object. + */ + public Point getPoint() { + return p; + } + + @Override + public void handleEvent(Event e) { + switch (e.type) { + case SWT.Resize: + Rectangle bounds = new Rectangle(canvas.getBounds()); + p.scale(bounds.getWidth() / oldShellWidth, bounds.getHeight() + / oldShellHeight); + oldShellWidth = bounds.getWidth(); + oldShellHeight = bounds.getHeight(); + update(); + break; + } + } + + private double inRange(double low, double value, double high) { + if (value < low) { + return low; + } else if (value > high) { + return high; + } + return value; + } + + @Override + public void mouseDoubleClick(MouseEvent e) { + } + + @Override + public void mouseDown(MouseEvent e) { + if (ellipse.contains(new Point(e.x, e.y))) { + isDragged = true; + } + } + + @Override + public void mouseMove(MouseEvent e) { + if (isDragged) { + relX = e.x - p.x; + relY = e.y - p.y; + p.x = e.x; + p.y = e.y; + update(); + canvas.redraw(); + } + } + + @Override + public void mouseUp(MouseEvent e) { + isDragged = false; + } + + public void setXLink(ControlPoint cp) { + xLink = cp; + cp.updateLinks.add(this); + } + + public void setYLink(ControlPoint cp) { + yLink = cp; + cp.updateLinks.add(this); + } + + private void update() { + double oldX = p.x, oldY = p.y; + + // check canvas pane: + p.x = inRange(canvas.getClientArea().x + radius, p.x, + canvas.getClientArea().x + canvas.getClientArea().width + - radius); + p.y = inRange(canvas.getClientArea().y + radius, p.y, + canvas.getClientArea().y + canvas.getClientArea().height + - radius); + + // check links: + if (xLink != null) { + p.x = xLink.p.x; + p.y += xLink.relY; + } else if (yLink != null) { // no need to link both x and y + p.x += yLink.relX; + p.y = yLink.p.y; + } + + // check forbidden areas: + for (ControlPoint cp : forbidden) { + double minDistance = radius + cp.radius; + if (p.getDistance(cp.p) < minDistance) { + if (relX > 0) { + p.x = cp.p.x - minDistance; + } else if (relX < 0) { + p.x = cp.p.x + minDistance; + } else if (relY > 0) { + p.y = cp.p.y - minDistance; + } else { + p.y = cp.p.y + minDistance; + } + } + } + + relX += p.x - oldX; + relY += p.y - oldY; + + for (ControlPoint cp : updateLinks) { + cp.update(); + } + + ellipse = new Ellipse(p.x - radius, p.y - radius, radius * 2, + radius * 2); + } + } + + private static final int INTERSECTION_POINT_COLOR = SWT.COLOR_RED; + + private static final int INTERSECTION_POINT_RADIUS = 5; + + protected AbstractControllableShape controllableShape1, controllableShape2; + + private Shell shell; + + public AbstractIntersectionExample(String title) { + this(title, "drag the blue control points", "resize the window"); + } + + /** + * + */ + public AbstractIntersectionExample(String title, String... infos) { + Display display = new Display(); + + shell = new Shell(display, SWT.SHELL_TRIM | SWT.DOUBLE_BUFFERED); + shell.setText(title); + shell.setBounds(0, 0, 640, 480); + shell.setLayout(new FormLayout()); + + Label infoLabel = new Label(shell, SWT.NONE); + FormData infoLabelFormData = new FormData(); + infoLabelFormData.right = new FormAttachment(100, -10); + infoLabelFormData.bottom = new FormAttachment(100, -10); + infoLabel.setLayoutData(infoLabelFormData); + + String infoText = "You can..."; + for (int i = 0; i < infos.length; i++) { + infoText += "\n..." + infos[i]; + } + infoLabel.setText(infoText); + + controllableShape1 = createControllableShape1(shell); + controllableShape2 = createControllableShape2(shell); + + shell.addPaintListener(this); + shell.open(); + + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) { + display.sleep(); + } + } + } + + protected abstract Point[] computeIntersections(IGeometry g1, IGeometry g2); + + protected abstract AbstractControllableShape createControllableShape1( + Canvas canvas); + + protected abstract AbstractControllableShape createControllableShape2( + Canvas canvas); + + @Override + public void paintControl(PaintEvent e) { + e.gc.setAntialias(SWT.ON); + + controllableShape1.drawShape(e.gc); + controllableShape2.drawShape(e.gc); + + controllableShape1.drawControlPoints(e.gc); + controllableShape2.drawControlPoints(e.gc); + + e.gc.setBackground(Display.getCurrent().getSystemColor( + INTERSECTION_POINT_COLOR)); + + for (Point p : computeIntersections( + controllableShape1.createGeometry(), + controllableShape2.createGeometry())) { + e.gc.fillOval((int) p.x - INTERSECTION_POINT_RADIUS, (int) p.y + - INTERSECTION_POINT_RADIUS, INTERSECTION_POINT_RADIUS * 2, + INTERSECTION_POINT_RADIUS * 2); + } + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/AbstractPolygonIntersectionExample.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/AbstractPolygonIntersectionExample.java new file mode 100644 index 0000000..4467822 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/AbstractPolygonIntersectionExample.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * 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.examples.intersection; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.Line; +import org.eclipse.gef4.geometry.planar.Polygon; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Canvas; + +public abstract class AbstractPolygonIntersectionExample extends + AbstractIntersectionExample { + + public AbstractPolygonIntersectionExample(String title) { + super(title); + } + + protected AbstractControllableShape createControllableShape1(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + // no control points => user cannot change it + } + + @Override + public Polygon createGeometry() { + Rectangle ca = getCanvas().getClientArea(); + double w = ca.width; + double wg = w / 6; + double h = ca.height; + double hg = h / 6; + + return new Polygon(new Point[] { new Point(wg, hg), + new Point(w - wg, h - hg), new Point(wg, h - hg), + new Point(w - wg, hg) }); + } + + @Override + public void drawShape(GC gc) { + Polygon polygon = createGeometry(); + for (Line segment : polygon.getOutlineSegments()) { + gc.drawLine((int) segment.getX1(), (int) segment.getY1(), + (int) segment.getX2(), (int) segment.getY2()); + } + } + }; + } +} \ No newline at end of file diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/BezierApproximationExample.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/BezierApproximationExample.java new file mode 100644 index 0000000..2bf2bb9 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/BezierApproximationExample.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * 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.examples.intersection; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.BezierCurve; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Line; +import org.eclipse.gef4.geometry.planar.Path; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Display; + +public class BezierApproximationExample extends AbstractIntersectionExample { + public static void main(String[] args) { + new BezierApproximationExample("Bezier Approximation Example"); + } + + public BezierApproximationExample(String title) { + super(title); + } + + protected AbstractControllableShape createControllableShape1(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + addControlPoint(new Point(100, 100)); + addControlPoint(new Point(150, 400)); + addControlPoint(new Point(200, 300)); + addControlPoint(new Point(250, 150)); + addControlPoint(new Point(300, 250)); + addControlPoint(new Point(350, 200)); + addControlPoint(new Point(400, 350)); + } + + @Override + public IGeometry createGeometry() { + return new BezierCurve(getControlPoints()).toPath(); + } + + @Override + public void drawShape(GC gc) { + Path curve = (Path) createGeometry(); + gc.drawPath(new org.eclipse.swt.graphics.Path(Display + .getCurrent(), curve.toSWTPathData())); + } + }; + } + + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + } + + @Override + public IGeometry createGeometry() { + return new Line(new Point(), new Point(1, 1)); + } + + @Override + public void drawShape(GC gc) { + } + }; + } + + @Override + protected Point[] computeIntersections(IGeometry g1, IGeometry g2) { + return new Point[] {}; + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/ConvexHullExample.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/ConvexHullExample.java new file mode 100644 index 0000000..4e317f6 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/ConvexHullExample.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * 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.examples.intersection; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Line; +import org.eclipse.gef4.geometry.planar.Polygon; +import org.eclipse.gef4.geometry.utils.PointListUtils; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Display; + +public class ConvexHullExample extends AbstractIntersectionExample { + public static void main(String[] args) { + new ConvexHullExample("Convex Hull Example"); + } + + public ConvexHullExample(String title) { + super(title); + } + + protected AbstractControllableShape createControllableShape1(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + addControlPoint(new Point(100, 100)); + addControlPoint(new Point(150, 400)); + addControlPoint(new Point(200, 300)); + addControlPoint(new Point(250, 150)); + addControlPoint(new Point(300, 250)); + addControlPoint(new Point(350, 200)); + addControlPoint(new Point(400, 350)); + } + + @Override + public IGeometry createGeometry() { + Polygon convexHull = new Polygon( + PointListUtils.getConvexHull(getControlPoints())); + return convexHull; + } + + @Override + public void drawShape(GC gc) { + Polygon convexHull = (Polygon) createGeometry(); + gc.drawPath(new org.eclipse.swt.graphics.Path(Display + .getCurrent(), convexHull.toPath().toSWTPathData())); + } + }; + } + + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + } + + @Override + public IGeometry createGeometry() { + return new Line(-10, -10, -10, -10); + } + + @Override + public void drawShape(GC gc) { + } + }; + } + + @Override + protected Point[] computeIntersections(IGeometry g1, IGeometry g2) { + return new Point[] {}; + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/CubicCurveDeCasteljauExample.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/CubicCurveDeCasteljauExample.java new file mode 100644 index 0000000..380f1b2 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/CubicCurveDeCasteljauExample.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * 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.examples.intersection; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.CubicCurve; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Line; +import org.eclipse.gef4.geometry.planar.Rectangle; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Display; + +public class CubicCurveDeCasteljauExample extends AbstractIntersectionExample { + public static void main(String[] args) { + new CubicCurveDeCasteljauExample("Cubic Bezier Curve Example"); + } + + public CubicCurveDeCasteljauExample(String title) { + super(title); + } + + protected AbstractControllableShape createControllableShape1(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + addControlPoint(new Point(100, 200)); + addControlPoint(new Point(200, 100)); + addControlPoint(new Point(300, 300)); + addControlPoint(new Point(400, 200)); + } + + @Override + public IGeometry createGeometry() { + Point[] points = getControlPoints(); + + CubicCurve curve = new CubicCurve(points[0], points[1], + points[2], points[3]); + + return curve; + } + + @Override + public void drawShape(GC gc) { + CubicCurve curve = (CubicCurve) createGeometry(); + + // draw curve + gc.drawPath(new org.eclipse.swt.graphics.Path(Display + .getCurrent(), curve.toPath().toSWTPathData())); + + // draw bounds + Rectangle bounds = curve.getBounds(); + + gc.setForeground(Display.getCurrent().getSystemColor( + SWT.COLOR_DARK_GRAY)); + gc.drawRectangle(bounds.toSWTRectangle()); + + // draw lerps + Point[] points = getControlPoints(); + for (int i = 0; i < 3; i++) { + gc.setForeground(Display.getCurrent().getSystemColor( + SWT.COLOR_DARK_GREEN)); + gc.drawLine((int) points[i].x, (int) points[i].y, + (int) points[i + 1].x, (int) points[i + 1].y); + gc.setForeground(Display.getCurrent().getSystemColor( + SWT.COLOR_BLACK)); + points[i] = points[i].getTranslated(points[i + 1] + .getTranslated(points[i].getScaled(-1)).getScaled( + 0.25)); + gc.drawOval((int) (points[i].x - 2), + (int) (points[i].y - 2), 4, 4); + } + for (int i = 0; i < 2; i++) { + gc.setForeground(Display.getCurrent().getSystemColor( + SWT.COLOR_BLUE)); + gc.drawLine((int) points[i].x, (int) points[i].y, + (int) points[i + 1].x, (int) points[i + 1].y); + gc.setForeground(Display.getCurrent().getSystemColor( + SWT.COLOR_BLACK)); + points[i] = points[i].getTranslated(points[i + 1] + .getTranslated(points[i].getScaled(-1)).getScaled( + 0.25)); + gc.drawOval((int) (points[i].x - 2), + (int) (points[i].y - 2), 4, 4); + } + for (int i = 0; i < 1; i++) { + gc.setForeground(Display.getCurrent().getSystemColor( + SWT.COLOR_DARK_RED)); + gc.drawLine((int) points[i].x, (int) points[i].y, + (int) points[i + 1].x, (int) points[i + 1].y); + gc.setForeground(Display.getCurrent().getSystemColor( + SWT.COLOR_BLACK)); + points[i] = points[i].getTranslated(points[i + 1] + .getTranslated(points[i].getScaled(-1)).getScaled( + 0.25)); + gc.drawOval((int) (points[i].x - 2), + (int) (points[i].y - 2), 4, 4); + } + } + }; + } + + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + } + + @Override + public IGeometry createGeometry() { + return new Line(new Point(), new Point()); + } + + @Override + public void drawShape(GC gc) { + } + }; + } + + @Override + protected Point[] computeIntersections(IGeometry g1, IGeometry g2) { + return new Point[] {}; + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/CubicCurvesIntersection.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/CubicCurvesIntersection.java new file mode 100644 index 0000000..19342cb --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/CubicCurvesIntersection.java @@ -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.examples.intersection; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.CubicCurve; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Display; + +public class CubicCurvesIntersection extends AbstractIntersectionExample { + public static void main(String[] args) { + new CubicCurvesIntersection("Cubic Bezier Curve/Curve Intersection"); + } + + public CubicCurvesIntersection(String title) { + super(title); + } + + private AbstractControllableShape createControllableCubicBezierCurveShape( + Canvas canvas, Point... points) { + final Point start = points[0]; + final Point ctrl1 = points[1]; + final Point ctrl2 = points[2]; + final Point end = points[3]; + + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + addControlPoint(start); + addControlPoint(ctrl1); + addControlPoint(ctrl2); + addControlPoint(end); + } + + @Override + public IGeometry createGeometry() { + return new CubicCurve(getControlPoints()); + } + + @Override + public void drawShape(GC gc) { + CubicCurve curve = (CubicCurve) createGeometry(); + + gc.drawPath(new org.eclipse.swt.graphics.Path(Display + .getCurrent(), curve.toPath().toSWTPathData())); + } + }; + } + + protected AbstractControllableShape createControllableShape1(Canvas canvas) { + return createControllableCubicBezierCurveShape(canvas, new Point(100, + 100), new Point(150, 50), new Point(310, 300), new Point(400, + 200)); + } + + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return createControllableCubicBezierCurveShape(canvas, new Point(400, + 100), new Point(310, 110), new Point(210, 210), new Point(100, + 200)); + } + + @Override + protected Point[] computeIntersections(IGeometry g1, IGeometry g2) { + return ((CubicCurve) g1).getIntersections((CubicCurve) g2); + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipseCubicCurveIntersection.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipseCubicCurveIntersection.java new file mode 100644 index 0000000..b58fd7c --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipseCubicCurveIntersection.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * 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.examples.intersection; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.CubicCurve; +import org.eclipse.gef4.geometry.planar.Ellipse; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Display; + +public class EllipseCubicCurveIntersection extends + AbstractEllipseIntersectionExample { + + /** + * @param args + */ + public static void main(String[] args) { + new EllipseCubicCurveIntersection(); + } + + public EllipseCubicCurveIntersection() { + super("Ellipse/CubicCurve Intersection"); + } + + protected Point[] computeIntersections(IGeometry g1, IGeometry g2) { + return ((Ellipse) g1).getIntersections((CubicCurve) g2); + } + + @Override + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + addControlPoint(new Point(100, 150)); + addControlPoint(new Point(400, 200)); + addControlPoint(new Point(300, 400)); + addControlPoint(new Point(550, 300)); + } + + @Override + public CubicCurve createGeometry() { + return new CubicCurve(getControlPoints()); + } + + @Override + public void drawShape(GC gc) { + CubicCurve c = createGeometry(); + gc.drawPath(new org.eclipse.swt.graphics.Path(Display + .getCurrent(), c.toPath().toSWTPathData())); + } + }; + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipseEllipseIntersection.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipseEllipseIntersection.java new file mode 100644 index 0000000..c1dd322 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipseEllipseIntersection.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * 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.examples.intersection; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.Ellipse; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Display; + +public class EllipseEllipseIntersection extends + AbstractEllipseIntersectionExample { + + /** + * @param args + */ + public static void main(String[] args) { + new EllipseEllipseIntersection(); + } + + public EllipseEllipseIntersection() { + super("Ellipse/Ellipse Intersection"); + } + + @Override + protected Point[] computeIntersections(IGeometry g1, IGeometry g2) { + return ((Ellipse) g1).getIntersections((Ellipse) g2); + } + + @Override + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + ControlPoint center = addControlPoint(new Point(300, 300)); + ControlPoint a = addControlPoint(new Point(400, 300)); + ControlPoint b = addControlPoint(new Point(300, 200)); + a.setYLink(center); + b.setXLink(center); + } + + @Override + public Ellipse createGeometry() { + Point[] points = getControlPoints(); + double a = Math.abs(points[0].x - points[1].x); + double b = Math.abs(points[0].y - points[2].y); + return new Ellipse(points[0].x - a, points[0].y - b, 2 * a, + 2 * b); + } + + @Override + public void drawShape(GC gc) { + Ellipse ellipse = createGeometry(); + gc.drawPath(new org.eclipse.swt.graphics.Path(Display + .getCurrent(), ellipse.toPath().toSWTPathData())); + } + }; + } + +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipseLineIntersection.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipseLineIntersection.java new file mode 100644 index 0000000..1f23103 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipseLineIntersection.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * 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.examples.intersection; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.Ellipse; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Line; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; + +/** + * Simple example demonstrating the intersection of an {@link Ellipse} and a + * {@link Line}. + * + * @author Matthias Wienand (matthias.wienand@itemis.de) + * + */ +public class EllipseLineIntersection extends AbstractEllipseIntersectionExample { + + public static void main(String[] args) { + new EllipseLineIntersection(); + } + + public EllipseLineIntersection() { + super("Ellipse/Line Intersection"); + } + + protected Point[] computeIntersections(IGeometry g1, IGeometry g2) { + return ((Ellipse) g1).getIntersections((Line) g2); + } + + @Override + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + addControlPoint(new Point(100, 100)); + addControlPoint(new Point(300, 300)); + } + + @Override + public Line createGeometry() { + Point[] points = getControlPoints(); + return new Line(points[0], points[1]); + } + + @Override + public void drawShape(GC gc) { + Line line = createGeometry(); + gc.drawPolyline(line.toSWTPointArray()); + } + }; + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipsePolygonIntersection.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipsePolygonIntersection.java new file mode 100644 index 0000000..770c0b7 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipsePolygonIntersection.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * 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.examples.intersection; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.Ellipse; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Polygon; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; + +public class EllipsePolygonIntersection extends + AbstractEllipseIntersectionExample { + + public static void main(String[] args) { + new EllipsePolygonIntersection(); + } + + public EllipsePolygonIntersection() { + super("Ellipse/Polygon Intersection"); + } + + @Override + protected Point[] computeIntersections(IGeometry g1, IGeometry g2) { + return ((Ellipse) g1).getOutline().getIntersections( + ((Polygon) g2).getOutline()); + } + + @Override + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + addControlPoint(new Point(100, 100)); + addControlPoint(new Point(600, 200)); + addControlPoint(new Point(100, 300)); + } + + @Override + public Polygon createGeometry() { + Point[] points = getControlPoints(); + Polygon polygon = new Polygon(points); + return polygon; + } + + @Override + public void drawShape(GC gc) { + Polygon polygon = createGeometry(); + gc.drawPolygon(polygon.toSWTPointArray()); + } + }; + } + +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipsePolylineIntersection.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipsePolylineIntersection.java new file mode 100644 index 0000000..9cc3c70 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipsePolylineIntersection.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * 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.examples.intersection; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.Ellipse; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Polyline; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; + +public class EllipsePolylineIntersection extends + AbstractEllipseIntersectionExample { + + /** + * @param args + */ + public static void main(String[] args) { + new EllipsePolylineIntersection(); + } + + public EllipsePolylineIntersection() { + super("Ellipse/Polyline Intersection"); + } + + @Override + protected Point[] computeIntersections(IGeometry g1, IGeometry g2) { + return ((Ellipse) g1).getIntersections((Polyline) g2); + } + + @Override + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + addControlPoint(new Point(100, 100)); + addControlPoint(new Point(500, 400)); + addControlPoint(new Point(100, 400)); + addControlPoint(new Point(500, 100)); + } + + @Override + public Polyline createGeometry() { + Point[] points = getControlPoints(); + Polyline polyline = new Polyline(points); + return polyline; + } + + @Override + public void drawShape(GC gc) { + Polyline polyline = createGeometry(); + gc.drawPolyline(polyline.toSWTPointArray()); + } + }; + } + +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipseQuadraticCurveIntersection.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipseQuadraticCurveIntersection.java new file mode 100644 index 0000000..5d5a25a --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipseQuadraticCurveIntersection.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * 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.examples.intersection; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.Ellipse; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.QuadraticCurve; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Display; + +public class EllipseQuadraticCurveIntersection extends + AbstractEllipseIntersectionExample { + + /** + * @param args + */ + public static void main(String[] args) { + new EllipseQuadraticCurveIntersection(); + } + + public EllipseQuadraticCurveIntersection() { + super("Ellipse/QuadraticCurve Intersection"); + } + + protected Point[] computeIntersections(IGeometry g1, IGeometry g2) { + return ((Ellipse) g1).getIntersections((QuadraticCurve) g2); + } + + @Override + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + addControlPoint(new Point(100, 150)); + addControlPoint(new Point(400, 200)); + addControlPoint(new Point(550, 300)); + } + + @Override + public QuadraticCurve createGeometry() { + return new QuadraticCurve(getControlPoints()); + } + + @Override + public void drawShape(GC gc) { + QuadraticCurve c = createGeometry(); + gc.drawPath(new org.eclipse.swt.graphics.Path(Display + .getCurrent(), c.toPath().toSWTPathData())); + } + }; + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipseRectangleIntersection.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipseRectangleIntersection.java new file mode 100644 index 0000000..cbed9a3 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/EllipseRectangleIntersection.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * 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.examples.intersection; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.Ellipse; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Rectangle; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; + +public class EllipseRectangleIntersection extends + AbstractEllipseIntersectionExample { + + /** + * @param args + */ + public static void main(String[] args) { + new EllipseRectangleIntersection(); + } + + public EllipseRectangleIntersection() { + super("Ellipse/Rectangle Intersection"); + } + + protected Point[] computeIntersections(IGeometry g1, IGeometry g2) { + return ((Ellipse) g1).getOutline().getIntersections( + ((Rectangle) g2).getOutline()); + } + + @Override + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + addControlPoint(new Point(100, 150)); + addControlPoint(new Point(550, 300)); + } + + @Override + public Rectangle createGeometry() { + Point[] points = getControlPoints(); + return new Rectangle(points[0], points[1]); + } + + @Override + public void drawShape(GC gc) { + Rectangle rect = createGeometry(); + gc.drawRectangle(rect.toSWTRectangle()); + } + }; + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonCubicCurveIntersection.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonCubicCurveIntersection.java new file mode 100644 index 0000000..68e3e64 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonCubicCurveIntersection.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * 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.examples.intersection; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.CubicCurve; +import org.eclipse.gef4.geometry.planar.Ellipse; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Line; +import org.eclipse.gef4.geometry.planar.Polygon; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Display; + +/** + * Simple example demonstrating the intersection of an {@link Ellipse} and a + * {@link Line}. + * + * @author Matthias Wienand (matthias.wienand@itemis.de) + * + */ +public class PolygonCubicCurveIntersection extends + AbstractPolygonIntersectionExample { + + public static void main(String[] args) { + new PolygonCubicCurveIntersection(); + } + + public PolygonCubicCurveIntersection() { + super("Polygon/CubicCurve Intersection"); + } + + protected Point[] computeIntersections(IGeometry g1, IGeometry g2) { + return ((Polygon) g1).getOutline().getIntersections((CubicCurve) g2); + } + + @Override + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + addControlPoint(new Point(200, 100)); + addControlPoint(new Point(190, 310)); + addControlPoint(new Point(410, 90)); + addControlPoint(new Point(400, 300)); + } + + @Override + public CubicCurve createGeometry() { + Point[] controlPoints = getControlPoints(); + System.out.println("new CubicCurve(" + controlPoints[0] + ", " + + controlPoints[1] + ", " + controlPoints[2] + ", " + + controlPoints[3] + ")"); + return new CubicCurve(controlPoints); + } + + @Override + public void drawShape(GC gc) { + CubicCurve c = createGeometry(); + gc.drawPath(new org.eclipse.swt.graphics.Path(Display + .getCurrent(), c.toPath().toSWTPathData())); + } + }; + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonEllipseIntersection.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonEllipseIntersection.java new file mode 100644 index 0000000..8e51a95 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonEllipseIntersection.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * 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.examples.intersection; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.Ellipse; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Polygon; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; + +public class PolygonEllipseIntersection extends + AbstractPolygonIntersectionExample { + + /** + * @param args + */ + public static void main(String[] args) { + new PolygonEllipseIntersection(); + } + + public PolygonEllipseIntersection() { + super("Polygon/Ellipse Intersection"); + } + + @Override + protected Point[] computeIntersections(IGeometry g1, IGeometry g2) { + return ((Polygon) g1).getOutline().getIntersections( + ((Ellipse) g2).getOutline()); + } + + @Override + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + ControlPoint center = addControlPoint(new Point(300, 300)); + ControlPoint a = addControlPoint(new Point(400, 300)); + ControlPoint b = addControlPoint(new Point(300, 200)); + a.setYLink(center); + b.setXLink(center); + } + + @Override + public Ellipse createGeometry() { + Point[] points = getControlPoints(); + double a = Math.abs(points[0].x - points[1].x); + double b = Math.abs(points[0].y - points[2].y); + return new Ellipse(points[0].x - a, points[0].y - b, 2 * a, + 2 * b); + } + + @Override + public void drawShape(GC gc) { + Ellipse ellipse = createGeometry(); + gc.drawOval((int) ellipse.getX(), (int) ellipse.getY(), + (int) ellipse.getWidth(), (int) ellipse.getHeight()); + } + }; + } + +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonLineIntersection.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonLineIntersection.java new file mode 100644 index 0000000..eef9c30 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonLineIntersection.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * 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.examples.intersection; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.Ellipse; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Line; +import org.eclipse.gef4.geometry.planar.Polygon; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; + +/** + * Simple example demonstrating the intersection of an {@link Ellipse} and a + * {@link Line}. + * + * @author Matthias Wienand (matthias.wienand@itemis.de) + * + */ +public class PolygonLineIntersection extends AbstractPolygonIntersectionExample { + + public static void main(String[] args) { + new PolygonLineIntersection(); + } + + public PolygonLineIntersection() { + super("Polygon/Line Intersection"); + } + + protected Point[] computeIntersections(IGeometry g1, IGeometry g2) { + return ((Polygon) g1).getOutline().getIntersections((Line) g2); + } + + @Override + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + addControlPoint(new Point(100, 100)); + addControlPoint(new Point(300, 300)); + } + + @Override + public Line createGeometry() { + Point[] points = getControlPoints(); + return new Line(points[0], points[1]); + } + + @Override + public void drawShape(GC gc) { + Line line = createGeometry(); + gc.drawPolyline(line.toSWTPointArray()); + } + }; + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonPolygonIntersection.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonPolygonIntersection.java new file mode 100644 index 0000000..69e57e9 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonPolygonIntersection.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * 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.examples.intersection; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Polygon; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; + +public class PolygonPolygonIntersection extends + AbstractPolygonIntersectionExample { + + public static void main(String[] args) { + new PolygonPolygonIntersection(); + } + + public PolygonPolygonIntersection() { + super("Polygon/Polygon Intersection"); + } + + @Override + protected Point[] computeIntersections(IGeometry g1, IGeometry g2) { + return ((Polygon) g1).getOutline().getIntersections( + ((Polygon) g2).getOutline()); + } + + @Override + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + addControlPoint(new Point(100, 100)); + addControlPoint(new Point(600, 200)); + addControlPoint(new Point(100, 300)); + } + + @Override + public Polygon createGeometry() { + Point[] points = getControlPoints(); + Polygon polygon = new Polygon(points); + return polygon; + } + + @Override + public void drawShape(GC gc) { + Polygon polygon = createGeometry(); + gc.drawPolygon(polygon.toSWTPointArray()); + } + }; + } + +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonPolylineIntersection.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonPolylineIntersection.java new file mode 100644 index 0000000..46d35e7 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonPolylineIntersection.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * 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.examples.intersection; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Polygon; +import org.eclipse.gef4.geometry.planar.Polyline; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; + +public class PolygonPolylineIntersection extends + AbstractPolygonIntersectionExample { + + public static void main(String[] args) { + new PolygonPolylineIntersection(); + } + + public PolygonPolylineIntersection() { + super("Polygon/Polyline Intersection"); + } + + @Override + protected Point[] computeIntersections(IGeometry g1, IGeometry g2) { + return ((Polygon) g1).getOutline().getIntersections((Polyline) g2); + } + + @Override + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + addControlPoint(new Point(100, 100)); + addControlPoint(new Point(100, 300)); + addControlPoint(new Point(300, 300)); + addControlPoint(new Point(300, 100)); + } + + @Override + public Polyline createGeometry() { + Point[] points = getControlPoints(); + Polyline polyline = new Polyline(points); + return polyline; + } + + @Override + public void drawShape(GC gc) { + Polyline polyline = createGeometry(); + gc.drawPolyline(polyline.toSWTPointArray()); + } + }; + } + +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonQuadraticCurveIntersection.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonQuadraticCurveIntersection.java new file mode 100644 index 0000000..13e1c53 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonQuadraticCurveIntersection.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * 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.examples.intersection; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.Ellipse; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Line; +import org.eclipse.gef4.geometry.planar.Polygon; +import org.eclipse.gef4.geometry.planar.QuadraticCurve; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Display; + +/** + * Simple example demonstrating the intersection of an {@link Ellipse} and a + * {@link Line}. + * + * @author Matthias Wienand (matthias.wienand@itemis.de) + * + */ +public class PolygonQuadraticCurveIntersection extends + AbstractPolygonIntersectionExample { + + public static void main(String[] args) { + new PolygonQuadraticCurveIntersection(); + } + + public PolygonQuadraticCurveIntersection() { + super("Polygon/QuadraticCurve Intersection"); + } + + protected Point[] computeIntersections(IGeometry g1, IGeometry g2) { + return ((Polygon) g1).getOutline() + .getIntersections((QuadraticCurve) g2); + } + + @Override + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + addControlPoint(new Point(100, 100)); + addControlPoint(new Point(300, 100)); + addControlPoint(new Point(300, 300)); + } + + @Override + public QuadraticCurve createGeometry() { + return new QuadraticCurve(getControlPoints()); + } + + @Override + public void drawShape(GC gc) { + QuadraticCurve c = createGeometry(); + gc.drawPath(new org.eclipse.swt.graphics.Path(Display + .getCurrent(), c.toPath().toSWTPathData())); + } + }; + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonRectangleIntersection.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonRectangleIntersection.java new file mode 100644 index 0000000..6f1eaea --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/PolygonRectangleIntersection.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * 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.examples.intersection; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.Polygon; +import org.eclipse.gef4.geometry.planar.Rectangle; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; + +public class PolygonRectangleIntersection extends + AbstractPolygonIntersectionExample { + + public static void main(String[] args) { + new PolygonRectangleIntersection(); + } + + public PolygonRectangleIntersection() { + super("Polygon/Rectangle Intersection"); + } + + protected Point[] computeIntersections(IGeometry g1, IGeometry g2) { + return ((Polygon) g1).getOutline().getIntersections( + ((Rectangle) g2).getOutline()); + } + + @Override + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + addControlPoint(new Point(100, 150)); + addControlPoint(new Point(550, 300)); + } + + @Override + public Rectangle createGeometry() { + Point[] points = getControlPoints(); + return new Rectangle(points[0], points[1]); + } + + @Override + public void drawShape(GC gc) { + Rectangle rect = createGeometry(); + gc.drawRectangle(rect.toSWTRectangle()); + } + }; + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/QuadraticCurvesIntersection.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/QuadraticCurvesIntersection.java new file mode 100644 index 0000000..04f265f --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/QuadraticCurvesIntersection.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * 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.examples.intersection; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.QuadraticCurve; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Display; + +public class QuadraticCurvesIntersection extends AbstractIntersectionExample { + public static void main(String[] args) { + new QuadraticCurvesIntersection( + "Quadratic Bezier Curve/Curve Intersection"); + } + + public QuadraticCurvesIntersection(String title) { + super(title); + } + + private AbstractControllableShape createControllableQuadraticBezierCurveShape( + Canvas canvas, Point... points) { + final Point start = points[0]; + final Point ctrl = points[1]; + final Point end = points[2]; + + return new AbstractControllableShape(canvas) { + @Override + public void createControlPoints() { + addControlPoint(start); + addControlPoint(ctrl); + addControlPoint(end); + } + + @Override + public IGeometry createGeometry() { + return new QuadraticCurve(getControlPoints()); + } + + @Override + public void drawShape(GC gc) { + QuadraticCurve curve = (QuadraticCurve) createGeometry(); + + gc.drawPath(new org.eclipse.swt.graphics.Path(Display + .getCurrent(), curve.toPath().toSWTPathData())); + } + }; + } + + protected AbstractControllableShape createControllableShape1(Canvas canvas) { + return createControllableQuadraticBezierCurveShape(canvas, new Point( + 100, 100), new Point(300, 150), new Point(400, 400)); + } + + protected AbstractControllableShape createControllableShape2(Canvas canvas) { + return createControllableQuadraticBezierCurveShape(canvas, new Point( + 400, 100), new Point(310, 290), new Point(100, 400)); + } + + @Override + protected Point[] computeIntersections(IGeometry g1, IGeometry g2) { + return ((QuadraticCurve) g1).getIntersections((QuadraticCurve) g2); + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/scalerotate/AbstractScaleRotateExample.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/scalerotate/AbstractScaleRotateExample.java new file mode 100644 index 0000000..9412e52 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/scalerotate/AbstractScaleRotateExample.java @@ -0,0 +1,160 @@ +/******************************************************************************* + * 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.examples.scalerotate; + +import org.eclipse.gef4.geometry.Angle; +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.euclidean.Vector; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.events.MouseWheelListener; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; + +public abstract class AbstractScaleRotateExample implements PaintListener, + MouseWheelListener, MouseMoveListener, MouseListener, Listener { + + // TODO: The new angle interface is easier to use and should be used here! + + protected abstract class AbstractScaleRotateShape { + private Canvas canvas; + private Angle rotationAngle = Angle.fromDeg(0); + private double zoomFactor = 1; + + public AbstractScaleRotateShape(Canvas c) { + canvas = c; + } + + public Canvas getCanvas() { + return canvas; + } + + public Angle getRotationAngle() { + return rotationAngle; + } + + public double getZoomFactor() { + return zoomFactor; + } + + public Point getCenter() { + return new Point(canvas.getClientArea().width / 2, + canvas.getClientArea().height / 2); + } + + public abstract boolean contains(Point p); + + public abstract IGeometry createGeometry(); + + public abstract void draw(GC gc); + } + + private final int GEOMETRY_FILL_COLOR = SWT.COLOR_WHITE; + + private Shell shell; + private AbstractScaleRotateShape shape; + private Vector dragBegin; + private Angle dragBeginAngle = Angle.fromDeg(0); + + /** + * + */ + public AbstractScaleRotateExample(String title) { + Display display = new Display(); + shell = new Shell(display, SWT.SHELL_TRIM | SWT.DOUBLE_BUFFERED); + shell.setText(title); + + shell.setBounds(0, 0, 640, 480); + + shape = createShape(shell); + + shell.addPaintListener(this); + shell.addMouseListener(this); + shell.addMouseMoveListener(this); + shell.addMouseWheelListener(this); + shell.addListener(SWT.Resize, this); + shell.open(); + + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) { + display.sleep(); + } + } + } + + protected abstract AbstractScaleRotateShape createShape(Canvas canvas); + + @Override + public void paintControl(PaintEvent e) { + e.gc.setAntialias(SWT.ON); + e.gc.setBackground(Display.getCurrent().getSystemColor( + GEOMETRY_FILL_COLOR)); + shape.draw(e.gc); + } + + @Override + public void mouseScrolled(MouseEvent e) { + shape.zoomFactor += (double) e.count / 30; + shell.redraw(); + } + + @Override + public void mouseDown(MouseEvent e) { + if (shape.contains(new Point(e.x, e.y))) { + dragBegin = new Vector(shape.getCenter(), new Point(e.x, e.y)); + }// else { + // dragBeginAngle = shape.rotationAngle; + // dragBegin = null; + // } + } + + @Override + public void mouseUp(MouseEvent e) { + dragBeginAngle = shape.rotationAngle; + dragBegin = null; + } + + @Override + public void mouseMove(MouseEvent e) { + if (dragBegin != null) { + Point center = shape.getCenter(); + Vector toMouse = new Vector(center, new Point(e.x, e.y)); + shape.rotationAngle = dragBegin.getAngleCW(toMouse).getAdded( + dragBeginAngle); + shell.redraw(); + } + } + + @Override + public void mouseDoubleClick(MouseEvent e) { + shape.zoomFactor += 0.1; + shell.redraw(); + } + + @Override + public void handleEvent(Event e) { + switch (e.type) { + case SWT.Resize: + shell.redraw(); + break; + } + } +} diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/scalerotate/PolygonScaleRotate.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/scalerotate/PolygonScaleRotate.java new file mode 100644 index 0000000..3747c34 --- /dev/null +++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/scalerotate/PolygonScaleRotate.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * 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.examples.scalerotate; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.planar.Polygon; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; + +public class PolygonScaleRotate extends AbstractScaleRotateExample { + + public static void main(String[] args) { + new PolygonScaleRotate(); + } + + public PolygonScaleRotate() { + super("Scale/Rotate - Polygon"); + } + + @Override + protected AbstractScaleRotateShape createShape(Canvas canvas) { + return new AbstractScaleRotateShape(canvas) { + @Override + public boolean contains(Point p) { + return createGeometry().contains(p); + } + + @Override + public Polygon createGeometry() { + double w = getCanvas().getClientArea().width; + double h = getCanvas().getClientArea().height; + double w2 = w / 2; + double h2 = h / 2; + double w5 = w / 5; + double h5 = h / 5; + + Polygon me = new Polygon(new Point(w2 - w5, h2 - h5), + new Point(w2 + w5, h2 - h5), + new Point(w2 + w5, h2 + h5), + new Point(w2 - w5, h2 + h5)); + + me.rotateCW(getRotationAngle(), getCenter()); + me.scale(getZoomFactor(), getCenter()); + + return me; + } + + @Override + public void draw(GC gc) { + Polygon me = createGeometry(); + gc.drawPolygon(me.toSWTPointArray()); + gc.fillPolygon(me.toSWTPointArray()); + } + }; + } + +} diff --git a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/CubicCurveTests.java b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/CubicCurveTests.java index 9b1cd8b..ea9b200 100644 --- a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/CubicCurveTests.java +++ b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/CubicCurveTests.java @@ -190,4 +190,9 @@ public class CubicCurveTests { assertEquals(2, cc2.getIntersections(cc1).length); } + @Test + public void test_getIntersections_containment() { + // TODO + } + } diff --git a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/CurveUtilsTests.java b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/CurveUtilsTests.java index da356a0..cb0df0d 100644 --- a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/CurveUtilsTests.java +++ b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/CurveUtilsTests.java @@ -16,11 +16,14 @@ import static org.junit.Assert.assertTrue; import java.util.Random; +import org.eclipse.gef4.geometry.Angle; import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.euclidean.Straight; +import org.eclipse.gef4.geometry.planar.BezierCurve; import org.eclipse.gef4.geometry.planar.CubicCurve; +import org.eclipse.gef4.geometry.planar.Line; import org.eclipse.gef4.geometry.planar.QuadraticCurve; import org.eclipse.gef4.geometry.planar.Rectangle; -import org.eclipse.gef4.geometry.utils.CurveUtils; import org.eclipse.gef4.geometry.utils.PrecisionUtils; import org.junit.Test; @@ -32,35 +35,35 @@ public class CurveUtilsTests { public void test_getSignedDistance_direction() { // sign-checks: // first quadrant (y-axis is inverted) - assertTrue(CurveUtils.getSignedDistance(new Point(), + assertTrue(Straight.getSignedDistanceCCW(new Point(), new Point(10, -10), new Point(0, -10)) > 0); - assertTrue(CurveUtils.getSignedDistance(new Point(10, -10), + assertTrue(Straight.getSignedDistanceCCW(new Point(10, -10), new Point(), new Point(0, -10)) < 0); - assertTrue(CurveUtils.getSignedDistance(new Point(), + assertTrue(Straight.getSignedDistanceCCW(new Point(), new Point(10, -10), new Point(1, -1)) == 0); // second quadrant (y-axis is inverted) - assertTrue(CurveUtils.getSignedDistance(new Point(), - new Point(-10, -10), new Point(0, -10)) < 0); - assertTrue(CurveUtils.getSignedDistance(new Point(-10, -10), + assertTrue(Straight.getSignedDistanceCCW(new Point(), new Point(-10, + -10), new Point(0, -10)) < 0); + assertTrue(Straight.getSignedDistanceCCW(new Point(-10, -10), new Point(), new Point(0, -10)) > 0); - assertTrue(CurveUtils.getSignedDistance(new Point(), - new Point(-10, -10), new Point(-1, -1)) == 0); + assertTrue(Straight.getSignedDistanceCCW(new Point(), new Point(-10, + -10), new Point(-1, -1)) == 0); // third quadrant (y-axis is inverted) - assertTrue(CurveUtils.getSignedDistance(new Point(), new Point(10, 10), - new Point(0, 10)) < 0); - assertTrue(CurveUtils.getSignedDistance(new Point(10, 10), new Point(), - new Point(0, 10)) > 0); - assertTrue(CurveUtils.getSignedDistance(new Point(), new Point(10, 10), - new Point(1, 1)) == 0); + assertTrue(Straight.getSignedDistanceCCW(new Point(), + new Point(10, 10), new Point(0, 10)) < 0); + assertTrue(Straight.getSignedDistanceCCW(new Point(10, 10), + new Point(), new Point(0, 10)) > 0); + assertTrue(Straight.getSignedDistanceCCW(new Point(), + new Point(10, 10), new Point(1, 1)) == 0); // forth quadrant (y-axis is inverted) - assertTrue(CurveUtils.getSignedDistance(new Point(), + assertTrue(Straight.getSignedDistanceCCW(new Point(), new Point(-10, 10), new Point(0, 10)) > 0); - assertTrue(CurveUtils.getSignedDistance(new Point(-10, 10), + assertTrue(Straight.getSignedDistanceCCW(new Point(-10, 10), new Point(), new Point(0, 10)) < 0); - assertTrue(CurveUtils.getSignedDistance(new Point(), + assertTrue(Straight.getSignedDistanceCCW(new Point(), new Point(-10, 10), new Point(-1, 1)) == 0); } @@ -68,24 +71,24 @@ public class CurveUtilsTests { public void test_getSignedDistance_abs() { // do only check for the absolute value of the signed distance - assertTrue(PrecisionUtils.equal(Math.abs(CurveUtils.getSignedDistance( + assertTrue(PrecisionUtils.equal(Math.abs(Straight.getSignedDistanceCCW( new Point(0, -5), new Point(0, 5), new Point(5, 0))), 5)); - assertTrue(PrecisionUtils.equal(Math.abs(CurveUtils.getSignedDistance( + assertTrue(PrecisionUtils.equal(Math.abs(Straight.getSignedDistanceCCW( new Point(0, 5), new Point(0, -5), new Point(5, 0))), 5)); - assertTrue(PrecisionUtils.equal(Math.abs(CurveUtils.getSignedDistance( + assertTrue(PrecisionUtils.equal(Math.abs(Straight.getSignedDistanceCCW( new Point(0, -1), new Point(0, 1), new Point(5, 0))), 5)); - assertTrue(PrecisionUtils.equal(Math.abs(CurveUtils.getSignedDistance( + assertTrue(PrecisionUtils.equal(Math.abs(Straight.getSignedDistanceCCW( new Point(0, 1), new Point(0, -1), new Point(5, 0))), 5)); - assertTrue(PrecisionUtils.equal(Math.abs(CurveUtils.getSignedDistance( + assertTrue(PrecisionUtils.equal(Math.abs(Straight.getSignedDistanceCCW( new Point(-5, 0), new Point(5, 0), new Point(0, 5))), 5)); - assertTrue(PrecisionUtils.equal(Math.abs(CurveUtils.getSignedDistance( + assertTrue(PrecisionUtils.equal(Math.abs(Straight.getSignedDistanceCCW( new Point(-5, 0), new Point(5, 0), new Point(0, 5))), 5)); - assertTrue(PrecisionUtils.equal(Math.abs(CurveUtils.getSignedDistance( + assertTrue(PrecisionUtils.equal(Math.abs(Straight.getSignedDistanceCCW( new Point(-1, 0), new Point(1, 0), new Point(0, 5))), 5)); - assertTrue(PrecisionUtils.equal(Math.abs(CurveUtils.getSignedDistance( + assertTrue(PrecisionUtils.equal(Math.abs(Straight.getSignedDistanceCCW( new Point(-1, 0), new Point(1, 0), new Point(0, 5))), 5)); } @@ -94,35 +97,35 @@ public class CurveUtilsTests { // check both, direction and value: // first quadrant (y-axis is inverted) double len = 10d / Math.sqrt(2); - assertTrue(PrecisionUtils.equal(CurveUtils.getSignedDistance( + assertTrue(PrecisionUtils.equal(Straight.getSignedDistanceCCW( new Point(), new Point(10, -10), new Point(0, -10)), len)); - assertTrue(PrecisionUtils.equal(CurveUtils.getSignedDistance(new Point( - 10, -10), new Point(), new Point(0, -10)), -len)); - assertTrue(PrecisionUtils.equal(CurveUtils.getSignedDistance( + assertTrue(PrecisionUtils.equal(Straight.getSignedDistanceCCW( + new Point(10, -10), new Point(), new Point(0, -10)), -len)); + assertTrue(PrecisionUtils.equal(Straight.getSignedDistanceCCW( new Point(), new Point(10, -10), new Point(1, -1)), 0)); // second quadrant (y-axis is inverted) - assertTrue(PrecisionUtils.equal(CurveUtils.getSignedDistance( + assertTrue(PrecisionUtils.equal(Straight.getSignedDistanceCCW( new Point(), new Point(-10, -10), new Point(0, -10)), -len)); - assertTrue(PrecisionUtils.equal(CurveUtils.getSignedDistance(new Point( - -10, -10), new Point(), new Point(0, -10)), len)); - assertTrue(PrecisionUtils.equal(CurveUtils.getSignedDistance( + assertTrue(PrecisionUtils.equal(Straight.getSignedDistanceCCW( + new Point(-10, -10), new Point(), new Point(0, -10)), len)); + assertTrue(PrecisionUtils.equal(Straight.getSignedDistanceCCW( new Point(), new Point(-10, -10), new Point(-1, -1)), 0)); // third quadrant (y-axis is inverted) - assertTrue(PrecisionUtils.equal(CurveUtils.getSignedDistance( + assertTrue(PrecisionUtils.equal(Straight.getSignedDistanceCCW( new Point(), new Point(10, 10), new Point(0, 10)), -len)); - assertTrue(PrecisionUtils.equal(CurveUtils.getSignedDistance(new Point( - 10, 10), new Point(), new Point(0, 10)), len)); - assertTrue(PrecisionUtils.equal(CurveUtils.getSignedDistance( + assertTrue(PrecisionUtils.equal(Straight.getSignedDistanceCCW( + new Point(10, 10), new Point(), new Point(0, 10)), len)); + assertTrue(PrecisionUtils.equal(Straight.getSignedDistanceCCW( new Point(), new Point(10, 10), new Point(1, 1)), 0)); // forth quadrant (y-axis is inverted) - assertTrue(PrecisionUtils.equal(CurveUtils.getSignedDistance( + assertTrue(PrecisionUtils.equal(Straight.getSignedDistanceCCW( new Point(), new Point(-10, 10), new Point(0, 10)), len)); - assertTrue(PrecisionUtils.equal(CurveUtils.getSignedDistance(new Point( - -10, 10), new Point(), new Point(0, 10)), -len)); - assertTrue(PrecisionUtils.equal(CurveUtils.getSignedDistance( + assertTrue(PrecisionUtils.equal(Straight.getSignedDistanceCCW( + new Point(-10, 10), new Point(), new Point(0, 10)), -len)); + assertTrue(PrecisionUtils.equal(Straight.getSignedDistanceCCW( new Point(), new Point(-10, 10), new Point(-1, 1)), 0)); } @@ -237,7 +240,7 @@ public class CurveUtilsTests { QuadraticCurve c = new QuadraticCurve(points); for (double t = 0; t <= 1; t += step) { - assertTrue(c.contains(c.get(t))); + assertTrue("t = " + t, c.contains(c.get(t))); } } } @@ -253,6 +256,8 @@ public class CurveUtilsTests { Point[] points = new Point[numPoints]; for (int j = 0; j < numPoints; j++) { points[j] = new Point(rng.nextDouble(), rng.nextDouble()); + assertTrue(!Double.isNaN(points[j].x)); + assertTrue(!Double.isNaN(points[j].y)); } CubicCurve c = new CubicCurve(points); @@ -260,6 +265,73 @@ public class CurveUtilsTests { assertTrue(c.contains(c.get(t))); } } + + BezierCurve c = new BezierCurve(new Point(0.0, 0.0), new Point(0.05, + 0.1), new Point(0.05, 0.1), new Point(0.1, -0.1)); + + assertTrue(!c.contains(new Point(0.1, 0.1))); + } + + @Test + public void test_getBounds() { + Random rng = new Random(SEED); + + for (int i = 0; i < 1000; i++) { + double w = Math.abs(rng.nextDouble()), h = Math.abs(rng + .nextDouble()); + Rectangle controlBounds = new Rectangle(rng.nextDouble(), + rng.nextDouble(), w, h); + + for (int n = 2; n < 10; n++) { + Point[] points = new Point[n]; + + points[0] = controlBounds.getBottomLeft(); + points[n - 1] = controlBounds.getBottomRight(); + + if (n == 3) { + points[1] = points[0].getTranslated(points[2]).getScaled( + 0.5); + } + if (n > 3) { + double span = controlBounds.getWidth() / (n - 3); + double d = 0; + for (int j = 1; j < n - 1; j++, d += span) { + points[j] = new Point(controlBounds.getX() + d, + controlBounds.getY()); + } + } + + BezierCurve c = new BezierCurve(points); + + // y maximum + double d = controlBounds.getY() + controlBounds.getHeight() + - c.get(0.5).y; + + Rectangle bounds = new Rectangle(controlBounds.getX(), + controlBounds.getY() + controlBounds.getHeight() - d, + controlBounds.getWidth(), d); + + assertEquals(bounds, c.getBounds()); + + // x maximum + controlBounds = controlBounds.getRotatedCCW(Angle.fromDeg(90), + new Point()).getBounds(); + c.rotateCCW(Angle.fromDeg(90), new Point()); + + d = controlBounds.getX() + controlBounds.getWidth() + - c.get(0.5).x; + + bounds = new Rectangle(controlBounds.getX() + + controlBounds.getWidth() - d, controlBounds.getY(), + d, controlBounds.getHeight()); + + if (!bounds.equals(c.getBounds())) { + System.out.println("DEBUG"); + } + + assertEquals(bounds, c.getBounds()); + } + } } @Test @@ -276,7 +348,7 @@ public class CurveUtilsTests { } CubicCurve c = new CubicCurve(points); - Rectangle bounds = CurveUtils.getControlBounds(c); + Rectangle bounds = c.getControlBounds(); for (double t = 0; t <= 1; t += step) { assertTrue(bounds.contains(c.get(t))); } @@ -285,13 +357,143 @@ public class CurveUtilsTests { } @Test - public void test_getIntersections_BezierClipping_simple() { + public void test_getIntersections_overlapping() { + // test case from + // http://web.mit.edu/hyperbook/Patrikalakis-Maekawa-Cho/node111.html + CubicCurve c1 = new CubicCurve(new double[] { 0, 0, 0.8, 0.8, 1.6, + 0.32, 2.4, 0.608 }); + CubicCurve c2 = new CubicCurve(new double[] { 0.6, 0.392, 1.4, 0.68, + 2.2, 0.2, 3, 1 }); + + assertEquals(0, c1.getIntersections(c2).length); + assertEquals(0, c2.getIntersections(c1).length); + + // c1 contains c2 / c2 contains c1 + c2 = c1.clip(0.25, 0.75); + assertEquals(0, c1.getIntersections(c2).length); + assertEquals(0, c2.getIntersections(c1).length); + + // single end-point-intersection + c2 = new CubicCurve(new double[] { 2.4, 0.608, 3, 3, 3, 3, 4, 3 }); + assertEquals(1, c1.getIntersections(c2).length); + assertEquals(1, c2.getIntersections(c1).length); + } + + @Test + public void test_getIntersections_simple() { CubicCurve c1 = new CubicCurve(new double[] { 100, 200, 200, 100, 300, 300, 400, 200 }); CubicCurve c2 = new CubicCurve(new double[] { 250, 100, 350, 200, 150, 300, 250, 400 }); assertEquals(1, c1.getIntersections(c2).length); + + c1 = new CubicCurve(new Point(201.89274447949526, 106.43015521064301), + new Point(213.0, 325.0), new Point(387.0, 119.0), new Point( + 118.0, 310.0)); + Line l = new Line(new Point(105.66666666666667, 75.16666666666667), + new Point(528.3333333333334, 375.8333333333333)); + + assertEquals(1, c1.getIntersections(l).length); + + QuadraticCurve q1 = new QuadraticCurve(new Point(200, 50), new Point( + 210, 100), new Point(190, 150)); + l = new Line(new Point(100, 100), new Point(300, 100)); + + assertEquals(1, q1.getIntersections(l).length); + + q1 = new QuadraticCurve(new Point(250, 50), new Point(200, 100), + new Point(200, 150)); + l = new Line(new Point(528, 75), new Point(105, 75)); + + assertEquals(1, q1.getIntersections(l).length); + + q1 = new QuadraticCurve(new Point(500, 50), new Point(300, 150), + new Point(100, 300)); + + assertEquals(1, q1.getIntersections(l).length); + + q1 = new QuadraticCurve(new Point(171, 409), new Point(302, 106), + new Point(345, 310)); + l = new Line(new Point(105.66666666666667, 375.8333333333333), + new Point(528.3333333333334, 375.8333333333333)); + + Point[] testInters = q1.getIntersections(l); + assertEquals(1, testInters.length); } + @Test + public void test_getIntersections_linear() { + BezierCurve yAxis = new BezierCurve(new Point(0, 0), new Point(1, 0)); + + BezierCurve curve = new BezierCurve(new Point(0, -20), new Point( + 0.3333333333333333, -10.0), new Point(0.6666666666666666, 0.0), + new Point(1, -10)); + + assertEquals(0, yAxis.getIntersections(curve).length); + } + + @Test + public void test_getIntersections_random_containment() { + final int numPoints = 8; + + Random rng = new Random(SEED); + + for (int i = 0; i < 1000; i++) { + Point[] points = new Point[numPoints]; + for (int j = 0; j < numPoints; j++) { + points[j] = new Point(rng.nextDouble(), rng.nextDouble()); + assertTrue(!Double.isNaN(points[j].x)); + assertTrue(!Double.isNaN(points[j].y)); + } + + BezierCurve c1 = new BezierCurve(points); + + for (int j = 0; j < numPoints; j++) { + points[j] = new Point(rng.nextDouble(), rng.nextDouble()); + assertTrue(!Double.isNaN(points[j].x)); + assertTrue(!Double.isNaN(points[j].y)); + } + + BezierCurve c2 = new BezierCurve(points); + + for (Point poi : c1.getIntersections(c2)) { + // if (!c1.contains(poi) || !c2.contains(poi)) { + // System.out.println("DEBUG"); + // } + // TODO: every failing test has to be sourced out into the + // test_check_contains_difficult_cases() method. + assertTrue(c1.contains(poi)); + assertTrue(c2.contains(poi)); + } + } + } + + @Test + public void test_check_contains_difficult_cases() { + BezierCurve c1 = new BezierCurve(new Point(0.5402658595791646, + 0.9741509829024984), new Point(0.16279154085195757, + 0.6904753002704389), new Point(0.5362586913177897, + 0.03544287335013263), new Point(0.34435494116180165, + 0.31041629374338775), new Point(0.3850664934271003, + 0.5238288178983336), new Point(0.13829366099352602, + 0.7410634269933081), new Point(0.8948987750498976, + 0.6198888125981984), new Point(0.11349279987471517, + 0.3388985501965609)); + + BezierCurve c2 = new BezierCurve(new Point(0.3494484760769454, + 0.47795072857018706), new Point(0.9841912220562209, + 0.10765341979304721), new Point(0.27977429726230696, + 0.7303050467844633), new Point(0.28022390386455787, + 0.3313265057575542), new Point(0.3914004373221056, + 0.6451799723354514), new Point(0.2875477493472879, + 0.5132577259019093), new Point(0.13579952633028602, + 0.5665583087171101), new Point(0.09831516780965299, + 0.25959706254491044)); + + Point p = new Point(0.3736012772933578, 0.4639965007739409); + + assertTrue(c1.contains(p)); + assertTrue(c2.contains(p)); + } } diff --git a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/EllipseTests.java b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/EllipseTests.java index 3b0f803..83d7dcf 100644 --- a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/EllipseTests.java +++ b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/EllipseTests.java @@ -6,11 +6,11 @@ * http://www.eclipse.org/legal/epl-v10.html * * Contributors: - * Alexander Ny√üen (itemis AG) - initial API and implementation + * Alexander Nyßen (itemis AG) - initial API and implementation + * Matthias Wienand (itemis AG) - contribution for Bugzilla #355997 * *******************************************************************************/ package org.eclipse.gef4.geometry.tests; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -21,6 +21,7 @@ import org.eclipse.gef4.geometry.planar.Line; import org.eclipse.gef4.geometry.planar.Rectangle; import org.junit.Test; + /** * Unit tests for {@link Ellipse}. * @@ -37,13 +38,13 @@ public class EllipseTests { Rectangle r = new Rectangle(34.3435, 56.458945, 123.3098, 146.578); Ellipse e = new Ellipse(r); - assertTrue(e.contains(r.getCenter())); + assertTrue(e.contains(r.getCentroid())); assertTrue(e.contains(r.getLeft())); - assertTrue(e.contains(r.getLeft().getTranslated( - PRECISION_FRACTION * 100, 0))); + assertTrue(e.contains(r.getLeft().getTranslated(PRECISION_FRACTION * 1, + 0))); assertFalse(e.contains(r.getLeft().getTranslated( - -PRECISION_FRACTION * 100, 0))); + -PRECISION_FRACTION * 1000, 0))); assertTrue(e.contains(r.getTop())); assertTrue(e.contains(r.getTop().getTranslated(0, @@ -78,12 +79,12 @@ public class EllipseTests { Rectangle r = new Rectangle(34.3435, 56.458945, 123.3098, 146.578); Ellipse e = new Ellipse(r); for (Line l : r.getOutlineSegments()) { - assertTrue(e.intersects(l)); // line touches ellipse (tangent) + assertTrue(e.touches(l)); // line touches ellipse (tangent) } } @Test - public void test_get_intersections_with_Ellipse() { + public void test_get_intersections_with_Ellipse_strict() { Rectangle r = new Rectangle(34.3435, 56.458945, 123.3098, 146.578); Ellipse e1 = new Ellipse(r); Ellipse e2 = new Ellipse(r); @@ -97,6 +98,10 @@ public class EllipseTests { Rectangle r2 = r.getExpanded(0, -10, -10, -10); e2 = new Ellipse(r2); intersections = e1.getIntersections(e2); + for (Point poi : intersections) { + assertTrue(e1.contains(poi)); + assertTrue(e2.contains(poi)); + } assertEquals(1, intersections.length); // if we create an x-scaled ellipse at the same position as before, they @@ -173,6 +178,72 @@ public class EllipseTests { 1, equalsRight); } + private void intersectionsTolerance(Ellipse e1, Ellipse e2, + Point... expected) { + Point[] intersections = e1.getIntersections(e2); + boolean[] foundExpected = new boolean[expected.length]; + for (Point poi : intersections) { + assertTrue( + "All points of intersection have to be contained by the first ellipse.", + e1.contains(poi)); + assertTrue( + "All points of intersection have to be contained by the second ellipse.", + e2.contains(poi)); + for (int i = 0; i < expected.length; i++) { + if (poi.equals(expected[i])) { + foundExpected[i] = true; + } + } + } + for (int i = 0; i < expected.length; i++) { + assertTrue("An expected point of intersection " + expected[i] + + " not found in the list of intersections.", + foundExpected[i]); + } + } + + @Test + public void test_getIntersections_with_Ellipse_tolerance() { + 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); + + // touching left + Rectangle r2 = r.getExpanded(0, -10, -10, -10); + e2 = new Ellipse(r2); + intersectionsTolerance(e1, e2, r.getLeft()); + + // 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) + r2 = r.getExpanded(0, 0, 100, 0); + e2 = new Ellipse(r2); + intersectionsTolerance(e1, e2, r.getLeft()); // TODO: other two pois + + // 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); + intersectionsTolerance(e1, e2, r.getTop()); // TODO: other two pois + + // 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); + intersectionsTolerance(e1, e2, r.getTop(), r.getBottom()); + + // 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); + intersectionsTolerance(e1, e2, r.getLeft(), r.getRight()); + } + + // @Ignore("This test is too strict. For a liberal test see below: test_getIntersections_with_Ellipse_Bezier_special_tolerance") @Test public void test_getIntersections_with_Ellipse_Bezier_special() { // 3 nearly tangential intersections @@ -181,7 +252,8 @@ public class EllipseTests { assertEquals(2, e1.getIntersections(e2).length); e2 = new Ellipse(133, 90, 2 * (315 - 133), 200); - assertEquals(3, e1.getIntersections(e2).length); + Point[] intersections = e1.getIntersections(e2); + assertEquals(3, intersections.length); e2 = new Ellipse(143, 90, 2 * (315 - 143), 200); assertEquals(3, e1.getIntersections(e2).length); @@ -190,4 +262,21 @@ public class EllipseTests { assertEquals(3, e1.getIntersections(e2).length); } + @Test + public void test_getIntersections_with_Ellipse_Bezier_special_tolerance() { + // 3 nearly tangential intersections + Ellipse e1 = new Ellipse(126, 90, 378, 270); + Ellipse e2 = new Ellipse(222, 77, 200, 200); + intersectionsTolerance(e1, e2); // TODO: find out the 2 expected points + + e2 = new Ellipse(133, 90, 2 * (315 - 133), 200); + intersectionsTolerance(e1, e2); // TODO: find out the 3 expected points + + e2 = new Ellipse(143, 90, 2 * (315 - 143), 200); + intersectionsTolerance(e1, e2); // TODO: find out the 3 expected points + + e2 = new Ellipse(145, 90, 2 * (315 - 145), 200); + intersectionsTolerance(e1, e2); // TODO: find out the 3 expected points + } + } diff --git a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/LineTests.java b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/LineTests.java index 63ecf4a..c6a4b53 100644 --- a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/LineTests.java +++ b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/LineTests.java @@ -19,6 +19,7 @@ import static org.junit.Assert.assertTrue; import org.eclipse.gef4.geometry.Point; import org.eclipse.gef4.geometry.euclidean.Straight; +import org.eclipse.gef4.geometry.euclidean.Vector; import org.eclipse.gef4.geometry.planar.Line; import org.eclipse.gef4.geometry.planar.Rectangle; import org.eclipse.gef4.geometry.utils.PrecisionUtils; @@ -63,13 +64,6 @@ public class LineTests { } @Test - public void test_contains_Rect() { - assertFalse(new Line(0, 0, 5, 0).contains(new Rectangle())); - assertFalse(new Line(0, 0, 5, 0).contains(new Rectangle(0, 0, 5, 0))); - assertFalse(new Line(0, 0, 5, 0).contains(new Rectangle(1, 1, 1, 1))); - } - - @Test public void test_copy() { Line l1 = new Line(0, 0, 5, 0); assertTrue(l1.equals(l1.getCopy())); @@ -131,7 +125,7 @@ public class LineTests { // simple intersection Line l1 = new Line(0, 0, 4, 4); Line l2 = new Line(0, 4, 4, 0); - assertTrue(l1.intersects(l2)); + assertTrue(l1.touches(l2)); assertTrue(l1.getIntersection(l2).equals(new Point(2, 2))); assertTrue(l2.getIntersection(l1).equals(new Point(2, 2))); @@ -167,13 +161,109 @@ public class LineTests { } @Test + public void test_intersects_specials() { + // degenerated cases + Line degen = new Line(new Point(), new Point()); + Line normal = new Line(new Point(-5, 0), new Point(5, 0)); + assertTrue(degen.touches(normal)); + assertTrue(normal.touches(degen)); + + // identical + assertTrue(normal.touches(normal)); + + // intersection within precision. no real intersection + Line close = new Line(new Point(-5, UNRECOGNIZABLE_FRACTION), + new Point(5, UNRECOGNIZABLE_FRACTION)); + assertTrue(normal.touches(close)); + assertTrue(close.touches(normal)); + + Line closeSp = new Line(new Point(-5, UNRECOGNIZABLE_FRACTION), + new Point(-5, 10)); + assertTrue(normal.touches(closeSp)); + assertTrue(closeSp.touches(normal)); + + Line closeEp = new Line(new Point(-5, 10), new Point(-5, + UNRECOGNIZABLE_FRACTION)); + assertTrue(normal.touches(closeEp)); + assertTrue(closeEp.touches(normal)); + + // intersection within precision, straights do intersect too, but the + // intersection of the straights is out of precision + Line slope = new Line(new Point(-5, UNRECOGNIZABLE_FRACTION), + new Point(5, 2 * UNRECOGNIZABLE_FRACTION)); + assertTrue(normal.touches(slope)); + assertTrue(slope.touches(normal)); + + // no intersection, straights do intersect + Line elsewhere = new Line(new Point(-5, 1), new Point(5, 10)); + assertTrue(!normal.touches(elsewhere)); + assertTrue(!elsewhere.touches(normal)); + + // big lines, imprecisely parallel but intersecting + Line bigX = new Line(new Point(-1000, 0), new Point(1000, 0)); + Line impreciselyParallel = new Line(new Point(-1000, + -UNRECOGNIZABLE_FRACTION), new Point(1000, + UNRECOGNIZABLE_FRACTION)); + assertTrue(new Vector(bigX.getP1(), bigX.getP2()) + .isParallelTo(new Vector(impreciselyParallel.getP1(), + impreciselyParallel.getP2()))); + assertTrue(bigX.touches(impreciselyParallel)); + } + + @Test + public void test_getIntersection_specials() { + // degenerated cases + Line degen = new Line(new Point(), new Point()); + Line normal = new Line(new Point(-5, 0), new Point(5, 0)); + assertEquals(new Point(), degen.getIntersection(normal)); + assertEquals(new Point(), normal.getIntersection(degen)); + + // identical + assertNull(normal.getIntersection(normal)); + + // intersection within precision, no real intersection + Line close = new Line(new Point(-5, UNRECOGNIZABLE_FRACTION), + new Point(5, UNRECOGNIZABLE_FRACTION)); + // parallel so we do not return an intersection point + assertNull(normal.getIntersection(close)); + assertNull(close.getIntersection(normal)); + + // non parallel, start point intersection + Line closeSp = new Line(new Point(-5, UNRECOGNIZABLE_FRACTION), + new Point(-5, 10)); + assertEquals(new Point(-5, 0), normal.getIntersection(closeSp)); + assertEquals(new Point(-5, 0), closeSp.getIntersection(normal)); + + // non parallel, end point intersection + Line closeEp = new Line(new Point(-5, 10), new Point(-5, + UNRECOGNIZABLE_FRACTION)); + assertEquals(new Point(-5, 0), normal.getIntersection(closeEp)); + assertEquals(new Point(-5, 0), closeEp.getIntersection(normal)); + + // intersection within precision, straights do intersect too, but the + // intersection of the straights is out of precision + Line slope = new Line(new Point(-5, UNRECOGNIZABLE_FRACTION), + new Point(5, 2 * UNRECOGNIZABLE_FRACTION)); + + // no point of intersection can be identified, because both endpoints + // lie on the line. is is assumed to be parallel. + assertNull(normal.getIntersection(slope)); + assertNull(slope.getIntersection(normal)); + + // no intersection, straights do intersect + Line elsewhere = new Line(new Point(-5, 1), new Point(5, 10)); + assertNull(normal.getIntersection(elsewhere)); + assertNull(elsewhere.getIntersection(normal)); + } + + @Test public void test_getters() { - for (double x1 = -2; x1 <= 2; x1 += 0.2) { - for (double y1 = -2; y1 <= 2; y1 += 0.2) { + for (double x1 = -2; x1 <= 2; x1 += 0.5) { + for (double y1 = -2; y1 <= 2; y1 += 0.5) { Point p1 = new Point(x1, y1); - for (double x2 = -2; x2 <= 2; x2 += 0.2) { - for (double y2 = -2; y2 <= 2; y2 += 0.2) { + for (double x2 = -2; x2 <= 2; x2 += 0.5) { + for (double y2 = -2; y2 <= 2; y2 += 0.5) { Point p2 = new Point(x2, y2); Line line = new Line(p1, p2); assertTrue(line.getP1().equals(p1)); @@ -214,45 +304,45 @@ public class LineTests { // simple intersection Line l1 = new Line(0, 0, 4, 4); Line l2 = new Line(0, 4, 4, 0); - assertTrue(l1.intersects(l2)); - assertTrue(l2.intersects(l1)); + assertTrue(l1.touches(l2)); + assertTrue(l2.touches(l1)); // lines touch in one point Line l3 = new Line(4, 4, 7, 9); - assertTrue(l1.intersects(l3)); - assertTrue(l3.intersects(l1)); + assertTrue(l1.touches(l3)); + assertTrue(l3.touches(l1)); // lines overlap Line l4 = new Line(2, 2, 6, 6); - assertTrue(l1.intersects(l4)); - assertTrue(l4.intersects(l1)); + assertTrue(l1.touches(l4)); + assertTrue(l4.touches(l1)); // one line is a single point Line l5 = new Line(1, 1, 1, 1); - assertTrue(l5.intersects(l1)); - assertTrue(l1.intersects(l5)); + assertTrue(l5.touches(l1)); + assertTrue(l1.touches(l5)); // straights would intersect, but these lines do not Line l6 = new Line(4, 0, 5, 4); - assertFalse(l6.intersects(l1)); - assertFalse(l1.intersects(l6)); + assertFalse(l6.touches(l1)); + assertFalse(l1.touches(l6)); } @Test public void test_intersects_with_Rect() { Line l1 = new Line(0, 0, 4, 4); Rectangle r1 = new Rectangle(0, 4, 4, 4); - assertTrue(l1.intersects(r1)); + assertTrue(l1.touches(r1)); } @Test public void test_setters() { - for (double x1 = -2; x1 <= 2; x1 += 0.2) { - for (double y1 = -2; y1 <= 2; y1 += 0.2) { + for (double x1 = -2; x1 <= 2; x1 += 0.5) { + for (double y1 = -2; y1 <= 2; y1 += 0.5) { Point p1 = new Point(x1, y1); - for (double x2 = -2; x2 <= 2; x2 += 0.2) { - for (double y2 = -2; y2 <= 2; y2 += 0.2) { + for (double x2 = -2; x2 <= 2; x2 += 0.5) { + for (double y2 = -2; y2 <= 2; y2 += 0.5) { Point p2 = new Point(x2, y2); Line line = new Line(new Point(-5, -5), new Point(-10, diff --git a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/PointListUtilsTests.java b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/PointListUtilsTests.java index 11c9f5e..5450bde 100644 --- a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/PointListUtilsTests.java +++ b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/PointListUtilsTests.java @@ -54,7 +54,7 @@ public class PointListUtilsTests { points[i] = new Point(i * i, i + i); } - Point[] copy = PointListUtils.getCopy(points); + Point[] copy = PointListUtils.copy(points); for (int i = 0; i < 10; i++) { assertTrue(points[i].equals(copy[i])); diff --git a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/PolygonTests.java b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/PolygonTests.java index 8bf959f..49e40bd 100644 --- a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/PolygonTests.java +++ b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/PolygonTests.java @@ -388,35 +388,51 @@ public class PolygonTests { @Test public void test_getIntersections_Ellipse() { - assertTrue(new Polygon(RHOMB.getIntersections(new Ellipse(0, 0, 4, 4))) - .getBounds().equals(RHOMB.getBounds())); + assertTrue(new Polygon(RHOMB.getOutline().getIntersections( + new Ellipse(0, 0, 4, 4).getOutline())).getBounds().equals( + RHOMB.getBounds())); } @Test public void test_getIntersections_Polygon() { - // TODO: something's wrong... debug this test - assertEquals(2, RHOMB.getIntersections(RECTANGLE).length); - assertEquals(4, - RHOMB.getIntersections(RHOMB.getBounds().toPolygon()).length); + assertEquals( + 2, + RHOMB.getOutline().getIntersections(RECTANGLE.getOutline()).length); + assertEquals( + 4, + RHOMB.getOutline().getIntersections( + RHOMB.getBounds().toPolygon().getOutline()).length); } @Test public void test_getIntersections_Polyline() { - assertEquals(1, RHOMB.getIntersections(new Polyline(0, 0, 0, 4)).length); - assertEquals(2, - RHOMB.getIntersections(new Polyline(0, 0, 0, 4, 4, 4)).length); + assertEquals( + 1, + RHOMB.getOutline().getIntersections(new Polyline(0, 0, 0, 4)).length); + assertEquals( + 2, + RHOMB.getOutline().getIntersections( + new Polyline(0, 0, 0, 4, 4, 4)).length); assertEquals( 3, - RHOMB.getIntersections(new Polyline(0, 0, 0, 4, 4, 4, 2, 2)).length); - assertEquals(4, RHOMB.getIntersections(new Polyline(0, 0, 0, 4, 4, 4, - 2, 2, 4, 0)).length); - assertEquals(5, RHOMB.getIntersections(new Polyline(0, 0, 0, 4, 4, 4, - 2, 2, 4, 0, 0, 0)).length); + RHOMB.getOutline().getIntersections( + new Polyline(0, 0, 0, 4, 4, 4, 2, 2)).length); + assertEquals( + 4, + RHOMB.getOutline().getIntersections( + new Polyline(0, 0, 0, 4, 4, 4, 2, 2, 4, 0)).length); + assertEquals( + 5, + RHOMB.getOutline().getIntersections( + new Polyline(0, 0, 0, 4, 4, 4, 2, 2, 4, 0, 0, 0)).length); } @Test public void test_getIntersections_Rectangle() { - assertEquals(4, RHOMB.getIntersections(RHOMB.getBounds()).length); + assertEquals( + 4, + RHOMB.getOutline().getIntersections( + RHOMB.getBounds().getOutline()).length); } @Test @@ -481,76 +497,76 @@ public class PolygonTests { @Test public void test_intersects_Ellipse() { - assertTrue(RHOMB.intersects(new Ellipse(0, 0, 4, 4))); + assertTrue(RHOMB.touches(new Ellipse(0, 0, 4, 4))); } @Test public void test_intersects_Line() { - assertFalse(RHOMB.intersects(new Line(-1, 1, 1, -1))); - assertTrue(RHOMB.intersects(new Line(-1, 2, 2, 2))); - assertTrue(RHOMB.intersects(new Line(2, 2, 5, 2))); - assertTrue(RHOMB.intersects(new Line(0, 2, 2, 0))); - assertTrue(RHOMB.intersects(new Line(0, 2, 2, 4))); - assertTrue(RHOMB.intersects(new Line(0, 2, 2, 2))); - assertTrue(RHOMB.intersects(new Line(1, 2, 3, 2))); + assertFalse(RHOMB.touches(new Line(-1, 1, 1, -1))); + assertTrue(RHOMB.touches(new Line(-1, 2, 2, 2))); + assertTrue(RHOMB.touches(new Line(2, 2, 5, 2))); + assertTrue(RHOMB.touches(new Line(0, 2, 2, 0))); + assertTrue(RHOMB.touches(new Line(0, 2, 2, 4))); + assertTrue(RHOMB.touches(new Line(0, 2, 2, 2))); + assertTrue(RHOMB.touches(new Line(1, 2, 3, 2))); assertTrue(new Polygon(new Point(), new Point(0, 5), new Point(5, 5), - new Point(5, 0), new Point(2.5, 2.5)).intersects(new Line(1, - 2.5, 4, 2.5))); - assertTrue(RHOMB.intersects(new Line(-1, 2, 5, 2))); + new Point(5, 0), new Point(2.5, 2.5)).touches(new Line(1, 2.5, + 4, 2.5))); + assertTrue(RHOMB.touches(new Line(-1, 2, 5, 2))); } @Test public void test_intersects_Polygon_Rhomb() { assertTrue( "The rhomb intersects itself, because it touches/contains itself", - RHOMB.intersects(RHOMB)); + RHOMB.touches(RHOMB)); assertTrue( "The rhomb intersects its shrinked self, because its shrinked self is fully contained by the rhomb", - RHOMB.intersects(RHOMB.getCopy().scale(0.5, new Point(2, 2)))); + RHOMB.touches(RHOMB.getCopy().scale(0.5, new Point(2, 2)))); assertTrue( "The rhomb intersects its expanded self, because its expanded self fully contains the rhomb", - RHOMB.intersects(RHOMB.getCopy().scale(2, new Point(2, 2)))); + RHOMB.touches(RHOMB.getCopy().scale(2, new Point(2, 2)))); assertTrue(RHOMB.contains(new Point(4, 2))); assertTrue(RHOMB.getTranslated(new Point(4, 0)).contains( new Point(4, 2))); assertTrue("The rhomb touches the given one", - RHOMB.intersects(RHOMB.getTranslated(4, 0))); + RHOMB.touches(RHOMB.getTranslated(4, 0))); assertTrue("The rhomb intersects the given one", - RHOMB.intersects(RHOMB.getTranslated(2, 0))); + RHOMB.touches(RHOMB.getTranslated(2, 0))); } @Test public void test_intersects_Polyline() { - assertFalse(RHOMB.intersects(new Polyline(0, 0, -1, -1))); - assertTrue(RHOMB.intersects(new Polyline(0, 0, 0, 4))); - assertTrue(RHOMB.intersects(new Polyline(0, 0, 0, 4, 4, 4))); - assertTrue(RHOMB.intersects(new Polyline(0, 0, 0, 4, 4, 4, 2, 2))); - assertTrue(RHOMB.intersects(new Polyline(0, 0, 0, 4, 4, 4, 2, 2, 4, 0))); - assertTrue(RHOMB.intersects(new Polyline(0, 0, 0, 4, 4, 4, 2, 2, 4, 0, - 0, 0))); + assertFalse(RHOMB.touches(new Polyline(0, 0, -1, -1))); + assertTrue(RHOMB.touches(new Polyline(0, 0, 0, 4))); + assertTrue(RHOMB.touches(new Polyline(0, 0, 0, 4, 4, 4))); + assertTrue(RHOMB.touches(new Polyline(0, 0, 0, 4, 4, 4, 2, 2))); + assertTrue(RHOMB.touches(new Polyline(0, 0, 0, 4, 4, 4, 2, 2, 4, 0))); + assertTrue(RHOMB.touches(new Polyline(0, 0, 0, 4, 4, 4, 2, 2, 4, 0, 0, + 0))); } @Test public void test_intersects_Rectangle_Rhomb() { assertTrue( "This rectangle is inside the rhomb and does therefore intersect it", - RHOMB.intersects(new Rectangle(1.5, 1.5, 1, 1))); + RHOMB.touches(new Rectangle(1.5, 1.5, 1, 1))); assertTrue( "This rectangle is inside the rhomb and does therefore intersect it", - RHOMB.intersects(new Rectangle(1, 1, 2, 2))); + RHOMB.touches(new Rectangle(1, 1, 2, 2))); assertTrue( "This rectangle is partly outside the rhomb and intersects it (intersection points are two polygon points)", - RHOMB.intersects(new Rectangle(0, 0, 2, 2))); + RHOMB.touches(new Rectangle(0, 0, 2, 2))); assertTrue(RHOMB.contains(new Point(0, 2))); assertTrue(new Rectangle(-2, 0, 2, 2).contains(new Point(0, 2))); assertTrue( "This rectangle is outside the rhomb and touches it in Point (0,2), which is contained in both", - RHOMB.intersects(new Rectangle(-2, 0, 2, 2))); + RHOMB.touches(new Rectangle(-2, 0, 2, 2))); } @Test @@ -594,4 +610,31 @@ public class PolygonTests { } } + @Test + public void test_contains_imprecision() { + Polygon poly = new Polygon(new Point(0.16384889386958243, + 0.5199137157713366), new Point(0.16388083282075672, + 0.5199518598437528), new Point(0.1639056804775328, + 0.5199687901987595), new Point(0.16381011945655763, + 0.5198551130149273)); + Point p = new Point(0.16383865075635337, 0.5198962222767928); + assertTrue(poly.contains(p)); + } + + @Test + public void test_contains_Point_special_cases() { + /* + * TODO: special cases are impossible to test without knowing how the + * algorithm counts intersections. The special cases are: + * + * 1) the point is inside the polygon. the scan line intersects the + * polygon in a vertex of the polygon. this is a double intersection in + * the same point. + * + * 2) the point is inside the polygon. the scan line intersects the + * polygon in a vertex of the polygon and somewhere else. the vertex + * intersection is a double intersection in the same point. + */ + } + } diff --git a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/RectangleTests.java b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/RectangleTests.java index 65bcfd5..4ac9488 100644 --- a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/RectangleTests.java +++ b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/RectangleTests.java @@ -169,8 +169,6 @@ public class RectangleTests { // test self containment assertTrue(preciseRect.contains(preciseRect)); - assertTrue(preciseRect.contains(preciseRect.getX(), preciseRect.getY(), - preciseRect.getWidth(), preciseRect.getHeight())); assertFalse(preciseRect.contains(preciseRect.getExpanded( RECOGNIZABLE_FRACTION, RECOGNIZABLE_FRACTION))); @@ -317,12 +315,19 @@ public class RectangleTests { assertTrue(PrecisionUtils.equal(25.92755905511811, rect.getHeight())); rect = new Rectangle(-9.486614173228347, -34.431496062992125, - 41.99055118110236, 25.92755905511811).getScaled(2, 0); + 2 * 9.486614173228347, 34.431496062992125).getScaled(2, 0); - assertTrue(PrecisionUtils.equal(-9.486614173228347, rect.getX())); - assertTrue(PrecisionUtils.equal(-34.431496062992125, rect.getY())); - assertTrue(PrecisionUtils.equal(2 * 41.99055118110236, rect.getWidth())); + assertTrue(PrecisionUtils.equal(2 * -9.486614173228347, rect.getX())); + assertTrue(PrecisionUtils.equal(0.5 * -34.431496062992125, rect.getY())); + assertTrue(PrecisionUtils.equal(4 * 9.486614173228347, rect.getWidth())); assertTrue(PrecisionUtils.equal(0, rect.getHeight())); + + // TODO: is this the desired behavior? + // assertTrue(PrecisionUtils.equal(-9.486614173228347, rect.getX())); + // assertTrue(PrecisionUtils.equal(-34.431496062992125, rect.getY())); + // assertTrue(PrecisionUtils.equal(2 * 41.99055118110236, + // rect.getWidth())); + // assertTrue(PrecisionUtils.equal(0, rect.getHeight())); } @Test @@ -435,27 +440,45 @@ public class RectangleTests { Rectangle r1 = new Rectangle(-5, -5, 10, 10); for (Line seg : r1.getOutlineSegments()) { - assertTrue(r1.intersects(seg)); + assertTrue(r1.touches(seg)); } - assertTrue(r1 - .intersects(new Line(r1.getTopLeft(), r1.getBottomRight()))); - assertTrue(r1.intersects(new Line(r1.getTop(), r1.getBottom()))); + assertTrue(r1.touches(new Line(r1.getTopLeft(), r1.getBottomRight()))); + assertTrue(r1.touches(new Line(r1.getTop(), r1.getBottom()))); - assertTrue(r1.intersects(new Line(-10, 0, 10, 0))); - assertFalse(r1.intersects(new Line(-10, 0, -6, 0))); - assertFalse(r1.intersects(new Line(0, -10, 0, -6))); - assertFalse(r1.intersects(new Line(10, 0, 6, 0))); - assertFalse(r1.intersects(new Line(0, 10, 0, 6))); + assertTrue(r1.touches(new Line(-10, 0, 10, 0))); + assertFalse(r1.touches(new Line(-10, 0, -6, 0))); + assertFalse(r1.touches(new Line(0, -10, 0, -6))); + assertFalse(r1.touches(new Line(10, 0, 6, 0))); + assertFalse(r1.touches(new Line(0, 10, 0, 6))); } @Test public void test_intersects_with_Rectangle() { - forRectanglePairs(new IPairAction() { - public void action(Rectangle r1, Rectangle r2) { - assertTrue(r1.intersects(r2) != r1.getIntersected(r2).isEmpty()); - } - }); + assertTrue(new Rectangle(0, 0, 100, 100).touches(new Rectangle(0, 0, + 100, 100))); + assertTrue(new Rectangle(0, 0, 100, 100).touches(new Rectangle(50, 50, + 100, 100))); + assertTrue(new Rectangle(0, 0, 100, 100).touches(new Rectangle(100, + 100, 100, 100))); + assertTrue(new Rectangle(0, 0, 100, 100).touches(new Rectangle(-100, + -100, 100, 100))); + assertTrue(new Rectangle(0, 0, 100, 100).touches(new Rectangle(-50, 0, + 100, 100))); + assertTrue(new Rectangle(0, 0, 100, 100).touches(new Rectangle(-100, 0, + 100, 100))); + assertTrue(new Rectangle(0, 0, 100, 100).touches(new Rectangle(50, 0, + 100, 100))); + assertTrue(new Rectangle(0, 0, 100, 100).touches(new Rectangle(100, 0, + 100, 100))); + assertTrue(new Rectangle(0, 0, 100, 100).touches(new Rectangle(0, -50, + 100, 100))); + assertTrue(new Rectangle(0, 0, 100, 100).touches(new Rectangle(0, -100, + 100, 100))); + assertTrue(new Rectangle(0, 0, 100, 100).touches(new Rectangle(0, 50, + 100, 100))); + assertTrue(new Rectangle(0, 0, 100, 100).touches(new Rectangle(0, 100, + 100, 100))); } @Test @@ -489,7 +512,7 @@ public class RectangleTests { Rectangle rect4 = new Rectangle(0, 0, 10, 10); assertEquals(rect3, rect4); - // TODO: how about negative width/height? + // negative width/height? assertEquals(new Rectangle(), new Rectangle(0, 0, -10, -10)); assertEquals(new Rectangle(5, 5, 0, 10), new Rectangle(5, 5, -10, 10)); assertEquals(new Rectangle(5, 5, 10, 0), new Rectangle(5, 5, 10, -10)); @@ -674,7 +697,7 @@ public class RectangleTests { assertEquals(bl, rect.getBottomLeft()); assertEquals(bo, rect.getBottom()); assertEquals(br, rect.getBottomRight()); - assertEquals(ce, rect.getCenter()); + assertEquals(ce, rect.getCentroid()); } }); } diff --git a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/VectorTests.java b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/VectorTests.java index d0fe5b5..771e5c9 100644 --- a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/VectorTests.java +++ b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/VectorTests.java @@ -212,13 +212,17 @@ public class VectorTests { // TODO: normalize the vectors first, so that they get comparable. - // the description of the method is mistakable: - // 1) does it mean that an angle of 180° returns the same dissimilarity - // as an angle of 0° - // 2) or does it mean that an angle of 180° returns the highest - // dissimilarity? - // - // the following code expects the first case + /* + * the description of the method is mistakable: + * + * 1) does it mean that an angle of 180 degrees returns the same + * dissimilarity as an angle of 0 degrees? + * + * 2) or does it mean that an angle of 180 degrees returns the highest + * dissimilarity? + * + * the following code expects the first case + */ forVectorPairs(new VectorPairAction() { @Override @@ -387,14 +391,9 @@ public class VectorTests { forVectorPairs(new VectorPairAction() { @Override public void action(Vector a, Vector b) { - Angle alpha = a.getAngle(b); - alpha.setRad(alpha.rad() * 2d); - - // DEBUG - // if (a.isParallelTo(b) != alpha.equals(Angle.fromRad(0))) - // throw new IllegalStateException(""); - - assertTrue(a.isParallelTo(b) == alpha.equals(Angle.fromRad(0))); + // TODO: rewrite this test! + assertTrue(a.isParallelTo(b) == PrecisionUtils.equal( + a.getDissimilarity(b), 0)); } }); } diff --git a/org.eclipse.gef4.geometry/README.txt b/org.eclipse.gef4.geometry/README.txt index 0d09043..253ab67 100644 --- a/org.eclipse.gef4.geometry/README.txt +++ b/org.eclipse.gef4.geometry/README.txt @@ -1,6 +1,15 @@ Differences to Draw2d Geometry API + 1) only double precision, with imprecise relations -2) scale only affects size, not location + + +2) scaling is performed relative to the geometries center, not the coordinate system origin + +To get the old behavior back, you can use: scale(x, y, new Point()); + + 3) Rectangle contains the right and bottom border + + 4) Polygon and Polyline instead of PointList \ No newline at end of file diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/Point.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/Point.java index 70bcbad..362daaa 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/Point.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/Point.java @@ -33,16 +33,6 @@ public class Point implements Cloneable, Serializable { private static final long serialVersionUID = 1L; /** - * The x value. - */ - public double x; - - /** - * The y value. - */ - public double y; - - /** * Creates a new Point representing the MAX of two provided Points. * * @param p1 @@ -69,6 +59,16 @@ public class Point implements Cloneable, Serializable { } /** + * The x value. + */ + public double x; + + /** + * The y value. + */ + public double y; + + /** * Constructs a Point at location (0,0). * */ @@ -204,13 +204,20 @@ public class Point implements Cloneable, Serializable { } /** - * Creates a new SWT {@link org.eclipse.swt.graphics.Point Point} from this - * Point. + * Returns a new {@link Point} scaled by the given scale-factors. The + * scaling is performed relative to the given {@link Point} center. * - * @return A new SWT Point + * @param factorX + * The horizontal scale-factor + * @param factorY + * The vertical scale-factor + * @param center + * The relative {@link Point} for the scaling + * @return The new, scaled {@link Point} */ - public org.eclipse.swt.graphics.Point toSWTPoint() { - return new org.eclipse.swt.graphics.Point((int) x, (int) y); + public Point getScaled(double factorX, double factorY, Point center) { + return getTranslated(center.getNegated()).scale(factorX, factorY) + .translate(center); } /** @@ -307,6 +314,25 @@ public class Point implements Cloneable, Serializable { } /** + * Scales this {@link Point} by the given scale-factors. The scaling is + * performed relative to the given {@link Point} center. + * + * @param factorX + * The horizontal scale-factor + * @param factorY + * The vertical scale-factor + * @param center + * The relative {@link Point} for the scaling + * @return this for convenience + */ + public Point scale(double factorX, double factorY, Point center) { + translate(center.getNegated()); + scale(factorX, factorY); + translate(center); + return this; + } + + /** * Sets the location of this Point to the provided x and y locations. * * @return this for convenience @@ -367,6 +393,16 @@ public class Point implements Cloneable, Serializable { } /** + * Creates a new SWT {@link org.eclipse.swt.graphics.Point Point} from this + * Point. + * + * @return A new SWT Point + */ + public org.eclipse.swt.graphics.Point toSWTPoint() { + return new org.eclipse.swt.graphics.Point((int) x, (int) y); + } + + /** * Shifts this Point by the values of the Dimension along each axis, and * returns this for convenience. * @@ -435,4 +471,5 @@ public class Point implements Cloneable, Serializable { public double y() { return y; } + } diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/euclidean/Straight.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/euclidean/Straight.java index 44387ef..694f1fa 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/euclidean/Straight.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/euclidean/Straight.java @@ -17,7 +17,9 @@ import java.io.Serializable; import org.eclipse.gef4.geometry.Angle; import org.eclipse.gef4.geometry.Point; -import org.eclipse.gef4.geometry.utils.CurveUtils; +import org.eclipse.gef4.geometry.planar.Line; +import org.eclipse.gef4.geometry.projective.Straight3D; +import org.eclipse.gef4.geometry.projective.Vector3D; import org.eclipse.gef4.geometry.utils.PrecisionUtils; /** @@ -58,6 +60,16 @@ public class Straight implements Cloneable, Serializable { this(new Vector(point1), new Vector(point1, point2)); } + /** + * Constructs a new {@link Straight} through the start and end {@link Point} + * of the given {@link Line}. + * + * @param line + */ + public Straight(Line line) { + this(line.getP1(), line.getP2()); + } + @Override public Straight clone() { return getCopy(); @@ -74,7 +86,7 @@ public class Straight implements Cloneable, Serializable { */ public boolean intersects(Straight other) { return !PrecisionUtils.equal(direction.getDotProduct(other.direction - .getOrthogonalComplement()), 0); + .getOrthogonalComplement()), 0, +6); } /** @@ -128,11 +140,16 @@ public class Straight implements Cloneable, Serializable { * if no intersection point exists (or the Straights are equal). */ public Vector getIntersection(Straight other) { - Point poi = CurveUtils.getIntersection(this, other); - if (poi != null) { - return new Vector(poi); - } - return null; + Vector3D l1 = new Vector3D(this.position.toPoint()) + .getCrossed(new Vector3D(this.position.getAdded(this.direction) + .toPoint())); + Vector3D l2 = new Vector3D(other.position.toPoint()) + .getCrossed(new Vector3D(other.position.getAdded( + other.direction).toPoint())); + + Point poi = l1.getCrossed(l2).toPoint(); + + return poi == null ? null : new Vector(poi); } /** @@ -232,20 +249,26 @@ public class Straight implements Cloneable, Serializable { * @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 angleCW = direction.getAngleCW(d); - - if (angleCW.equals(Angle.fromDeg(90))) { - len = -len; - } - } - - return len; + // TODO: check which implementation is better + + return Straight.getSignedDistanceCCW(this.position.toPoint(), + this.position.getAdded(this.direction).toPoint(), + vector.toPoint()); + + // Vector projected = getProjection(vector); + // Vector d = vector.getSubtracted(projected); + // + // double len = d.getLength(); + // + // if (!d.isNull()) { + // Angle angleCW = direction.getAngleCW(d); + // + // if (angleCW.equals(Angle.fromDeg(90))) { + // len = -len; + // } + // } + // + // return len; } /** @@ -280,7 +303,7 @@ public class Straight implements Cloneable, Serializable { if (direction.y != 0) { return (p.y - position.y) / direction.y; } - return 0; + return 0; // never get here } /** @@ -419,4 +442,33 @@ public class Straight implements Cloneable, Serializable { return new Straight(position, direction); } + /** + * Computes the signed distance of the third {@link Point} to the line + * through the first two {@link Point}s. + * + * The signed distance is positive if the three {@link Point}s are in + * counter-clockwise order and negative if the {@link Point}s are in + * clockwise order. It is zero if the third {@link Point} lies on the line. + * + * If the first two {@link Point}s do not form a line (i.e. they are equal) + * this function returns the distance of the first and the last + * {@link Point}. + * + * @param p + * the start-{@link Point} of the line + * @param q + * the end-{@link Point} of the line + * @param r + * the relative {@link Point} to test for + * @return the signed distance of {@link Point} r to the line through + * {@link Point}s p and q + */ + public static double getSignedDistanceCCW(Point p, Point q, Point r) { + Straight3D line = Straight3D.through(new Vector3D(p), new Vector3D(q)); + if (line == null) { + return 0d; + } + return -line.getSignedDistanceCW(new Vector3D(r)); + } + } \ No newline at end of file diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/euclidean/Vector.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/euclidean/Vector.java index 090cbea..43a7ee0 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/euclidean/Vector.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/euclidean/Vector.java @@ -113,7 +113,7 @@ public class Vector implements Cloneable, Serializable { /** * Calculates the magnitude of the cross product of this {@link Vector} with - * another. Normalized the {@link Vector}s before calculating the cross + * another. Normalizes the {@link Vector}s before calculating the cross * product. Represents the amount by which two {@link Vector}s are * directionally different. Parallel {@link Vector}s return a value of 0. * @@ -135,7 +135,9 @@ public class Vector implements Cloneable, Serializable { * otherwise. */ public boolean isParallelTo(Vector other) { - return PrecisionUtils.equal(getDissimilarity(other), 0); + Angle alpha = getAngle(other); + alpha.setRad(2d * alpha.rad()); + return alpha.equals(Angle.fromRad(0d)); } /** diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractGeometry.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractGeometry.java index 48953f0..921d2e1 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractGeometry.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractGeometry.java @@ -22,7 +22,7 @@ abstract class AbstractGeometry implements IGeometry { * {@link Cloneable}. */ @Override - public final Object clone() { + public Object clone() { return getCopy(); } @@ -44,7 +44,36 @@ abstract class AbstractGeometry implements IGeometry { * @return a transformed {@link Path} representation of this * {@link IGeometry} */ - public IGeometry getTransformed(AffineTransform t) { + public IGeometry getTransformed(final AffineTransform t) { return toPath().getTransformed(t); } + + public boolean touches(final IGeometry g) { + if (this instanceof ICurve) { + if (g instanceof ICurve) { + return ((ICurve) this).intersects((ICurve) g) + || ((ICurve) this).overlaps((ICurve) g); + } else if (g instanceof IShape) { + return ((IShape) g).contains((ICurve) this) + || this.touches(((IShape) g).getOutline()); + } else { + throw new UnsupportedOperationException("Not yet implemented."); + } + } else if (this instanceof IShape) { + if (g instanceof ICurve) { + return ((IShape) this).contains((ICurve) g) + || ((IShape) this).getOutline().touches((ICurve) g); + } else if (g instanceof IShape) { + return ((IShape) this).contains((IShape) g) + || ((IShape) g).contains((IShape) this) + || ((IShape) this).getOutline().touches( + ((IShape) g).getOutline()); + } else { + throw new UnsupportedOperationException("Not yet implemented."); + } + } else { + throw new UnsupportedOperationException("Not yet implemented."); + } + } + } diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractPointListBasedGeometry.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractPointListBasedGeometry.java index d289382..8e74ed7 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractPointListBasedGeometry.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractPointListBasedGeometry.java @@ -7,23 +7,23 @@ * * Contributors: * Alexander Nyßen (itemis AG) - initial API and implementation + * Matthias Wienand (itemis AG) - contribution for Bugzilla #355997 * *******************************************************************************/ package org.eclipse.gef4.geometry.planar; +import org.eclipse.gef4.geometry.Angle; import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.euclidean.Vector; import org.eclipse.gef4.geometry.utils.PointListUtils; -abstract class AbstractPointListBasedGeometry extends AbstractGeometry { +abstract class AbstractPointListBasedGeometry> + extends AbstractGeometry { private static final long serialVersionUID = 1L; Point[] points; - public AbstractPointListBasedGeometry(Point... points) { - this.points = PointListUtils.getCopy(points); - } - public AbstractPointListBasedGeometry(double... coordinates) { points = new Point[coordinates.length / 2]; for (int i = 0; i < coordinates.length / 2; i++) { @@ -31,6 +31,10 @@ abstract class AbstractPointListBasedGeometry extends AbstractGeometry { } } + public AbstractPointListBasedGeometry(Point... points) { + this.points = PointListUtils.copy(points); + } + /** * @see IGeometry#getBounds() */ @@ -39,6 +43,42 @@ abstract class AbstractPointListBasedGeometry extends AbstractGeometry { } /** + * Computes the centroid of this {@link AbstractPointListBasedGeometry}. The + * centroid is the "center of gravity", i.e. assuming the {@link Polygon} + * which is spanned by the {@link Point}s of this + * {@link AbstractPointListBasedGeometry} is made of a material of constant + * density, it is in a balanced state, if you put it on a pin that is placed + * exactly on its centroid. + * + * @return the center {@link Point} (or centroid) of this + * {@link AbstractPointListBasedGeometry} + */ + public Point getCentroid() { + if (points.length == 0) { + return null; + } else if (points.length == 1) { + return points[0].getCopy(); + } + + double cx = 0, cy = 0, a, sa = 0; + for (int i = 0; i < points.length - 1; i++) { + a = points[i].x * points[i + 1].y - points[i].y * points[i + 1].x; + sa += a; + cx += (points[i].x + points[i + 1].x) * a; + cy += (points[i].y + points[i + 1].y) * a; + } + + // closing segment + a = points[points.length - 2].x * points[points.length - 1].y + - points[points.length - 2].y * points[points.length - 1].x; + sa += a; + cx += (points[points.length - 2].x + points[points.length - 1].x) * a; + cy += (points[points.length - 2].x + points[points.length - 1].x) * a; + + return new Point(cx / (3 * sa), cy / (3 * sa)); + } + + /** * Returns a double array, which represents the sequence of coordinates of * the {@link Point}s that make up this {@link Polygon}. * @@ -58,7 +98,255 @@ abstract class AbstractPointListBasedGeometry extends AbstractGeometry { * this {@link Polygon} */ public final Point[] getPoints() { - return PointListUtils.getCopy(points); + return PointListUtils.copy(points); + } + + /** + * Returns a new {@link AbstractPointListBasedGeometry} which is rotated + * counter-clock-wise by the given {@link Angle} around its centroid (see + * {@link #getCentroid()}). + * + * @see #getCopy() + * @see #getRotatedCCW(Angle, Point) + * @param alpha + * The rotation {@link Angle} + * @return The new rotated {@link AbstractPointListBasedGeometry} + */ + public T getRotatedCCW(Angle alpha) { + return getRotatedCCW(alpha, getCentroid()); + } + + /** + * Returns a new {@link AbstractPointListBasedGeometry} which is rotated + * counter-clock-wise by the given {@link Angle} around the given + * {@link Point}. + * + * @see #getCopy() + * @see #rotateCW(Angle, Point) + * @param alpha + * The rotation {@link Angle} + * @param center + * The {@link Point} to rotate around + * @return The new rotated {@link AbstractPointListBasedGeometry} + */ + @SuppressWarnings("unchecked") + public T getRotatedCCW(Angle alpha, Point center) { + return (T) ((T) getCopy()).rotateCCW(alpha, center); + } + + /** + * Returns a new {@link AbstractPointListBasedGeometry} which is rotated + * clock-wise by the given {@link Angle} around its centroid (see + * {@link #getCentroid()}). + * + * @see #getCopy() + * @see #getRotatedCW(Angle, Point) + * @param alpha + * The rotation {@link Angle} + * @return The new rotated {@link AbstractPointListBasedGeometry} + */ + public T getRotatedCW(Angle alpha) { + return getRotatedCW(alpha, getCentroid()); + } + + /** + * Returns a new {@link AbstractPointListBasedGeometry} which is rotated + * clock-wise by the given {@link Angle} around the given {@link Point}. + * + * @see #getCopy() + * @see #rotateCW(Angle, Point) + * @param alpha + * The rotation {@link Angle} + * @param center + * The {@link Point} to rotate around + * @return The new rotated {@link AbstractPointListBasedGeometry} + */ + @SuppressWarnings("unchecked") + public T getRotatedCW(Angle alpha, Point center) { + return (T) ((T) getCopy()).rotateCW(alpha, center); + } + + /** + * Returns a new {@link AbstractPointListBasedGeometry} which is scaled by + * the given factor. The {@link AbstractPointListBasedGeometry} is + * translated by the negated centroid (see {@link #getCentroid()}) first. + * The translation is reversed afterwards. + * + * @param factor + * The scale-factor + * @return The new scaled {@link AbstractPointListBasedGeometry} + * @see #getScaled(double, Point) + */ + @SuppressWarnings("unchecked") + public T getScaled(double factor) { + return (T) ((T) getCopy()).scale(factor); + } + + @SuppressWarnings("unchecked") + public T getScaled(double factorX, double factorY) { + return (T) ((T) getCopy()).scale(factorX, factorY); + } + + @SuppressWarnings("unchecked") + public T getScaled(double factorX, double factorY, Point center) { + return (T) ((T) getCopy()).scale(factorX, factorY, center); + } + + @SuppressWarnings("unchecked") + public T getScaled(double factor, Point center) { + return (T) ((T) getCopy()).scale(factor, center); + } + + /** + * Returns a new {@link AbstractPointListBasedGeometry} which is shifted + * along each axis by the passed values. + * + * @param dx + * Displacement along X axis + * @param dy + * Displacement along Y axis + * @return The new translated {@link AbstractPointListBasedGeometry} + */ + @SuppressWarnings("unchecked") + public T getTranslated(double dx, double dy) { + return (T) ((T) getCopy()).translate(dx, dy); + } + + /** + * Returns a new {@link AbstractPointListBasedGeometry} which is shifted by + * the position of the given {@link Point}. + * + * @param pt + * {@link Point} providing the amount of shift along each axis + * @return The new translated {@link AbstractPointListBasedGeometry} + */ + @SuppressWarnings("unchecked") + public T getTranslated(Point pt) { + return (T) ((T) getCopy()).translate(pt); + } + + /** + * Rotates this {@link AbstractPointListBasedGeometry} counter-clock-wise by + * the given {@link Angle} around its centroid (see {@link #getCentroid()}). + * + * @param alpha + * The rotation {@link Angle} + * @return this for convenience + * @see #rotateCCW(Angle, Point) + */ + public T rotateCCW(Angle alpha) { + return rotateCCW(alpha, getCentroid()); + } + + /** + * Rotates this {@link AbstractPointListBasedGeometry} counter-clock-wise by + * the given {@link Angle} around the given {@link Point}. + * + * The rotation is done by + *
    + *
  1. translating this {@link AbstractPointListBasedGeometry} by the + * negated {@link Point} center
  2. + *
  3. rotating each {@link Point} of this + * {@link AbstractPointListBasedGeometry} counter-clock-wise by the given + * {@link Angle}
  4. + *
  5. translating this {@link AbstractPointListBasedGeometry} back by the + * {@link Point} center
  6. + *
+ * + * @param alpha + * The rotation {@link Angle}. + * @param center + * The {@link Point} to rotate around. + * @return this for convenience + */ + @SuppressWarnings("unchecked") + public T 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 (T) this; + } + + /** + * Rotates this {@link AbstractPointListBasedGeometry} clock-wise by the + * given {@link Angle} around its centroid (see {@link #getCentroid()}). + * + * @param alpha + * The rotation {@link Angle} + * @return this for convenience + * @see #rotateCW(Angle, Point) + */ + public T rotateCW(Angle alpha) { + return (T) rotateCW(alpha, getCentroid()); + } + + /** + * Rotates this {@link AbstractPointListBasedGeometry} clock-wise by the + * given {@link Angle} around the given {@link Point}. + * + * The rotation is done by + *
    + *
  1. translating this {@link AbstractPointListBasedGeometry} by the + * negated {@link Point} center
  2. + *
  3. rotating each {@link Point} of this + * {@link AbstractPointListBasedGeometry} clock-wise by the given + * {@link Angle}
  4. + *
  5. translating this {@link AbstractPointListBasedGeometry} back by the + * {@link Point} center
  6. + *
+ * + * @param alpha + * The rotation {@link Angle} + * @param center + * The {@link Point} to rotate around + * @return this for convenience + */ + @SuppressWarnings("unchecked") + public T 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 (T) this; + } + + /** + * Scales this {@link AbstractPointListBasedGeometry} by the given factor. + * The {@link AbstractPointListBasedGeometry} is translated by its negated + * centroid (see {@link #getCentroid()}) first. The translation is reversed + * afterwards. + * + * @see #scale(double, Point) + * @param factor + * @return this for convenience + */ + public T scale(double factor) { + return scale(factor, factor); + } + + public T scale(double factorX, double factorY) { + return scale(factorX, factorY, getCentroid()); + } + + @SuppressWarnings("unchecked") + public T scale(double factorX, double factorY, Point center) { + for (Point p : points) { + Point np = p.getScaled(factorX, factorY, center); + p.x = np.x; + p.y = np.y; + } + return (T) this; + } + + public T scale(double factor, Point center) { + return scale(factor, factor, center); } /** @@ -72,4 +360,36 @@ abstract class AbstractPointListBasedGeometry extends AbstractGeometry { return PointListUtils.toIntegerArray(PointListUtils .toCoordinatesArray(points)); } + + /** + * Moves this {@link AbstractPointListBasedGeometry} horizontally by dx and + * vertically by dy, then returns this + * {@link AbstractPointListBasedGeometry} for convenience. + * + * @param dx + * Shift along X axis + * @param dy + * Shift along Y axis + * @return this for convenience + */ + @SuppressWarnings("unchecked") + public T translate(double dx, double dy) { + PointListUtils.translate(points, dx, dy); + return (T) this; + } + + /** + * Moves this {@link AbstractPointListBasedGeometry} horizontally by the x + * value of the given {@link Point} and vertically by the y value of the + * given {@link Point}, then returns this + * {@link AbstractPointListBasedGeometry} for convenience. + * + * @param p + * {@link Point} which provides translation information + * @return this for convenience + */ + public T translate(Point p) { + return (T) translate(p.x, p.y); + } + } \ No newline at end of file diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractRectangleBasedGeometry.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractRectangleBasedGeometry.java index 1fdd18a..1e7fba8 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractRectangleBasedGeometry.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractRectangleBasedGeometry.java @@ -7,6 +7,7 @@ * * Contributors: * Alexander Nyßen (itemis AG) - initial API and implementation + * Matthias Wienand (itemis AG) - contribution for Bugzilla #355997 * *******************************************************************************/ package org.eclipse.gef4.geometry.planar; @@ -21,7 +22,8 @@ import org.eclipse.gef4.geometry.Point; * @author anyssen * */ -abstract class AbstractRectangleBasedGeometry extends AbstractGeometry { +abstract class AbstractRectangleBasedGeometry> + extends AbstractGeometry { private static final long serialVersionUID = 1L; @@ -38,13 +40,17 @@ abstract class AbstractRectangleBasedGeometry extends AbstractGeometry { return new Rectangle(x, y, width, height); } + public Point getCentroid() { + return new Point(x + width / 2, y + height / 2); + } + public final double getHeight() { return height; } /** - * Returns the location of this {@link AbstractRectangleBasedGeometry}, which is the - * location of its bounds. + * Returns the location of this {@link AbstractRectangleBasedGeometry}, + * which is the location of its bounds. * * @return a {@link Point} representing the location of this * {@link AbstractRectangleBasedGeometry} 's bounds @@ -54,6 +60,37 @@ abstract class AbstractRectangleBasedGeometry extends AbstractGeometry { } /** + * Returns a new {@link AbstractPointListBasedGeometry} which is scaled by + * the given factor. The {@link AbstractPointListBasedGeometry} is + * translated by the negated centroid (see {@link #getCentroid()}) first. + * The translation is reversed afterwards. + * + * @param factor + * The scale-factor + * @return The new scaled {@link AbstractPointListBasedGeometry} + * @see #getScaled(double, Point) + */ + @SuppressWarnings("unchecked") + public T getScaled(double factor) { + return (T) ((T) getCopy()).scale(factor); + } + + @SuppressWarnings("unchecked") + public T getScaled(double factorX, double factorY) { + return (T) ((T) getCopy()).scale(factorX, factorY); + } + + @SuppressWarnings("unchecked") + public T getScaled(double factorX, double factorY, Point center) { + return (T) ((T) getCopy()).scale(factorX, factorY, center); + } + + @SuppressWarnings("unchecked") + public T getScaled(double factor, Point center) { + return (T) ((T) getCopy()).scale(factor, center); + } + + /** * Returns the size of this {@link Rectangle}. * * @return The current size @@ -62,6 +99,34 @@ abstract class AbstractRectangleBasedGeometry extends AbstractGeometry { return new Dimension(width, height); } + /** + * Returns a new {@link AbstractPointListBasedGeometry} which is shifted + * along each axis by the passed values. + * + * @param dx + * Displacement along X axis + * @param dy + * Displacement along Y axis + * @return The new translated {@link AbstractPointListBasedGeometry} + */ + @SuppressWarnings("unchecked") + public T getTranslated(double dx, double dy) { + return (T) ((T) getCopy()).translate(dx, dy); + } + + /** + * Returns a new {@link AbstractPointListBasedGeometry} which is shifted by + * the position of the given {@link Point}. + * + * @param pt + * {@link Point} providing the amount of shift along each axis + * @return The new translated {@link AbstractPointListBasedGeometry} + */ + @SuppressWarnings("unchecked") + public T getTranslated(Point pt) { + return (T) ((T) getCopy()).translate(pt); + } + public final double getWidth() { return width; } @@ -75,6 +140,39 @@ abstract class AbstractRectangleBasedGeometry extends AbstractGeometry { } /** + * Scales this {@link AbstractPointListBasedGeometry} by the given factor. + * The {@link AbstractPointListBasedGeometry} is translated by its negated + * centroid (see {@link #getCentroid()}) first. The translation is reversed + * afterwards. + * + * @see #scale(double, Point) + * @param factor + * @return this for convenience + */ + public T scale(double factor) { + return scale(factor, factor); + } + + public T scale(double factorX, double factorY) { + return scale(factorX, factorY, getCentroid()); + } + + @SuppressWarnings("unchecked") + public T scale(double factorX, double factorY, Point center) { + double nx = (x - center.x) * factorX + center.x; + double ny = (y - center.y) * factorY + center.y; + width = (x + width - center.x) * factorX + center.x - nx; + height = (y + height - center.y) * factorY + center.y - ny; + x = nx; + y = ny; + return (T) this; + } + + public T scale(double factor, Point center) { + return scale(factor, factor, center); + } + + /** * Sets the x, y, width and height values of this {@link Rectangle} to match * those that are given. * @@ -86,12 +184,15 @@ abstract class AbstractRectangleBasedGeometry extends AbstractGeometry { * the new width * @param h * the new height + * @return this for convenience */ - public final void setBounds(double x, double y, double w, double h) { + @SuppressWarnings("unchecked") + public final T setBounds(double x, double y, double w, double h) { this.x = x; this.y = y; this.width = w; this.height = h; + return (T) this; } /** @@ -101,9 +202,12 @@ abstract class AbstractRectangleBasedGeometry extends AbstractGeometry { * The new location * @param size * The new size + * @return this for convenience */ - public final void setBounds(Point loc, Dimension size) { + @SuppressWarnings("unchecked") + public final T setBounds(Point loc, Dimension size) { setBounds(loc.x, loc.y, size.width, size.height); + return (T) this; } /** @@ -113,16 +217,21 @@ abstract class AbstractRectangleBasedGeometry extends AbstractGeometry { * @param r * The {@link Rectangle} whose location and size are to be * transferred. + * @return this for convenience */ - public final void setBounds(Rectangle r) { + @SuppressWarnings("unchecked") + public final T setBounds(Rectangle r) { setBounds(r.x, r.y, r.width, r.height); + return (T) this; } - public final void setHeight(double height) { + @SuppressWarnings("unchecked") + public final T setHeight(double height) { if (height < 0) { height = 0; } this.height = height; + return (T) this; } /** @@ -133,10 +242,13 @@ abstract class AbstractRectangleBasedGeometry extends AbstractGeometry { * The new x-coordinate * @param y * The new y-coordinate + * @return this for convenience */ - public final void setLocation(double x, double y) { + @SuppressWarnings("unchecked") + public final T setLocation(double x, double y) { this.x = x; this.y = y; + return (T) this; } /** @@ -145,9 +257,12 @@ abstract class AbstractRectangleBasedGeometry extends AbstractGeometry { * * @param p * the new location of this Rectangle + * @return this for convenience */ - public final void setLocation(Point p) { + @SuppressWarnings("unchecked") + public final T setLocation(Point p) { setLocation(p.x, p.y); + return (T) this; } /** @@ -156,9 +271,12 @@ abstract class AbstractRectangleBasedGeometry extends AbstractGeometry { * * @param d * the new size + * @return this for convenience */ - public final void setSize(Dimension d) { + @SuppressWarnings("unchecked") + public final T setSize(Dimension d) { setSize(d.width, d.height); + return (T) this; } /** @@ -169,8 +287,10 @@ abstract class AbstractRectangleBasedGeometry extends AbstractGeometry { * The new width * @param h * The new height + * @return this for convenience */ - public final void setSize(double w, double h) { + @SuppressWarnings("unchecked") + public final T setSize(double w, double h) { if (w < 0) { w = 0; } @@ -179,21 +299,60 @@ abstract class AbstractRectangleBasedGeometry extends AbstractGeometry { } width = w; height = h; + return (T) this; } - public final void setWidth(double width) { + @SuppressWarnings("unchecked") + public final T setWidth(double width) { if (width < 0) { width = 0; } this.width = width; + return (T) this; } - public final void setX(double x) { + @SuppressWarnings("unchecked") + public final T setX(double x) { this.x = x; + return (T) this; } - public final void setY(double y) { + @SuppressWarnings("unchecked") + public final T setY(double y) { this.y = y; + return (T) this; + } + + /** + * Moves this {@link AbstractPointListBasedGeometry} horizontally by dx and + * vertically by dy, then returns this + * {@link AbstractPointListBasedGeometry} for convenience. + * + * @param dx + * Shift along X axis + * @param dy + * Shift along Y axis + * @return this for convenience + */ + @SuppressWarnings("unchecked") + public T translate(double dx, double dy) { + x += dx; + y += dy; + return (T) this; + } + + /** + * Moves this {@link AbstractPointListBasedGeometry} horizontally by the x + * value of the given {@link Point} and vertically by the y value of the + * given {@link Point}, then returns this + * {@link AbstractPointListBasedGeometry} for convenience. + * + * @param p + * {@link Point} which provides translation information + * @return this for convenience + */ + public T translate(Point p) { + return (T) translate(p.x, p.y); } } \ No newline at end of file diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Arc.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Arc.java index 50f1b14..de25471 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Arc.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Arc.java @@ -12,12 +12,11 @@ package org.eclipse.gef4.geometry.planar; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; import java.util.List; import org.eclipse.gef4.geometry.Angle; import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.utils.CurveUtils; /** * Represents the geometric shape of an arc, which is defined by its enclosing @@ -27,7 +26,8 @@ import org.eclipse.gef4.geometry.Point; * @author anyssen * */ -public final class Arc extends AbstractRectangleBasedGeometry implements ICurve { +public final class Arc extends AbstractRectangleBasedGeometry implements + ICurve { private static final long serialVersionUID = 1L; @@ -108,178 +108,116 @@ public final class Arc extends AbstractRectangleBasedGeometry implements ICurve } /** - * @see IGeometry#contains(Rectangle) + * Returns the extension {@link Angle} of this {@link Arc}, i.e. the + * {@link Angle} defining the span of the {@link Arc}. + * + * @return the extension {@link Angle} of this {@link Arc} */ - public boolean contains(Rectangle r) { - return false; - } - public Angle getAngularExtent() { return angularExtent; } /** - * Returns the points of intersection between this {@link Arc} and the given - * other {@link Arc}. - * - * @param other - * The {@link Arc} to test for intersections - * @return the points of intersection. + * @see org.eclipse.gef4.geometry.planar.IGeometry#getCopy() */ - public Point[] getIntersections(Arc other) { - if (equals(other)) { - return new Point[] {}; - } - - HashSet intersections = new HashSet(); - - for (CubicCurve seg : getSegments()) { - intersections.addAll(Arrays.asList(getIntersections(seg))); - } + public Arc getCopy() { + return new Arc(x, y, width, height, startAngle, angularExtent); + } - return intersections.toArray(new Point[] {}); + public Point[] getIntersections(ICurve g) { + return CurveUtils.getIntersections(this, g); } /** - * Returns the points of intersection between this {@link Arc} and the given - * {@link CubicCurve}. + * Returns a {@link Point} representing the start point of this {@link Arc}. * - * @param c - * The {@link CubicCurve} to test for intersections - * @return the points of intersection. + * @return the start {@link Point} of this {@link Arc} */ - public Point[] getIntersections(CubicCurve c) { - return c.getIntersections(this); + public Point getP1() { + return getPoint(Angle.fromRad(0)); } - /** - * Returns the points of intersection between this {@link Arc} and the given - * {@link Ellipse}. - * - * @param e - * The {@link Ellipse} to test for intersections - * @return the points of intersection. - */ - public Point[] getIntersections(Ellipse e) { - return e.getIntersections(this); + public Point getP2() { + return getPoint(angularExtent); } /** - * Returns the points of intersection between this {@link Arc} and the given - * {@link Line}. + * Computes a {@link Point} on this {@link Arc}. The {@link Point}'s + * coordinates are calculated by moving the given {@link Angle} on the + * {@link Arc} starting at the {@link Arc} start {@link Point}. * - * @param l - * The {@link Line} to test for intersections - * @return the points of intersection. + * @param angularExtent + * @return the {@link Point} at the given {@link Angle} */ - public Point[] getIntersections(Line l) { - HashSet intersections = new HashSet(); - - for (CubicCurve seg : getSegments()) { - intersections.addAll(Arrays.asList(seg.getIntersections(l))); - } + public Point getPoint(Angle angularExtent) { + double a = width / 2; + double b = height / 2; - return intersections.toArray(new Point[] {}); + // // calculate start and end points of the arc from start to end + return new Point(x + a + a + * Math.cos(startAngle.rad() + angularExtent.rad()), y + b - b + * Math.sin(startAngle.rad() + angularExtent.rad())); } /** - * Returns the points of intersection between this {@link Arc} and the given - * {@link Polygon}. + * Returns this {@link Arc}'s start {@link Angle}. * - * @param p - * The {@link Polygon} to test for intersections - * @return the points of intersection. + * @return this {@link Arc}'s start {@link Angle} */ - public Point[] getIntersections(Polygon p) { - return p.getIntersections(this); + public Angle getStartAngle() { + return startAngle; } - /** - * Returns the points of intersection between this {@link Arc} and the given - * {@link Polyline}. - * - * @param p - * The {@link Polyline} to test for intersections - * @return the points of intersection. - */ - public Point[] getIntersections(Polyline p) { - HashSet intersections = new HashSet(); + public double getX1() { + return getP1().x; + } - for (CubicCurve seg : getSegments()) { - intersections.addAll(Arrays.asList(seg.getIntersections(p))); - } + public double getX2() { + return getP2().x; + } - return intersections.toArray(new Point[] {}); + public double getY1() { + return getP1().y; } - /** - * Returns the points of intersection between this {@link Arc} and the given - * {@link QuadraticCurve}. - * - * @param c - * The {@link QuadraticCurve} to test for intersections - * @return the points of intersection. - */ - public Point[] getIntersections(QuadraticCurve c) { - HashSet intersections = new HashSet(); + public double getY2() { + return getP2().y; + } - for (CubicCurve seg : getSegments()) { - intersections.addAll(Arrays.asList(seg.getIntersections(c))); - } + public boolean intersects(ICurve c) { + return CurveUtils.getIntersections(this, c).length > 0; + } - return intersections.toArray(new Point[] {}); + public boolean overlaps(ICurve c) { + for (BezierCurve seg1 : toBezier()) { + if (seg1.overlaps(c)) { + return true; + } + } + return false; } /** - * Returns the points of intersection between this {@link Arc} and the given - * {@link Rectangle}. + * Sets the extension {@link Angle} of this {@link Arc}. * - * @param r - * The {@link Rectangle} to test for intersections - * @return the points of intersection. + * @param angularExtent + * the new extension {@link Angle} for this {@link Arc} */ - public Point[] getIntersections(Rectangle r) { - HashSet intersections = new HashSet(); - - for (CubicCurve seg : getSegments()) { - intersections.addAll(Arrays.asList(seg.getIntersections(r))); - } - - return intersections.toArray(new Point[] {}); + public void setAngularExtent(Angle angularExtent) { + this.angularExtent = angularExtent; } /** - * Returns the points of intersection between this {@link Arc} and the given - * {@link RoundedRectangle}. + * Sets the start {@link Angle} of this {@link Arc}. * - * @param r - * The {@link RoundedRectangle} to test for intersections - * @return the points of intersection. + * @param startAngle + * the new start {@link Angle} for this {@link Arc} */ - public Point[] getIntersections(RoundedRectangle r) { - HashSet intersections = new HashSet(); - - // line segments - intersections.addAll(Arrays.asList(getIntersections(r.getTop()))); - intersections.addAll(Arrays.asList(getIntersections(r.getLeft()))); - intersections.addAll(Arrays.asList(getIntersections(r.getBottom()))); - intersections.addAll(Arrays.asList(getIntersections(r.getRight()))); - - // arc segments - intersections - .addAll(Arrays.asList(getIntersections(r.getTopRightArc()))); - intersections - .addAll(Arrays.asList(getIntersections(r.getTopLeftArc()))); - intersections.addAll(Arrays.asList(getIntersections(r - .getBottomLeftArc()))); - intersections.addAll(Arrays.asList(getIntersections(r - .getBottomRightArc()))); - - return intersections.toArray(new Point[] {}); + public void setStartAngle(Angle startAngle) { + this.startAngle = startAngle; } - // TODO: rename this method... - public CubicCurve[] getSegments() { + public CubicCurve[] toBezier() { double start = getStartAngle().rad(); double end = getStartAngle().rad() + getAngularExtent().rad(); @@ -317,75 +255,10 @@ public final class Arc extends AbstractRectangleBasedGeometry implements ICurve return segments.toArray(new CubicCurve[] {}); } - public Angle getStartAngle() { - return startAngle; - } - - public Point getPoint(Angle angularExtent) { - double a = width / 2; - double b = height / 2; - - // // calculate start and end points of the arc from start to end - return new Point(x + a + a - * Math.cos(startAngle.rad() + angularExtent.rad()), y + b - b - * Math.sin(startAngle.rad() + angularExtent.rad())); - } - - /** - * Returns a {@link Point} representing the start point of this {@link Arc}. - * - * @return - */ - public Point getP1() { - return getPoint(Angle.fromRad(0)); - } - - public Point getP2() { - return getPoint(angularExtent); - } - - /** - * @see IGeometry#intersects(Rectangle) - */ - public boolean intersects(Rectangle r) { - throw new UnsupportedOperationException(); - } - - public void setAngularExtent(Angle angularExtent) { - this.angularExtent = angularExtent; - } - - public void setStartAngle(Angle startAngle) { - this.startAngle = startAngle; - } - /** * @see IGeometry#toPath() */ public Path toPath() { - return toPath(getSegments()); - } - - /** - * @see org.eclipse.gef4.geometry.planar.IGeometry#getCopy() - */ - public Arc getCopy() { - return new Arc(x, y, width, height, startAngle, angularExtent); - } - - public double getY2() { - return getP2().y; - } - - public double getY1() { - return getP1().y; - } - - public double getX2() { - return getP2().x; - } - - public double getX1() { - return getP1().x; + return toPath(toBezier()); } } diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/BezierCurve.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/BezierCurve.java index 9698653..efef00f 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/BezierCurve.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/BezierCurve.java @@ -7,12 +7,25 @@ * * Contributors: * Alexander Nyßen (itemis AG) - initial API and implementation + * Matthias Wienand (itemis AG) - contribution for Bugzilla #355997 * *******************************************************************************/ package org.eclipse.gef4.geometry.planar; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.Stack; + +import org.eclipse.gef4.geometry.Angle; import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.euclidean.Vector; +import org.eclipse.gef4.geometry.projective.Straight3D; +import org.eclipse.gef4.geometry.projective.Vector3D; import org.eclipse.gef4.geometry.utils.PointListUtils; +import org.eclipse.gef4.geometry.utils.PrecisionUtils; /** * Abstract base class of Bezier Curves. @@ -23,258 +36,2062 @@ import org.eclipse.gef4.geometry.utils.PointListUtils; * @author anyssen * */ -public abstract class BezierCurve extends AbstractGeometry implements ICurve { +public class BezierCurve extends AbstractGeometry implements ICurve { - double x1; - double y1; - double x2; - double y2; + private static final long serialVersionUID = 1L; - // TODO: use point array instead - double[] ctrlCoordinates = null; + private static class FatLine { + public static FatLine from(BezierCurve c, boolean ortho) { + FatLine L = new FatLine(); + L.dmin = L.dmax = 0; - public BezierCurve(double... coordinates) { - if (coordinates.length < 4) { - throw new IllegalArgumentException( - "A bezier curve needs at least a start and an end point"); + L.line = Straight3D.through(c.points[0], + c.points[c.points.length - 1]); + if (L.line == null) { + return null; + } + + if (ortho) { + L.line = L.line.getOrtho(); + } + if (L.line == null) { + return null; + } + + for (int i = 0; i < c.points.length; i++) { + double d = L.line.getSignedDistanceCW(c.points[i]); + if (d < L.dmin) + L.dmin = d; + else if (d > L.dmax) + L.dmax = d; + } + + return L; } - this.x1 = coordinates[0]; - this.y1 = coordinates[1]; - this.x2 = coordinates[coordinates.length - 2]; - this.y2 = coordinates[coordinates.length - 1]; - if (coordinates.length > 4) { - this.ctrlCoordinates = new double[coordinates.length - 4]; - System.arraycopy(coordinates, 2, ctrlCoordinates, 0, - coordinates.length - 4); + + public Straight3D line; + + public double dmin, dmax; + + private FatLine() { + line = null; + dmin = dmax = 0; } } - public BezierCurve(Point... points) { - this(PointListUtils.toCoordinatesArray(points)); + /** + * Representation of an interval [a;b]. It is used to represent sub-curves + * of a {@link BezierCurve}. + * + * @author wienand + */ + public static final class Interval { + /** + * Constructs a new {@link Interval} object holding an invalid parameter + * interval. + * + * @return a new {@link Interval} object holding an invalid parameter + * interval + */ + public static Interval getEmpty() { + return new Interval(1, 0); + } + + /** + * Constructs a new {@link Interval} object holding the interval [0;1] + * which is the parameter interval representing a full + * {@link BezierCurve}. + * + * @return a new {@link Interval} object holding the interval [0;1] + */ + public static Interval getFull() { + return new Interval(0, 1); + } + + /** + * Returns the smaller {@link Interval} object, i.e. the one with the + * smallest parameter range. + * + * @param i + * @param j + * @return the {@link Interval} with the smallest parameter range + */ + public static Interval min(Interval i, Interval j) { + return (i.b - i.a) > (j.b - j.a) ? j : i; + } + + /** + * An {@link Interval} holds the parameter range [a;b]. Valid parameter + * ranges require 0 <= a <= b <= 1. + */ + public double a; + + /** + * An {@link Interval} holds the parameter range [a;b]. Valid parameter + * ranges require 0 <= a <= b <= 1. + */ + public double b; + + /** + * Constructs a new {@link Interval} object from the given double + * values. Only the first two double values are of importance as the + * rest of them are ignored. + * + * The new {@link Interval} holds the parameter range [a;b] if a is the + * first double value and b is the second double value. + * + * @param ds + */ + public Interval(double... ds) { + if (ds.length > 1) { + a = ds[0]; + b = ds[1]; + } else { + throw new IllegalArgumentException( + "not enough values to create interval"); + } + } + + /** + * Tests if this {@link Interval}'s parameter range does converge with + * default imprecision. Returns true for a ~= b, + * false otherwise. + * + * @see Interval#converges(int) + * + * @return true if a ~= b (default imprecision), + * false otherwise + */ + public boolean converges() { + return converges(0); + } + + /** + * Tests if this {@link Interval}'s parameter range does converge with + * specified imprecision. Returns true for a ~= b, + * false otherwise. + * + * The imprecision is specified by providing a shift value which shifts + * the epsilon used for the number comparison. A positive shift demands + * for a smaller epsilon (higher precision) whereas a negative shift + * demands for a greater epsilon (lower precision). + * + * @param shift + * precision shift + * @return true if a ~= b (specified imprecision), + * false otherwise + */ + public boolean converges(int shift) { + return PrecisionUtils.equal(a, b, shift); + } + + /** + * Returns a copy of this {@link Interval}. + * + * @return a copy of this {@link Interval} + */ + public Interval getCopy() { + return new Interval(a, b); + } + + /** + * Returns the middle parameter value m = (a+b)/2 of this + * {@link Interval}. + * + * @return the middle parameter value of this {@link Interval} + */ + public double getMid() { + return (a + b) / 2; + } + + /** + * Scales this {@link Interval} to the given {@link Interval}. The given + * {@link Interval} specifies the new upper and lower bounds of this + * {@link Interval} in percent. + * + * Returns the ratio of this {@link Interval}'s new parameter range to + * its old parameter range. + * + * @param interval + * the new upper and lower bounds in percent + * @return the ratio of this {@link Interval}'s new parameter range to + * its old parameter range + */ + public double scaleTo(Interval interval) { + double na = a + interval.a * (b - a); + double nb = a + interval.b * (b - a); + double ratio = (nb - na) / (b - a); + a = na; + b = nb; + return ratio; + } } - public final boolean contains(Rectangle r) { - // TODO: may contain the rectangle only in case the rectangle is - // degenerated... - return false; + /** + * An {@link IntervalPair} combines two {@link BezierCurve}s and their + * corresponding parameter ranges. + * + * @author wienand + */ + public static final class IntervalPair { + /** + * The first {@link BezierCurve}. + */ + public BezierCurve p; + /** + * The second {@link BezierCurve}. + */ + public BezierCurve q; + /** + * The parameter {@link Interval} for the first {@link BezierCurve}. + */ + public Interval pi; + /** + * The parameter {@link Interval} for the second {@link BezierCurve}. + */ + public Interval qi; + + /** + * Constructs a new {@link IntervalPair} with the given + * {@link BezierCurve}s and their corresponding parameter ranges. + * + * @param pp + * the first {@link BezierCurve} + * @param pt + * the parameter {@link Interval} for the first + * {@link BezierCurve} + * @param pq + * the second {@link BezierCurve} + * @param pu + * the parameter {@link Interval} for the second + * {@link BezierCurve} + */ + public IntervalPair(BezierCurve pp, Interval pt, BezierCurve pq, + Interval pu) { + p = pp; + pi = pt; + q = pq; + qi = pu; + } + + /** + * Tests if both parameter {@link Interval}s do converge (@see + * Interval#converges()) or both {@link BezierCurve}s are degenerated, + * i.e. they are collapsed to a single {@link Point}. + * + * @return true if both parameter {@link Interval}s do converge, false + * otherwise + */ + public boolean converges() { + return converges(0); + } + + /** + * Tests if both parameter {@link Interval}s do converge (@see + * Interval#converges(int)) or both {@link BezierCurve}s are + * degenerated, i.e. they are collapsed to a single {@link Point}. + * + * @param shift + * the precision shift + * @return true if both parameter {@link Interval}s do converge, false + * otherwise + */ + public boolean converges(int shift) { + return (pi.converges(shift) || pointsEquals( + p.getHC(pi.a).toPoint(), p.getHC(pi.b).toPoint(), shift)) + && (qi.converges(shift) || pointsEquals(q.getHC(qi.a) + .toPoint(), q.getHC(qi.b).toPoint(), shift)); + // return pi.converges(shift) && qi.converges(shift); + } + + /** + * Returns a copy of this {@link IntervalPair}. The underlying + * {@link BezierCurve}s are only shallow copied. The corresponding + * parameter {@link Interval}s are truly copied. + * + * @return a copy of this {@link IntervalPair} + */ + public IntervalPair getCopy() { + return new IntervalPair(p, pi.getCopy(), q, qi.getCopy()); + } + + /** + * Returns the first sub-curve of this {@link IntervalPair}. This curve + * is the first {@link BezierCurve} p over its corresponding parameter + * {@link Interval} pi. + * + * @return the first sub-curve of this {@link IntervalPair} + */ + public BezierCurve getPClipped() { + return p.getClipped(pi.a, pi.b); + } + + /** + * Splits the first parameter {@link Interval} pi at half and returns + * the resulting {@link IntervalPair}s. + * + * @return two {@link IntervalPair}s representing a split of the first + * paramter {@link Interval} in half + */ + public IntervalPair[] getPSplit() { + double pm = (pi.a + pi.b) / 2; + return new IntervalPair[] { + new IntervalPair(p, new Interval(pi.a, pm), q, qi.getCopy()), + new IntervalPair(p, new Interval(pm + 10 + * UNRECOGNIZABLE_PRECISION_FRACTION, pi.b), q, + qi.getCopy()) }; + } + + /** + * Returns the second sub-curve of this {@link IntervalPair}. This curve + * is the second {@link BezierCurve} q over its corresponding parameter + * {@link Interval} qi. + * + * @return the second sub-curve of this {@link IntervalPair} + */ + public BezierCurve getQClipped() { + return q.getClipped(qi.a, qi.b); + } + + /** + * Splits the second parameter {@link Interval} qi at half and returns + * the resulting {@link IntervalPair}s. + * + * @return two {@link IntervalPair}s representing a split of the second + * paramter {@link Interval} in half + */ + public IntervalPair[] getQSplit() { + double qm = (qi.a + qi.b) / 2; + return new IntervalPair[] { + new IntervalPair(q, new Interval(qi.a, qm), p, pi.getCopy()), + new IntervalPair(q, new Interval(qm + 10 + * UNRECOGNIZABLE_PRECISION_FRACTION, qi.b), p, + pi.getCopy()) }; + } + + /** + * Creates a new {@link IntervalPair} with swapped {@link BezierCurve}s + * and their parameter {@link Interval}s. + * + * @return a new {@link IntervalPair} with swapped {@link BezierCurve}s + * and their parameter {@link Interval}s + */ + public IntervalPair getSwapped() { + return new IntervalPair(q, qi.getCopy(), p, pi.getCopy()); + } + + /** + * Calculates which {@link BezierCurve}'s parameter {@link Interval} is + * longer. Returns true if the distance from start + * parameter to end parameter of the frist parameter {@link Interval} pi + * is greater than the distance from start parameter to end parameter of + * the second parameter {@link Interval} qi. Otherwise, returns + * false. + * + * @return true if the distance from start to end parameter + * value of the first parameter {@link Interval} pi is greater + * than the distance from start to end parameter value of the + * second parameter {@link Interval} qi. Othwise, returns + * false. + */ + public boolean isPLonger() { + return (pi.b - pi.a) > (qi.b - qi.a); + } + } + + private interface IPointCmp { + public boolean pIsBetterThanQ(Point p, Point q); + } + + // TODO: use constants that limit the number of iterations for the + // different iterative/recursive algorithms: + // INTERSECTIONS_MAX_ITERATIONS, APPROXIMATION_MAX_ITERATIONS + + private static final int CHUNK_SHIFT = -3; + + private static final boolean ORTHOGONAL = true; + + private static final boolean PARALLEL = false; + + private static final double UNRECOGNIZABLE_PRECISION_FRACTION = PrecisionUtils + .calculateFraction(0) / 10; + + private static IntervalPair[] clusterChunks(IntervalPair[] intervalPairs, + int shift) { + List clusters = new ArrayList(); + + // TODO: do something intelligent instead! + boolean isCompletelyClustered = true; + + for (IntervalPair ip : intervalPairs) { + boolean isExpansion = false; + + for (IntervalPair cluster : clusters) { + if (isNextTo(cluster, ip, shift)) { + expand(cluster, ip); + isExpansion = true; + break; + } + } + + if (!isExpansion) { + clusters.add(ip); + } else { + isCompletelyClustered = false; + } + } + + IntervalPair[] clustersArray = clusters.toArray(new IntervalPair[] {}); + return isCompletelyClustered ? clustersArray : clusterChunks( + clustersArray, shift); + } + + private static void copyIntervalPair(IntervalPair a, IntervalPair b) { + a.p = b.p; + a.q = b.q; + a.pi = b.pi; + a.qi = b.qi; + } + + private static void expand(IntervalPair group, IntervalPair newcomer) { + if (group.pi.a > newcomer.pi.a) { + group.pi.a = newcomer.pi.a; + } + if (group.pi.b < newcomer.pi.b) { + group.pi.b = newcomer.pi.b; + } + if (group.qi.a > newcomer.qi.a) { + group.qi.a = newcomer.qi.a; + } + if (group.qi.b < newcomer.qi.b) { + group.qi.b = newcomer.qi.b; + } + } + + /** + * Returns the convex hull of the given {@link Vector3D}s. + * + * The {@link PointListUtils#getConvexHull(Point[])} method is used to + * calculate the convex hull. This method does only accept {@link Point} s + * for input. + * + * @param vectors + * @return + */ + private static Point[] getConvexHull(Vector3D[] vectors) { + Point[] points = new Point[vectors.length]; + for (int i = 0; i < vectors.length; i++) { + points[i] = vectors[i].toPoint(); + } + return PointListUtils.getConvexHull(points); + } + + /** + * Computes the intersection of the line from {@link Point} p to + * {@link Point} q with the x-axis-parallel line f(x) = y. + * + * There is always an intersection, because this routine is only called when + * either the lower or the higher fat line bound is crossed. + * + * The following conditions are fulfilled: (p.x!=q.x) and (p.y!=q.y) and + * (p.yy>q.y). + * + * From these values, one can build a function g(x) = m*x + b where + * m=(q.y-p.y)/(q.x-p.x) and b=p.y-m*p.x. + * + * The point of intersection is given by f(x) = g(x). The x-coordinate of + * this point is x = (y - b) / m. + * + * @param p + * The start point of the {@link Line} + * @param q + * The end point of the {@link Line} + * @param y + * The x-axis-parallel line f(x) = y + * @return the x coordinate of the intersection point. + */ + private static double intersectXAxisParallel(Point p, Point q, double y) { + double m = (q.y - p.y) / (q.x - p.x); + return (y - p.y + m * p.x) / m; + } + + private static boolean isNextTo(IntervalPair a, IntervalPair b, int shift) { + boolean isPNeighbour = PrecisionUtils.greaterEqual(a.pi.a, b.pi.a, + shift) + && PrecisionUtils.smallerEqual(a.pi.a, b.pi.b, shift) + || PrecisionUtils.smallerEqual(a.pi.a, b.pi.a, shift) + && PrecisionUtils.greaterEqual(a.pi.b, b.pi.a, shift); + boolean isQNeighbour = PrecisionUtils.greaterEqual(a.qi.a, b.qi.a, + shift) + && PrecisionUtils.smallerEqual(a.qi.a, b.qi.b, shift) + || PrecisionUtils.smallerEqual(a.qi.a, b.qi.a, shift) + && PrecisionUtils.greaterEqual(a.qi.b, b.qi.a, shift); + + return isPNeighbour && isQNeighbour; + } + + private static IntervalPair isOverlap( + IntervalPair[] intersectionCandidates, IntervalPair[] endPoints) { + // merge intersection candidates and end points + IntervalPair[] fineChunks = new IntervalPair[intersectionCandidates.length + + endPoints.length]; + for (int i = 0; i < intersectionCandidates.length; i++) { + fineChunks[i] = intersectionCandidates[i]; + } + for (int i = 0; i < endPoints.length; i++) { + fineChunks[intersectionCandidates.length + i] = endPoints[i]; + } + + if (fineChunks.length == 0) { + return new IntervalPair(null, null, null, null); + } + + // recluster chunks + normalizeIntervalPairs(fineChunks); + IntervalPair[] chunks = clusterChunks(fineChunks, CHUNK_SHIFT - 1); + + // we should have a single chunk now + if (chunks.length != 1) { + return new IntervalPair(null, null, null, null); + } + + IntervalPair overlap = chunks[0]; + + /* + * if they do overlap in a single point, the point of intersection has + * to be an end-point of both curves. therefore, we do not have to + * consider this case here, because it is already checked in the main + * intersection method. + * + * if they overlap, the chunk has to start/end in a start-/endpoint of + * the curves. + */ + + if (PrecisionUtils.equal(overlap.pi.a, 0) + && PrecisionUtils.equal(overlap.pi.b, 1) + || PrecisionUtils.equal(overlap.qi.a, 0) + && PrecisionUtils.equal(overlap.qi.b, 1) + || (PrecisionUtils.equal(overlap.pi.a, 0) || PrecisionUtils + .equal(overlap.pi.b, 1)) + && (PrecisionUtils.equal(overlap.qi.a, 0) || PrecisionUtils + .equal(overlap.qi.b, 1))) { + // it overlaps + + if (PrecisionUtils.equal(overlap.pi.a, 0, CHUNK_SHIFT - 1) + && PrecisionUtils.equal(overlap.pi.b, 0, CHUNK_SHIFT - 1) + || PrecisionUtils.equal(overlap.pi.a, 1, CHUNK_SHIFT - 1) + && PrecisionUtils.equal(overlap.pi.b, 1, CHUNK_SHIFT - 1) + || PrecisionUtils.equal(overlap.qi.a, 0, CHUNK_SHIFT - 1) + && PrecisionUtils.equal(overlap.qi.b, 0, CHUNK_SHIFT - 1) + || PrecisionUtils.equal(overlap.qi.a, 1, CHUNK_SHIFT - 1) + && PrecisionUtils.equal(overlap.qi.b, 1, CHUNK_SHIFT - 1)) { + // end-point-intersection + return new IntervalPair(null, null, null, null); + } + + return overlap; + } + + return new IntervalPair(null, null, null, null); + } + + private static void normalizeIntervalPairs(IntervalPair[] intervalPairs) { + // in every interval, p and q have to be the same curves + if (intervalPairs.length == 0) { + return; + } + + BezierCurve pId = intervalPairs[0].p; + BezierCurve qId = intervalPairs[0].q; + + for (IntervalPair ip : intervalPairs) { + if (ip.p != pId) { + Interval qi = ip.pi; + Interval pi = ip.qi; + ip.p = pId; + ip.q = qId; + ip.pi = pi; + ip.qi = qi; + } + } } - public Point getCtrl(int i) { - return new Point(getCtrlX(i), getCtrlY(i)); + private static boolean pointsEquals(Point p1, Point p2, int shift) { + return PrecisionUtils.equal(p1.x, p2.x, shift) + && PrecisionUtils.equal(p1.y, p2.y, shift); + } + + private Vector3D[] points; + + private static final IPointCmp xminCmp = new IPointCmp() { + public boolean pIsBetterThanQ(Point p, Point q) { + return PrecisionUtils.smallerEqual(p.x, q.x); + } + }; + + private static final IPointCmp xmaxCmp = new IPointCmp() { + public boolean pIsBetterThanQ(Point p, Point q) { + return PrecisionUtils.greaterEqual(p.x, q.x); + } + }; + + private static final IPointCmp yminCmp = new IPointCmp() { + public boolean pIsBetterThanQ(Point p, Point q) { + return PrecisionUtils.smallerEqual(p.y, q.y); + } + }; + + private static final IPointCmp ymaxCmp = new IPointCmp() { + public boolean pIsBetterThanQ(Point p, Point q) { + return PrecisionUtils.greaterEqual(p.y, q.y); + } + }; + + private static boolean containmentParameter(BezierCurve c, + double[] interval, Point p) { + Stack parts = new Stack(); + parts.push(new Interval(interval)); + while (!parts.empty()) { + Interval i = parts.pop(); + + if (i.converges(1)) { + interval[0] = i.a; + interval[1] = i.b; + break; + } + + double iMid = i.getMid(); + Interval left = new Interval(i.a, iMid); + Interval right = new Interval(iMid, i.b); + + BezierCurve clipped = c.getClipped(left.a, left.b); + Rectangle bounds = clipped.getControlBounds(); + + if (bounds.contains(p)) { + parts.push(left); + } + + clipped = c.getClipped(right.a, right.b); + bounds = clipped.getControlBounds(); + + if (bounds.contains(p)) { + parts.push(right); + } + } + return PrecisionUtils.equal(interval[0], interval[1], 1); } /** - * Returns the point-wise coordinates (i.e. x1, y1, x2, y2, etc.) of the - * inner control points of this {@link BezierCurve}, i.e. exclusive of the - * start and end points. + * Returns the similarity of the given {@link BezierCurve} to a {@link Line} + * , which is defined as the absolute distance of its control points to the + * base line connecting its end points. * - * @see BezierCurve#getCtrls() + * A similarity of 0 means that the given {@link BezierCurve}'s control + * points are on a straight line. * - * @return an array containing the inner control points' coordinates + * @param c + * @return */ - public double[] getCtrlCoordinates() { - return PointListUtils.getCopy(ctrlCoordinates); + private static double distanceToBaseLine(BezierCurve c) { + Straight3D baseLine = Straight3D.through(c.points[0], + c.points[c.points.length - 1]); + + if (baseLine == null) { + return 0d; + } + double maxDistance = 0d; + for (int i = 1; i < c.points.length - 1; i++) { + maxDistance = Math.max(maxDistance, + Math.abs(baseLine.getSignedDistanceCW(c.points[i]))); + } + + return maxDistance; } /** - * Returns an array of points representing the inner control points of this - * curve, i.e. excluding the start and end points. In case of s linear - * curve, no control points will be returned, in case of a quadratic curve, - * one control point, and so on. + * Constructs a new {@link BezierCurve} from the given {@link CubicCurve}. * - * @return an array of points with the coordinates of the inner control - * points of this {@link BezierCurve}, i.e. exclusive of the start - * and end point. The number of control points will depend on the - * degree ({@link #getDegree()}) of the curve, so in case of a line - * (linear curve) the array will be empty, in case of a quadratic - * curve, it will be of size 1, in case of a cubic - * curve of size 2, etc.. + * @param c */ - public Point[] getCtrls() { - return PointListUtils.toPointsArray(ctrlCoordinates); + public BezierCurve(CubicCurve c) { + this(c.getP1(), c.getCtrl1(), c.getCtrl2(), c.getP2()); } - public double getCtrlX(int i) { - return ctrlCoordinates[2 * i]; + /** + * Constructs a new {@link BezierCurve} from the given control point + * coordinates. The coordinates are expected to be in x, y order, i.e. x1, + * y1, x2, y2, x3, y3, ... + * + * @param controlPoints + */ + public BezierCurve(double... controlPoints) { + this(PointListUtils.toPointsArray(controlPoints)); } - public double getCtrlY(int i) { - return ctrlCoordinates[2 * i + 1]; + /** + * Constructs a new {@link BezierCurve} object from the given control + * points. + * + * @param controlPoints + */ + public BezierCurve(Point... controlPoints) { + points = new Vector3D[controlPoints.length]; + for (int i = 0; i < points.length; i++) { + points[i] = new Vector3D(controlPoints[i].x, controlPoints[i].y, 1); + } } /** - * Returns the degree of this curve which corresponds to the number of - * overall control points (including start and end point) used to define the - * curve. The degree is zero-based, so a line (linear curve) will have - * degree 1, a quadratic curve will have degree 2, - * and so on. 1 in case of a + * Constructs a new {@link BezierCurve} from the given + * {@link QuadraticCurve}. * - * @return The degree of this {@link ICurve}, which corresponds to the - * zero-based overall number of control points (including start and - * end point) used to define this {@link ICurve}. + * @param c */ - public int getDegree() { - return getCtrls().length + 1; + public BezierCurve(QuadraticCurve c) { + this(c.getP1(), c.getCtrl(), c.getP2()); } /** - * Returns an array of points that represent this {@link BezierCurve}, i.e. - * the start point, the inner control points, and the end points. + * Constructs a new {@link BezierCurve} object from the given control + * points. + * + * Note that a Point(2, 3) is represented by a Vector3D(2, 3, 1). So for a + * Point(x, y) the corresponding vector is Vector(x, y, 1). * - * @return an array of points representing the control points (including - * start and end point) of this {@link BezierCurve} + * @param controlPoints */ - public Point[] getPoints() { - Point[] points = new Point[ctrlCoordinates.length / 2 + 2]; - points[0] = new Point(x1, y1); - points[points.length - 1] = new Point(x2, y2); - for (int i = 1; i < points.length - 1; i++) { - points[i] = new Point(ctrlCoordinates[2 * i - 2], - ctrlCoordinates[2 * i - 1]); + private BezierCurve(Vector3D... controlPoints) { + points = new Vector3D[controlPoints.length]; + for (int i = 0; i < points.length; i++) { + points[i] = controlPoints[i].getCopy(); } - return points; } /** - * {@inheritDoc} + * There are three tests, that have to be done. + * + * The inside-fat-line-check has to be done for every point. It adjusts the + * interval, so that inseparable portions of the curve are detected. + * + * The other two checks have to be done for every segment. They find + * intersections of the curve's convex hull with the fat-line boundaries. + * The points of intersection identify points of breakage. + * + * The starting interval is chosen to be invalid. The individual checks move + * the start and end parameter values past to one another. If everything can + * be clipped, the resulting interval remains invalid. If the resulting + * interval I = [a;b] is valid (a <= b), then the portions [0;a] and [b;1] + * of the curve can be clipped away. * - * @see org.eclipse.gef4.geometry.planar.ICurve#getP1() + * @param L + * The {@link FatLine} to clip to + * @return the new parameter interval for this {@link BezierCurve}. */ - public Point getP1() { - return new Point(x1, y1); + private double[] clipTo(FatLine L) { + double[] interval = new double[] { 1, 0 }; + + Point[] D = getConvexHull(genDifferencePoints(L.line)); + + for (Point p : D) { + if (Double.isNaN(p.y) || L.dmin <= p.y && p.y <= L.dmax) { + moveInterval(interval, p.x); + } + } + + for (Line seg : PointListUtils.toSegmentsArray(D, true)) { + if (seg.getP1().y < L.dmin != seg.getP2().y < L.dmin) { + double x = intersectXAxisParallel(seg.getP1(), seg.getP2(), + L.dmin); + moveInterval(interval, x); + } + if (seg.getP1().y < L.dmax != seg.getP2().y < L.dmax) { + double x = intersectXAxisParallel(seg.getP1(), seg.getP2(), + L.dmax); + moveInterval(interval, x); + } + } + + return interval; } /** - * {@inheritDoc} + * Returns true if the given {@link Point} lies on this {@link BezierCurve}. + * Returns false, otherwise. * - * @see org.eclipse.gef4.geometry.planar.ICurve#getP2() + * @param p + * the {@link Point} to test for containment + * @return true if the {@link Point} is contained, false otherwise */ + public boolean contains(final Point p) { + if (p == null) { + return false; + } + + return containmentParameter(this, new double[] { 0, 1 }, p); + } + + private void findEndPointIntersections(IntervalPair ip, + Set endPointIntervalPairs, Set intersections) { + final double CHUNK_SHIFT_EPSILON = PrecisionUtils + .calculateFraction(CHUNK_SHIFT); + + Point poi = ip.p.points[0].toPoint(); + double[] interval = new double[] { 0, 1 }; + if (containmentParameter(ip.q, interval, poi)) { + ip.pi.a = CHUNK_SHIFT_EPSILON; + interval[0] = (interval[0] + interval[1]) / 2; + interval[1] = interval[0] + CHUNK_SHIFT_EPSILON / 2; + interval[0] = interval[0] - CHUNK_SHIFT_EPSILON / 2; + endPointIntervalPairs.add(new IntervalPair(ip.p, new Interval(0, + ip.pi.a), ip.q, new Interval(interval))); + intersections.add(poi); + } + + poi = ip.p.points[ip.p.points.length - 1].toPoint(); + interval[0] = 0; + interval[1] = 1; + if (containmentParameter(ip.q, interval, poi)) { + ip.pi.b = 1 - CHUNK_SHIFT_EPSILON; + interval[0] = (interval[0] + interval[1]) / 2; + interval[1] = interval[0] + CHUNK_SHIFT_EPSILON / 2; + interval[0] = interval[0] - CHUNK_SHIFT_EPSILON / 2; + endPointIntervalPairs.add(new IntervalPair(ip.p, new Interval( + ip.pi.b, 1), ip.q, new Interval(interval))); + intersections.add(poi); + } + + poi = ip.q.points[0].toPoint(); + interval[0] = 0; + interval[1] = 1; + if (containmentParameter(ip.p, interval, poi)) { + ip.qi.a = CHUNK_SHIFT_EPSILON; + interval[0] = (interval[0] + interval[1]) / 2; + interval[1] = interval[0] + CHUNK_SHIFT_EPSILON / 2; + interval[0] = interval[0] - CHUNK_SHIFT_EPSILON / 2; + endPointIntervalPairs.add(new IntervalPair(ip.p, new Interval( + interval), ip.q, new Interval(0, ip.qi.a))); + intersections.add(poi); + } + + poi = ip.q.points[ip.q.points.length - 1].toPoint(); + interval[0] = 0; + interval[1] = 1; + if (containmentParameter(ip.p, interval, poi)) { + ip.qi.b = 1 - CHUNK_SHIFT_EPSILON; + interval[0] = (interval[0] + interval[1]) / 2; + interval[1] = interval[0] + CHUNK_SHIFT_EPSILON / 2; + interval[0] = interval[0] - CHUNK_SHIFT_EPSILON / 2; + endPointIntervalPairs.add(new IntervalPair(ip.p, new Interval( + interval), ip.q, new Interval(ip.qi.b, 1))); + intersections.add(poi); + } + } + + private Point findExtreme(IPointCmp cmp) { + return findExtreme(cmp, Interval.getFull()); + } + + private Point findExtreme(IPointCmp cmp, Interval iStart) { + Stack parts = new Stack(); + parts.push(iStart); + + Point xtreme = getHC(iStart.a).toPoint(); + + while (!parts.isEmpty()) { + Interval i = parts.pop(); + BezierCurve clipped = getClipped(i.a, i.b); + + Point sp = clipped.points[0].toPoint(); + xtreme = cmp.pIsBetterThanQ(sp, xtreme) ? sp : xtreme; + Point ep = clipped.points[clipped.points.length - 1].toPoint(); + xtreme = cmp.pIsBetterThanQ(ep, xtreme) ? ep : xtreme; + + boolean everythingWorse = true; + for (int j = 1; j < clipped.points.length - 1; j++) { + if (!cmp.pIsBetterThanQ(xtreme, clipped.points[j].toPoint())) { + everythingWorse = false; + break; + } + } + + if (everythingWorse) { + continue; + } + + // split interval + double im = i.getMid(); + parts.push(new Interval(im, i.b)); + parts.push(new Interval(i.a, im)); + } + + return xtreme; + } + + /** + * Find intersection interval chunks. The chunks are not very precise. We + * will refine them later. + * + * @param ip + * @param intervalPairs + * @param intersections + */ + private void findIntersectionChunks(IntervalPair ip, + Set intervalPairs, Set intersections) { + if (ip.converges(CHUNK_SHIFT)) { + intervalPairs.add(ip.getCopy()); + return; + } + + BezierCurve pClipped = ip.getPClipped(); + BezierCurve qClipped = ip.getQClipped(); + + // construct "parallel" and "orthogonal" fat lines + FatLine L1 = FatLine.from(qClipped, PARALLEL); + FatLine L2 = FatLine.from(qClipped, ORTHOGONAL); + + // curve implosion check + if (L1 == null || L2 == null) { + // q is degenerated + Point poi = ip.q.getHC(ip.qi.getMid()).toPoint(); + if (ip.p.contains(poi)) { + intersections.add(poi); + } + return; + } + + // clip to the fat lines + Interval interval = new Interval(pClipped.clipTo(L1)); + Interval intervalOrtho = new Interval(pClipped.clipTo(L2)); + + // pick smaller interval range + interval = Interval.min(interval, intervalOrtho); + + // re-calculate s and e from the clipped interval + double ratio = ip.pi.scaleTo(interval); + + if (ratio < 0) { + // no more intersections + return; + } else if (ratio > 0.8) { + /* + * Split longer curve and find intersections for both halves. Add an + * unrecognizable fraction to the beginning of the second parameter + * interval, so that only one of the getIntersection() calls can + * converge in the middle. + */ + if (ip.isPLonger()) { + IntervalPair[] nip = ip.getPSplit(); + findIntersectionChunks(nip[0], intervalPairs, intersections); + findIntersectionChunks(nip[1], intervalPairs, intersections); + } else { + IntervalPair[] nip = ip.getQSplit(); + findIntersectionChunks(nip[0], intervalPairs, intersections); + findIntersectionChunks(nip[1], intervalPairs, intersections); + } + + return; + } else { + findIntersectionChunks(ip.getSwapped(), intervalPairs, + intersections); + } + } + + /** + * This routine is only called for an interval that has been detected to + * contain a single tangential point of intersection. We do now try to find + * it. + * + * @param ipIO + * @param intervalPairs + * @param intersections + */ + private Point findSinglePreciseIntersection(IntervalPair ipIO) { + Stack partStack = new Stack(); + partStack.push(ipIO); + + while (!partStack.isEmpty()) { + IntervalPair ip = partStack.pop(); + + if (ip.converges()) { + // TODO: do another clipping algorithm here. the one that + // uses control bounds. + for (Point pp : ip.p.toPoints(ip.pi)) { + for (Point qp : ip.q.toPoints(ip.qi)) { + if (pp.equals(qp)) { + copyIntervalPair(ipIO, ip); + return pp; + } + } + } + continue; + } + + BezierCurve pClipped = ip.getPClipped(); + BezierCurve qClipped = ip.getQClipped(); + + // construct "parallel" and "orthogonal" fat lines + FatLine L1 = FatLine.from(qClipped, PARALLEL); + FatLine L2 = FatLine.from(qClipped, ORTHOGONAL); + + // curve implosion check + if (L1 == null || L2 == null) { + // q is degenerated + Point poi = ip.q.getHC(ip.qi.getMid()).toPoint(); + if (ip.p.contains(poi)) { + copyIntervalPair(ipIO, ip); + return poi; + } + continue; + } + + // clip to the fat lines + Interval interval = new Interval(pClipped.clipTo(L1)); + Interval intervalOrtho = new Interval(pClipped.clipTo(L2)); + + // pick smaller interval range + interval = Interval.min(interval, intervalOrtho); + + // re-calculate s and e from the clipped interval + double ratio = ip.pi.scaleTo(interval); + + if (ratio < 0) { + // no more intersections + continue; + } else if (ratio > 0.8) { + /* + * Split longer curve and find intersections for both halves. + * Add an unrecognizable fraction to the beginning of the second + * parameter interval, so that only one of the getIntersection() + * calls can converge in the middle. + */ + IntervalPair[] nip = ip.isPLonger() ? ip.getPSplit() : ip + .getQSplit(); + partStack.push(nip[1]); + partStack.push(nip[0]); + } else { + partStack.push(ip.getSwapped()); + } + } + + return null; + } + + /** + * Generates the difference points of this {@link BezierCurve} to the given + * line. + * + * The difference points are the control points of a Bezier curve that + * yields the signed difference of the point on this curve at a determinate + * parameter value to the given line. + * + * @param line + * @return the difference curve's control points + */ + private Vector3D[] genDifferencePoints(Straight3D line) { + Vector3D[] D = new Vector3D[points.length]; + for (int i = 0; i < points.length; i++) { + double y = line.getSignedDistanceCW(points[i]); + D[i] = new Vector3D((double) (i) / (double) (points.length - 1), y, + 1); + } + return D; + } + + /** + * Computes the {@link Point} on this {@link BezierCurve} at parameter value + * t, which is expected to lie in the range [0;1]. + * + * @param t + * parameter value + * @return the {@link Point} on this {@link BezierCurve} at the given + * parameter value + */ + public Point get(double t) { + return getHC(t).toPoint(); + } + + /** + * @see IGeometry#getBounds() + * @return the bounds of this {@link BezierCurve}. + */ + public Rectangle getBounds() { + double xmin = findExtreme(xminCmp).x; + double xmax = findExtreme(xmaxCmp).x; + double ymin = findExtreme(yminCmp).y; + double ymax = findExtreme(ymaxCmp).y; + + return new Rectangle(new Point(xmin, ymin), new Point(xmax, ymax)); + } + + /** + * Returns a new {@link BezierCurve} object representing this bezier curve + * on the interval [s;e]. + * + * @param s + * @param e + * @return a new {@link BezierCurve} object representing this bezier curve + * on the interval [s;e] + */ + public BezierCurve getClipped(double s, double e) { + if (s == 1) { + return new BezierCurve(points[points.length - 1]); + } + BezierCurve right = split(s)[1]; + double rightT2 = (e - s) / (1 - s); + return right.split(rightT2)[0]; + } + + /** + * Returns the bounds of the control polygon of this {@link BezierCurve} . + * + * @return a {@link Rectangle} representing the bounds of the control + * polygon of this {@link BezierCurve} + */ + public Rectangle getControlBounds() { + Point[] realPoints = getPoints(); + + double xmin = realPoints[0].x, xmax = realPoints[0].x, ymin = realPoints[0].y, ymax = realPoints[0].y; + + for (int i = 1; i < realPoints.length; i++) { + if (realPoints[i].x < xmin) { + xmin = realPoints[i].x; + } else if (realPoints[i].x > xmax) { + xmax = realPoints[i].x; + } + + if (realPoints[i].y < ymin) { + ymin = realPoints[i].y; + } else if (realPoints[i].y > ymax) { + ymax = realPoints[i].y; + } + } + + return new Rectangle(xmin, ymin, xmax - xmin, ymax - ymin); + } + + public BezierCurve getCopy() { + return new BezierCurve(points); + } + + /** + * Computes the hodograph (first parametric derivative) of this + * {@link BezierCurve}. + * + * @return the hodograph (first parametric derivative) of this + * {@link BezierCurve} + */ + public BezierCurve getDerivative() { + Vector3D[] controlPoints = new Vector3D[points.length - 1]; + + for (int i = 0; i < controlPoints.length; i++) { + controlPoints[i] = points[i + 1].getSubtracted(points[i]) + .getScaled(points.length - 1); + // ignore z coordinate: + controlPoints[i].z = 1; + } + + return new BezierCurve(controlPoints); + } + + /** + * Returns the {@link Point} at the given parameter value t. + * + * @param t + * Parameter value + * @return {@link Point} at parameter value t + */ + private Vector3D getHC(double t) { + if (t < 0 || t > 1) { + throw new IllegalArgumentException("t out of range: " + t); + } + + // using horner's scheme: + int n = points.length; + if (n < 1) { + return null; + } + + double bn = 1, tn = 1, d = 1d - t; + Vector3D pn = points[0].getScaled(bn * tn); + for (int i = 1; i < n; i++) { + bn = bn * (n - i) / i; + tn = tn * t; + pn = pn.getScaled(d).getAdded(points[i].getScaled(bn * tn)); + } + + return pn; + } + + /** + * Computes {@link IntervalPair}s which do reflect points of intersection + * between this and the given other {@link BezierCurve}. Each + * {@link IntervalPair} reflects a single point of intersection. + * + * For every {@link IntervalPair} a point of intersection is inserted into + * the given {@link Set} of {@link Point}s. + * + * If there are infinite {@link Point}s of intersection, i.e. the curves do + * overlap, an empty set is returned. (see + * {@link BezierCurve#overlaps(BezierCurve)}) + * + * @param other + * @param intersections + * the {@link Point}-{@link Set} where points of intersection are + * inserted + * @return for a finite number of intersection {@link Point}s, a {@link Set} + * of {@link IntervalPair}s is returned where every + * {@link IntervalPair} represents a single {@link Point} of + * intersection. For an infinite number of intersection + * {@link Point}s, an empty {@link Set} is returned. + */ + public Set getIntersectionIntervalPairs(BezierCurve other, + Set intersections) { + Set intervalPairs = new HashSet(); + Set endPointIntervalPairs = new HashSet(); + + IntervalPair ip = new IntervalPair(this, Interval.getFull(), other, + Interval.getFull()); + + findEndPointIntersections(ip, endPointIntervalPairs, intersections); + findIntersectionChunks(ip, intervalPairs, intersections); + normalizeIntervalPairs(intervalPairs.toArray(new IntervalPair[] {})); + IntervalPair[] clusters = clusterChunks( + intervalPairs.toArray(new IntervalPair[] {}), 0); + + if (isOverlap(clusters, + endPointIntervalPairs.toArray(new IntervalPair[] {})).p != null) { + return new HashSet(0); + } + + Set results = new HashSet(); + results.addAll(endPointIntervalPairs); + + outer: for (IntervalPair cluster : clusters) { + for (IntervalPair epip : endPointIntervalPairs) { + if (isNextTo(cluster, epip, CHUNK_SHIFT)) { + continue outer; + } + } + + // a.t.m. assume for every cluster just a single point of + // intersection: + Point poi = findSinglePreciseIntersection(cluster); + if (poi != null) { + if (cluster.converges()) { + results.add(cluster.getCopy()); + } else { + intersections.add(poi); + } + } + } + + return results; + } + + /** + * @see BezierCurve#findEndPointIntersections(IntervalPair, Set, Set) + * @see BezierCurve#findIntersectionChunks(IntervalPair, Set, Set) + * + * @param other + * @return the points of intersection of this {@link BezierCurve} and the + * given other {@link BezierCurve}. + */ + public Point[] getIntersections(BezierCurve other) { + Set intersections = new HashSet(); + Set intervalPairs = new HashSet(); + Set endPointIntervalPairs = new HashSet(); + + IntervalPair ip = new IntervalPair(this, Interval.getFull(), other, + Interval.getFull()); + + findEndPointIntersections(ip, endPointIntervalPairs, intersections); + findIntersectionChunks(ip, intervalPairs, intersections); + normalizeIntervalPairs(intervalPairs.toArray(new IntervalPair[] {})); + IntervalPair[] clusters = clusterChunks( + intervalPairs.toArray(new IntervalPair[] {}), 0); + + if (isOverlap(clusters, + endPointIntervalPairs.toArray(new IntervalPair[] {})).p != null) { + return new Point[] {}; + } + + outer: for (IntervalPair cluster : clusters) { + for (IntervalPair epip : endPointIntervalPairs) { + if (isNextTo(cluster, epip, CHUNK_SHIFT)) { + continue outer; + } + } + + // a.t.m. assume for every cluster just a single point of + // intersection: + Point poi = findSinglePreciseIntersection(cluster); + if (poi != null) { + intersections.add(poi); + } + } + + return intersections.toArray(new Point[] {}); + } + + public final Point[] getIntersections(ICurve curve) { + Set intersections = new HashSet(); + + for (BezierCurve c : curve.toBezier()) { + intersections.addAll(Arrays.asList(getIntersections(c))); + } + + return intersections.toArray(new Point[] {}); + } + + /** + * Computes the overlap of this {@link BezierCurve} and the given other + * {@link BezierCurve}. If no overlap exists, null is returned. + * Otherwise, a {@link BezierCurve}, representing the overlap, is returned. + * + * An overlap is identified by an infinite number of intersection points. + * + * @param other + * @return a {@link BezierCurve} representing the overlap of this and the + * given other {@link BezierCurve} if an overlap exists, otherwise + * null. + */ + public BezierCurve getOverlap(BezierCurve other) { + Set intersections = new HashSet(); + Set intervalPairs = new HashSet(); + Set endPointIntervalPairs = new HashSet(); + + IntervalPair ip = new IntervalPair(this, Interval.getFull(), other, + Interval.getFull()); + + findEndPointIntersections(ip, endPointIntervalPairs, intersections); + findIntersectionChunks(ip, intervalPairs, intersections); + IntervalPair[] clusters = clusterChunks( + intervalPairs.toArray(new IntervalPair[] {}), 0); + + IntervalPair overlap = isOverlap(clusters, + endPointIntervalPairs.toArray(new IntervalPair[] {})); + + if (overlap.p != null) { + return overlap.getPClipped(); + } + return null; + } + + public Point getP1() { + return points[0].toPoint(); + } + public Point getP2() { - return new Point(x2, y2); + return points[points.length - 1].toPoint(); } /** - * {@inheritDoc} + * @param p + * @return -1 if p not on curve, otherwise the corresponding parameter + * value. + */ + public double getParameterAt(Point p) { + if (p == null) { + // return -1; + throw new NullPointerException("Point may not be null."); + } + + double[] interval = new double[] { 0, 1 }; + if (containmentParameter(this, interval, p)) { + return (interval[0] + interval[1]) / 2; + } else { + // return -1; + throw new IllegalArgumentException( + "The given point does not lie on the curve."); + } + } + + /** + * Returns the {@link Point} at index i in the points array of this + * {@link BezierCurve}. The start {@link Point} is at index 0, the first + * handle-{@link Point} is at index 1, etc. * - * @see org.eclipse.gef4.geometry.planar.ICurve#getX1() + * @param i + * the index of a {@link Point} in the points array of this + * {@link BezierCurve} + * @return the {@link Point} at the given index in the points array of this + * {@link BezierCurve} */ - public double getX1() { - return x1; + public Point getPoint(int i) { + if (i < 0 || i >= points.length) { + throw new IllegalArgumentException( + "getPoint(" + + i + + "): You can only index this BezierCurve's points from 0 to " + + (points.length - 1) + "."); + } + return points[i].toPoint(); } /** - * {@inheritDoc} + * Computes the real planar {@link Point}s for this {@link BezierCurve}. * - * @see org.eclipse.gef4.geometry.planar.ICurve#getX2() + * @return the real planar {@link Point}s for this {@link BezierCurve} */ - public double getX2() { - return x2; + public Point[] getPoints() { + Point[] realPoints = new Point[points.length]; + for (int i = 0; i < points.length; i++) { + realPoints[i] = points[i].toPoint(); + } + return realPoints; } /** - * {@inheritDoc} + * Returns a copy of this {@link BezierCurve}'s points. * - * @see org.eclipse.gef4.geometry.planar.ICurve#getY1() + * @return a copy of this {@link BezierCurve}'s points */ - public double getY1() { - return y1; + private Vector3D[] getPointsCopy() { + Vector3D[] copy = new Vector3D[points.length]; + for (int i = 0; i < points.length; i++) { + copy[i] = points[i].getCopy(); + } + return copy; } /** - * {@inheritDoc} + * Creates a new {@link BezierCurve} with all points translated by the given + * {@link Point}. * - * @see org.eclipse.gef4.geometry.planar.ICurve#getY2() + * @param p + * @return a new {@link BezierCurve} with all points translated by the given + * {@link Point} */ + public BezierCurve getTranslated(Point p) { + Point[] translated = new Point[points.length]; + + for (int i = 0; i < translated.length; i++) { + translated[i] = points[i].toPoint().getTranslated(p); + } + + return new BezierCurve(translated); + } + + public double getX1() { + return getP1().x; + } + + public double getX2() { + return getP2().x; + } + + public double getY1() { + return getP1().y; + } + public double getY2() { - return y2; + return getP2().y; } - protected void setCtrl(int i, Point p) { - setCtrlX(i, p.x); - setCtrlY(i, p.y); + public final boolean intersects(ICurve c) { + return getIntersections(c).length > 0; } - public void setCtrls(Point... ctrls) { - ctrlCoordinates = PointListUtils.toCoordinatesArray(ctrls); + /** + * Moves the interval's start and end values. The start value is set to x if + * x is smaller than the start value. The end value is set to x if x is + * greater than the end value. + * + * @param interval + * The current interval + * @param x + */ + private void moveInterval(double[] interval, double x) { + if (interval[0] > x) { + interval[0] = x; + } + if (interval[1] < x) { + interval[1] = x; + } + } + + /** + * Returns true if this and the given other {@link BezierCurve} + * do overlap. Otherwise, returns false. + * + * @param other + * @return true if this and the given other {@link BezierCurve} + * overlap, otherwise false + */ + public boolean overlaps(BezierCurve other) { + Set intersections = new HashSet(); + Set intervalPairs = new HashSet(); + Set endPointIntervalPairs = new HashSet(); + + IntervalPair ip = new IntervalPair(this, Interval.getFull(), other, + Interval.getFull()); + + findEndPointIntersections(ip, endPointIntervalPairs, intersections); + findIntersectionChunks(ip, intervalPairs, intersections); + IntervalPair[] clusters = clusterChunks( + intervalPairs.toArray(new IntervalPair[] {}), 0); + + return isOverlap(clusters, + endPointIntervalPairs.toArray(new IntervalPair[] {})).p != null; } - protected void setCtrlX(int i, double x) { - // TODO: enlarge array if its too small - ctrlCoordinates[2 * i] = x; + public final boolean overlaps(ICurve c) { + for (BezierCurve seg : c.toBezier()) { + if (overlaps(seg)) { + return true; + } + } + return false; } - protected void setCtrlY(int i, double y) { - // TODO: enlarge array if its too small - ctrlCoordinates[2 * i + 1] = y; + /** + * @param alpha + * @param center + */ + public void rotateCCW(Angle alpha, Point center) { + for (int i = 0; i < points.length; i++) { + points[i] = new Vector3D(new Vector(points[i].toPoint() + .getTranslated(center.getNegated())).getRotatedCCW(alpha) + .toPoint().getTranslated(center)); + } } /** * Sets the start {@link Point} of this {@link BezierCurve} to the given - * {@link Point} p1. + * {@link Point}. * * @param p1 - * the new start {@link Point} + * the new start {@link Point} of this {@link BezierCurve} */ public void setP1(Point p1) { - this.x1 = p1.x; - this.y1 = p1.y; + setPoint(0, p1); } /** * Sets the end {@link Point} of this {@link BezierCurve} to the given - * {@link Point} p2. + * {@link Point}. * * @param p2 - * the new end {@link Point} + * the new end {@link Point} of this {@link BezierCurve} */ public void setP2(Point p2) { - this.x2 = p2.x; - this.y2 = p2.y; + setPoint(points.length - 1, p2); + } + + /** + * Sets the {@link Point} at index i in this {@link BezierCurve}'s + * {@link Point}s array to the given {@link Point}. The start {@link Point} + * 's index is 0. The index of the first handle {@link Point} is 1, etc. + * + * @param i + * the index in this {@link BezierCurve}'s {@link Point}s array + * @param p + * the new {@link Point} for the given index + */ + public void setPoint(int i, Point p) { + if (i < 0 || i >= points.length) { + throw new IllegalArgumentException( + "setPoint(" + + i + + ", " + + p + + "): You can only index this BezierCurve's points from 0 to " + + (points.length - 1) + "."); + } + points[i] = new Vector3D(p); } /** - * Sets the x-coordinate of the start {@link Point} of this - * {@link BezierCurve} to x1. + * Subdivides this {@link BezierCurve} at the given parameter value t into + * two new {@link BezierCurve}s. The first one is the curve over [0;t] and + * the second one is the curve over [t;1]. * - * @param x1 - * the new start {@link Point}'s x-coordinate + * NOTE: One could provide two methods splitLeft() and splitRight() in case + * just the left or right part of the curve is needed. + * + * @param t + * Parameter value + * @return Two curves; to the left and to the right of t. */ - public void setX1(double x1) { - this.x1 = x1; + public BezierCurve[] split(double t) { + Vector3D[] leftPoints = new Vector3D[points.length]; + Vector3D[] rightPoints = new Vector3D[points.length]; + + Vector3D[] ratioPoints = getPointsCopy(); + + for (int i = 0; i < points.length; i++) { + leftPoints[i] = ratioPoints[0]; + rightPoints[points.length - 1 - i] = ratioPoints[points.length - 1 + - i]; + + for (int j = 0; j < points.length - i - 1; j++) { + ratioPoints[j] = ratioPoints[j].getRatio(ratioPoints[j + 1], t); + } + } + + return new BezierCurve[] { new BezierCurve(leftPoints), + new BezierCurve(rightPoints) }; + } + + public BezierCurve[] toBezier() { + return new BezierCurve[] { this }; } /** - * Sets the x-coordinate of the end {@link Point} of this - * {@link BezierCurve} to x2. + * Returns a hard approximation of this {@link BezierCurve} as a + * {@link CubicCurve}. The new {@link CubicCurve} is constructed from the + * first four {@link Point}s in this {@link BezierCurve}'s {@link Point}s + * array. If this {@link BezierCurve} is not of degree four or higher, i.e. + * it does not have four or more control {@link Point}s (including start and + * end {@link Point}), null is returned. * - * @param x2 - * the new end {@link Point}'s x-coordinate + * @return a new {@link CubicCurve} that is constructed by the first four + * control {@link Point}s of this {@link BezierCurve} or + * null if this {@link BezierCurve} does not have at + * least four control {@link Point}s */ - public void setX2(double x2) { - this.x2 = x2; + public CubicCurve toCubic() { + if (points.length > 3) { + return new CubicCurve(points[0].toPoint(), points[1].toPoint(), + points[2].toPoint(), points[points.length - 1].toPoint()); + } + return null; } /** - * Sets the y-coordinate of the start {@link Point} of this - * {@link BezierCurve} to y1. + * Returns a hard approximation of this {@link BezierCurve} as a + * {@link Line}. The {@link Line} is constructed from the start and end + * {@link Point} of this {@link BezierCurve}. * - * @param y1 - * the new start {@link Point}'s y-coordinate + * Sometimes, this {@link Line} is referred to as the base-line of the + * {@link BezierCurve}. + * + * @return a {@link Line} from start to end {@link Point} of this + * {@link BezierCurve} */ - public void setY1(double y1) { - this.y1 = y1; + public Line toLine() { + if (points.length > 1) { + return new Line(points[0].toPoint(), + points[points.length - 1].toPoint()); + } + return null; } /** - * Sets the y-coordinate of the end {@link Point} of this - * {@link BezierCurve} to y2. + * Returns an approximation of this {@link BezierCurve} by a strip of + * {@link Line}s. For detailed information on how the approximation is + * calculated, see {@link BezierCurve#toLineStrip(double, Interval)}. * - * @param y2 - * the new end {@link Point}'s y-coordinate + * @see BezierCurve#toLineStrip(double, Interval) + * @param lineSimilarity + * @return an approximation of this {@link BezierCurve} by a strip of + * {@link Line}s */ - public void setY2(double y2) { - this.y2 = y2; + public Line[] toLineStrip(double lineSimilarity) { + return toLineStrip(lineSimilarity, Interval.getFull()); } + /** + * Returns {@link Line} segments approximating this {@link BezierCurve}. + * + * The {@link BezierCurve} is recursively subdivided until it is "similar" + * to a straight line. The similarity check computes the sum of the + * distances of the control points to the baseline of the + * {@link BezierCurve}. If this sum is smaller than the given + * lineSimilarity, the {@link BezierCurve} is assumed to be "similar" to a + * straight line. + * + * @param lineSimilarity + * The threshold for the sum of the distances of the control + * points to the baseline of this {@link BezierCurve} + * @param startInterval + * The interval of the curve that has to be transformed into a + * strip of {@link Line}s. + * @return {@link Line} segments approximating this {@link BezierCurve}. + */ + public Line[] toLineStrip(double lineSimilarity, Interval startInterval) { + ArrayList lines = new ArrayList(); + + Point startPoint = getHC(startInterval.a).toPoint(); + + // System.out.println("BEZIER CURVE - LINE APPROXIMATION"); + // System.out.println("---------------------------------"); + // System.out.println("moveTo(" + startPoint.x + ", " + startPoint.y + // + ")"); + + Stack parts = new Stack(); + parts.push(startInterval); + + while (!parts.isEmpty()) { + // System.out.println("pop"); + Interval i = parts.pop(); + BezierCurve part = getClipped(i.a, i.b); + + if (distanceToBaseLine(part) < lineSimilarity) { + Point endPoint = getHC(i.b).toPoint(); + lines.add(new Line(startPoint, endPoint)); + startPoint = endPoint; + // System.out.println("lineTo(" + endPoint.x + ", " + // + endPoint.y + ")"); + } else { + // System.out.println("push'em"); + double im = i.getMid(); + parts.push(new Interval(im, i.b)); + parts.push(new Interval(i.a, im)); + } + } + + return lines.toArray(new Line[] {}); + } + + /** + * Returns a {@link Path} approximating this {@link BezierCurve} using + * {@link Line} segments. + * + * @return a {@link Path} approximating this {@link BezierCurve} using + * {@link Line} segments. + */ + public Path toPath() { + Path path = new Path(); + + Point startPoint = points[0].toPoint(); + path.moveTo(startPoint.x, startPoint.y); + + for (Line seg : toLineStrip(0.25d)) { + path.lineTo(seg.getX2(), seg.getY2()); + } + + return path; + } + + /** + * Computes {@link Point}s on this {@link BezierCurve} over the given + * {@link Interval}. Consecutive returned {@link Point}s are required to be + * {@link Point#equals(Object)} to each other. + * + * @param startInterval + * the {@link Interval} of this {@link BezierCurve} to calculate + * {@link Point}s for + * @return {@link Point}s on this {@link BezierCurve} over the given + * parameter {@link Interval} where consecutive {@link Point}s are + * {@link Point#equals(Object)} to each other + */ + public Point[] toPoints(Interval startInterval) { + ArrayList points = new ArrayList(); + points.add(getHC(startInterval.a).toPoint()); + + // System.out.println("BEZIER CURVE - LINE APPROXIMATION"); + // System.out.println("---------------------------------"); + // System.out.println("moveTo(" + startPoint.x + ", " + startPoint.y + // + ")"); + + Stack parts = new Stack(); + parts.push(startInterval); + + while (!parts.isEmpty()) { + // System.out.println("pop"); + Interval i = parts.pop(); + BezierCurve part = getClipped(i.a, i.b); + + Point[] partPoints = part.getPoints(); + + boolean allTogether = true; + for (int j = 1; j < partPoints.length; j++) { + if (!partPoints[0].equals(partPoints[j])) { + allTogether = false; + break; + } + } + + if (allTogether) { + points.add(partPoints[partPoints.length - 1]); + } else { + double im = i.getMid(); + parts.push(new Interval(im, i.b)); + parts.push(new Interval(i.a, im)); + } + } + + return points.toArray(new Point[] {}); + } + + /** + * Returns a hard approximation of this {@link BezierCurve} as a + * {@link QuadraticCurve}. The new {@link QuadraticCurve} is constructed + * from the first three {@link Point}s in this {@link BezierCurve}'s + * {@link Point}s array. If this {@link BezierCurve} is not of degree three + * or higher, i.e. it does not have three or more control {@link Point}s + * (including start and end {@link Point}), null is returned. + * + * @return a new {@link QuadraticCurve} that is constructed by the first + * three control {@link Point}s of this {@link BezierCurve} or + * null if this {@link BezierCurve} does not have at + * least three control {@link Point}s + */ + public QuadraticCurve toQuadratic() { + if (points.length > 2) { + return new QuadraticCurve(points[0].toPoint(), points[1].toPoint(), + points[points.length - 1].toPoint()); + } + return null; + } + + // double x1; + // double y1; + // double x2; + // double y2; + // + // // TODO: use point array instead + // double[] ctrlCoordinates = null; + // + // public BezierCurve(double... coordinates) { + // if (coordinates.length < 4) { + // throw new IllegalArgumentException( + // "A bezier curve needs at least a start and an end point"); + // } + // this.x1 = coordinates[0]; + // this.y1 = coordinates[1]; + // this.x2 = coordinates[coordinates.length - 2]; + // this.y2 = coordinates[coordinates.length - 1]; + // if (coordinates.length > 4) { + // this.ctrlCoordinates = new double[coordinates.length - 4]; + // System.arraycopy(coordinates, 2, ctrlCoordinates, 0, + // coordinates.length - 4); + // } + // } + // + // public BezierCurve(Point... points) { + // this(PointListUtils.toCoordinatesArray(points)); + // } + // + // public final boolean contains(Rectangle r) { + // // TODO: may contain the rectangle only in case the rectangle is + // // degenerated... + // return false; + // } + // + // public Point getCtrl(int i) { + // return new Point(getCtrlX(i), getCtrlY(i)); + // } + // + // /** + // * Returns the point-wise coordinates (i.e. x1, y1, x2, y2, etc.) of the + // * inner control points of this {@link BezierCurve}, i.e. exclusive of the + // * start and end points. + // * + // * @see BezierCurve#getCtrls() + // * + // * @return an array containing the inner control points' coordinates + // */ + // public double[] getCtrlCoordinates() { + // return PointListUtils.getCopy(ctrlCoordinates); + // + // } + // + // /** + // * Returns an array of points representing the inner control points of + // this + // * curve, i.e. excluding the start and end points. In case of s linear + // * curve, no control points will be returned, in case of a quadratic + // curve, + // * one control point, and so on. + // * + // * @return an array of points with the coordinates of the inner control + // * points of this {@link BezierCurve}, i.e. exclusive of the start + // * and end point. The number of control points will depend on the + // * degree ({@link #getDegree()}) of the curve, so in case of a line + // * (linear curve) the array will be empty, in case of a quadratic + // * curve, it will be of size 1, in case of a cubic + // * curve of size 2, etc.. + // */ + // public Point[] getCtrls() { + // return PointListUtils.toPointsArray(ctrlCoordinates); + // } + // + // public double getCtrlX(int i) { + // return ctrlCoordinates[2 * i]; + // } + // + // public double getCtrlY(int i) { + // return ctrlCoordinates[2 * i + 1]; + // } + // + // /** + // * Returns the degree of this curve which corresponds to the number of + // * overall control points (including start and end point) used to define + // the + // * curve. The degree is zero-based, so a line (linear curve) will have + // * degree 1, a quadratic curve will have degree + // 2, + // * and so on. 1 in case of a + // * + // * @return The degree of this {@link ICurve}, which corresponds to the + // * zero-based overall number of control points (including start and + // * end point) used to define this {@link ICurve}. + // */ + // public int getDegree() { + // return getCtrls().length + 1; + // } + // + // /** + // * Returns an array of points that represent this {@link BezierCurve}, + // i.e. + // * the start point, the inner control points, and the end points. + // * + // * @return an array of points representing the control points (including + // * start and end point) of this {@link BezierCurve} + // */ + // public Point[] getPoints() { + // Point[] points = new Point[ctrlCoordinates.length / 2 + 2]; + // points[0] = new Point(x1, y1); + // points[points.length - 1] = new Point(x2, y2); + // for (int i = 1; i < points.length - 1; i++) { + // points[i] = new Point(ctrlCoordinates[2 * i - 2], + // ctrlCoordinates[2 * i - 1]); + // } + // return points; + // } + // + // /** + // * {@inheritDoc} + // * + // * @see org.eclipse.gef4.geometry.planar.ICurve#getP1() + // */ + // public Point getP1() { + // return new Point(x1, y1); + // } + // + // /** + // * {@inheritDoc} + // * + // * @see org.eclipse.gef4.geometry.planar.ICurve#getP2() + // */ + // public Point getP2() { + // return new Point(x2, y2); + // } + // + // /** + // * {@inheritDoc} + // * + // * @see org.eclipse.gef4.geometry.planar.ICurve#getX1() + // */ + // public double getX1() { + // return x1; + // } + // + // /** + // * {@inheritDoc} + // * + // * @see org.eclipse.gef4.geometry.planar.ICurve#getX2() + // */ + // public double getX2() { + // return x2; + // } + // + // /** + // * {@inheritDoc} + // * + // * @see org.eclipse.gef4.geometry.planar.ICurve#getY1() + // */ + // public double getY1() { + // return y1; + // } + // + // /** + // * {@inheritDoc} + // * + // * @see org.eclipse.gef4.geometry.planar.ICurve#getY2() + // */ + // public double getY2() { + // return y2; + // } + // + // protected void setCtrl(int i, Point p) { + // setCtrlX(i, p.x); + // setCtrlY(i, p.y); + // } + // + // public void setCtrls(Point... ctrls) { + // ctrlCoordinates = PointListUtils.toCoordinatesArray(ctrls); + // } + // + // protected void setCtrlX(int i, double x) { + // // TODO: enlarge array if its too small + // ctrlCoordinates[2 * i] = x; + // } + // + // protected void setCtrlY(int i, double y) { + // // TODO: enlarge array if its too small + // ctrlCoordinates[2 * i + 1] = y; + // } + // + // /** + // * Sets the start {@link Point} of this {@link BezierCurve} 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 BezierCurve} 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 BezierCurve} 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 BezierCurve} 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 BezierCurve} 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 BezierCurve} to y2. + // * + // * @param y2 + // * the new end {@link Point}'s y-coordinate + // */ + // public void setY2(double y2) { + // this.y2 = y2; + // } + } diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/BezierSpline.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/BezierSpline.java index 6783eef..0373f1e 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/BezierSpline.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/BezierSpline.java @@ -14,39 +14,86 @@ package org.eclipse.gef4.geometry.planar; import org.eclipse.gef4.geometry.Point; import org.eclipse.gef4.geometry.transform.AffineTransform; -public class BezierSpline implements IPolyCurve { +public class BezierSpline implements ICurve { - public BezierCurve[] getCurves() { + public boolean contains(Point p) { + // TODO Auto-generated method stub + return false; + } + + public Rectangle getBounds() { // TODO Auto-generated method stub return null; } - public boolean contains(Point p) { - throw new UnsupportedOperationException("Not yet implemented."); + public IGeometry getCopy() { + // TODO Auto-generated method stub + return null; + } + + public Point[] getIntersections(ICurve g) { + // TODO Auto-generated method stub + return null; } - public boolean contains(Rectangle r) { - throw new UnsupportedOperationException("Not yet implemented."); + public Point getP1() { + // TODO Auto-generated method stub + return null; } - public Rectangle getBounds() { - throw new UnsupportedOperationException("Not yet implemented."); + public Point getP2() { + // TODO Auto-generated method stub + return null; } public IGeometry getTransformed(AffineTransform t) { - throw new UnsupportedOperationException("Not yet implemented."); + // TODO Auto-generated method stub + return null; + } + + public double getX1() { + // TODO Auto-generated method stub + return 0; + } + + public double getX2() { + // TODO Auto-generated method stub + return 0; + } + + public double getY1() { + // TODO Auto-generated method stub + return 0; + } + + public double getY2() { + // TODO Auto-generated method stub + return 0; + } + + public boolean intersects(ICurve c) { + // TODO Auto-generated method stub + return false; } - public boolean intersects(Rectangle r) { - throw new UnsupportedOperationException("Not yet implemented."); + public boolean overlaps(ICurve c) { + // TODO Auto-generated method stub + return false; + } + + public BezierCurve[] toBezier() { + // TODO Auto-generated method stub + return null; } public Path toPath() { - throw new UnsupportedOperationException("Not yet implemented."); + // TODO Auto-generated method stub + return null; } - public IGeometry getCopy() { - throw new UnsupportedOperationException("Not yet implemented."); + public boolean touches(IGeometry g) { + // TODO Auto-generated method stub + return false; } } diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/CubicCurve.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/CubicCurve.java index b31299a..b82f031 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/CubicCurve.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/CubicCurve.java @@ -12,14 +12,9 @@ *******************************************************************************/ package org.eclipse.gef4.geometry.planar; -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.CurveUtils; import org.eclipse.gef4.geometry.utils.PolynomCalculationUtils; -import org.eclipse.gef4.geometry.utils.PrecisionUtils; /** * Represents the geometric shape of a cubic Bézier curve. @@ -31,46 +26,6 @@ public class CubicCurve extends BezierCurve { private static final long serialVersionUID = 1L; - 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(polygon.getBounds().getArea(), 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[] {}; - } - /** * Constructs a new {@link CubicCurve} object with the given sequence of x- * and y-coordinates of the start-, the first and second control-, and the @@ -155,38 +110,20 @@ public class CubicCurve extends BezierCurve { * @return the {@link CubicCurve} on the interval [t1, t2] */ public CubicCurve clip(double t1, double t2) { - return CurveUtils.clip(this, t1, t2); - } - - /** - * @see IGeometry#contains(Point) - */ - public boolean contains(Point p) { - return CurveUtils.contains(this, p); + return super.getClipped(t1, t2).toCubic(); } @Override public boolean equals(Object other) { CubicCurve o = (CubicCurve) other; - AbstractPointListBasedGeometry myPoly = getControlPolygon(); - AbstractPointListBasedGeometry otherPoly = o.getControlPolygon(); + Polygon myPoly = getControlPolygon(); + Polygon otherPoly = o.getControlPolygon(); return myPoly.equals(otherPoly); } /** - * 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) { - return CurveUtils.get(this, t); - } - - /** * @see IGeometry#getBounds() */ public Rectangle getBounds() { @@ -281,7 +218,7 @@ public class CubicCurve extends BezierCurve { * @return the first control {@link Point}'s x-coordinate. */ public double getCtrlX1() { - return getCtrlX(0); + return getPoint(1).x; } /** @@ -290,7 +227,7 @@ public class CubicCurve extends BezierCurve { * @return the first control {@link Point}'s y-coordinate. */ public double getCtrlY1() { - return getCtrlY(0); + return getPoint(1).y; } /** @@ -308,7 +245,7 @@ public class CubicCurve extends BezierCurve { * @return the second control {@link Point}'s x-coordinate. */ public double getCtrlX2() { - return getCtrlX(1); + return getPoint(2).x; } /** @@ -317,152 +254,7 @@ public class CubicCurve extends BezierCurve { * @return the second control {@link Point}'s y-coordinate. */ public double getCtrlY2() { - return getCtrlY(1); - } - - /** - * Returns the points of intersection between this {@link CubicCurve} and - * the given {@link Arc}. - * - * @param arc - * The {@link Arc} to test for intersections - * @return the points of intersection. - */ - public Point[] getIntersections(Arc arc) { - HashSet intersections = new HashSet(); - - for (CubicCurve seg : arc.getSegments()) { - intersections.addAll(Arrays.asList(getIntersections(seg))); - } - - return intersections.toArray(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) { - if (equals(other)) { - return new Point[] {}; - } - return CurveUtils.getIntersections(this, other); - } - - /** - * Returns the points of intersection between this {@link CubicCurve} and - * the given {@link Ellipse}. - * - * @param e - * The {@link Ellipse} to test for intersections - * @return the points of intersection. - */ - public Point[] getIntersections(Ellipse e) { - return e.getIntersections(this); - } - - /** - * 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 points of intersection between this {@link CubicCurve} and - * the given {@link Polygon}. - * - * @param p - * The {@link Polygon} to test for intersections - * @return the points of intersection. - */ - public Point[] getIntersections(Polygon p) { - return p.getIntersections(this); - } - - /** - * Returns the points of intersection between this {@link CubicCurve} and - * the given {@link Polyline}. - * - * @param p - * The {@link Polyline} to test for intersections - * @return the points of intersection. - */ - public Point[] getIntersections(Polyline p) { - HashSet intersections = new HashSet(); - - for (Line seg : p.getCurves()) { - intersections.addAll(Arrays.asList(getIntersections(seg))); - } - - return intersections.toArray(new Point[] {}); - } - - /** - * Returns the points of intersection between this {@link CubicCurve} and - * the given {@link QuadraticCurve}. - * - * @param c - * The {@link QuadraticCurve} to test for intersections - * @return the points of intersection. - */ - public Point[] getIntersections(QuadraticCurve c) { - return getIntersections(c.getElevated()); - } - - /** - * Returns the points of intersection between this {@link CubicCurve} and - * the given {@link Rectangle}. - * - * @param r - * The {@link Rectangle} to test for intersections - * @return the points of intersection. - */ - public Point[] getIntersections(Rectangle r) { - HashSet intersections = new HashSet(); - - for (Line seg : r.getOutlineSegments()) { - intersections.addAll(Arrays.asList(getIntersections(seg))); - } - - return intersections.toArray(new Point[] {}); - } - - /** - * Returns the points of intersection between this {@link CubicCurve} and - * the given {@link RoundedRectangle}. - * - * @param rr - * The {@link RoundedRectangle} to test for intersections - * @return the points of intersection. - */ - public Point[] getIntersections(RoundedRectangle rr) { - HashSet intersections = new HashSet(); - - // line segments - intersections.addAll(Arrays.asList(getIntersections(rr.getTop()))); - intersections.addAll(Arrays.asList(getIntersections(rr.getLeft()))); - intersections.addAll(Arrays.asList(getIntersections(rr.getBottom()))); - intersections.addAll(Arrays.asList(getIntersections(rr.getRight()))); - - // arc segments - intersections - .addAll(Arrays.asList(getIntersections(rr.getTopRightArc()))); - intersections - .addAll(Arrays.asList(getIntersections(rr.getTopLeftArc()))); - intersections.addAll(Arrays.asList(getIntersections(rr - .getBottomLeftArc()))); - intersections.addAll(Arrays.asList(getIntersections(rr - .getBottomRightArc()))); - - return intersections.toArray(new Point[] {}); + return getPoint(2).y; } /** @@ -475,23 +267,6 @@ public class CubicCurve extends BezierCurve { } /** - * 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; - } - - /** - * @see org.eclipse.gef4.geometry.planar.IGeometry#intersects(Rectangle) - */ - public boolean intersects(Rectangle r) { - return false; - } - - /** * Sets the first control {@link Point} to the given {@link Point} ctrl1. * * @param ctrl1 @@ -510,7 +285,7 @@ public class CubicCurve extends BezierCurve { * the new first control {@link Point}'s x-coordinate */ public void setCtrl1X(double ctrl1x) { - setCtrlX(0, ctrl1x); + setPoint(1, new Point(ctrl1x, getCtrlY1())); } /** @@ -521,7 +296,7 @@ public class CubicCurve extends BezierCurve { * the new first control {@link Point}'s y-coordinate */ public void setCtrl1Y(double ctrl1y) { - setCtrlY(0, ctrl1y); + setPoint(1, new Point(getCtrlX1(), ctrl1y)); } /** @@ -543,7 +318,7 @@ public class CubicCurve extends BezierCurve { * the new second control {@link Point}'s x-coordinate */ public void setCtrl2X(double ctrl2x) { - setCtrlX(1, ctrl2x); + setPoint(2, new Point(ctrl2x, getCtrlY2())); } /** @@ -554,7 +329,7 @@ public class CubicCurve extends BezierCurve { * the new second control {@link Point}'s y-coordinate */ public void setCtrl2Y(double ctrl2y) { - setCtrlY(1, ctrl2y); + setPoint(2, new Point(getCtrlX2(), ctrl2y)); } /** @@ -586,7 +361,8 @@ public class CubicCurve extends BezierCurve { * @return the two {@link CubicCurve}s */ public CubicCurve[] split(double t) { - return CurveUtils.split(this, t); + BezierCurve[] split = super.split(t); + return new CubicCurve[] { split[0].toCubic(), split[1].toCubic() }; } /** diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Ellipse.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Ellipse.java index 13d225b..fb59b7c 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Ellipse.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Ellipse.java @@ -20,6 +20,7 @@ import java.util.List; import org.eclipse.gef4.geometry.Point; import org.eclipse.gef4.geometry.transform.AffineTransform; +import org.eclipse.gef4.geometry.utils.CurveUtils; import org.eclipse.gef4.geometry.utils.PrecisionUtils; /** @@ -33,7 +34,8 @@ import org.eclipse.gef4.geometry.utils.PrecisionUtils; * @author anyssen * @author Matthias Wienand */ -public class Ellipse extends AbstractRectangleBasedGeometry implements IShape { +public class Ellipse extends AbstractRectangleBasedGeometry implements + IShape { private static final long serialVersionUID = 1L; @@ -70,38 +72,6 @@ public class Ellipse extends AbstractRectangleBasedGeometry implements IShape { } /** - * Tests whether the given {@link CubicCurve} is fully contained within this - * {@link Ellipse}. - * - * @param c - * the {@link CubicCurve} to test for containment - * @return true in case the given {@link CubicCurve} is fully - * contained, false otherwise - */ - public boolean contains(CubicCurve c) { - return contains(c.getP1()) && contains(c.getP2()) - && getIntersections(c).length == 0; - } - - /** - * Tests whether the given {@link Ellipse} is fully contained within this - * {@link Ellipse}. - * - * @param o - * the {@link Ellipse} to test for containment - * @return true in case the given {@link Ellipse} is fully - * contained, false otherwise - */ - public boolean contains(Ellipse o) { - for (CubicCurve seg : o.getOutlineSegments()) { - if (!contains(seg)) { - return false; - } - } - return true; - } - - /** * Tests whether the given {@link Line} is fully contained within this * {@link Ellipse}. * @@ -118,70 +88,38 @@ public class Ellipse extends AbstractRectangleBasedGeometry implements IShape { * @see IGeometry#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 - // normalize point p by subtracting the center + /* + * 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); + // then check if it fulfills the ellipse equation - return PrecisionUtils.smallerEqual(normalizedX * normalizedX + if (PrecisionUtils.smallerEqual(normalizedX * normalizedX / (width * width * 0.25d) + normalizedY * normalizedY - / (height * height * 0.25d), 1.0d); - } - - /** - * 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.getOutlineSegments()) { - if (!contains(segment)) { - return false; - } + / (height * height * 0.25d), 1d)) { + return true; } - return true; - } - /** - * 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.getCurves()) { - if (!contains(segment)) { - return false; + // is it "on" the outline? + for (CubicCurve seg : getOutlineSegments()) { + if (seg.contains(p)) { + return true; } } - return true; - } - /** - * Tests whether the given {@link QuadraticCurve} is fully contained within - * this {@link Ellipse}. - * - * @param c - * the {@link QuadraticCurve} to test for containment - * @return true in case the given {@link QuadraticCurve} is - * fully contained, false otherwise - */ - public boolean contains(QuadraticCurve c) { - return contains(c.getP1()) && contains(c.getP2()) - && getIntersections(c).length == 0; + // TODO: what about the small space between outline and ellipse area? + + return false; } /** - * @see IGeometry#contains(Rectangle) + * @see IShape#contains(IGeometry) */ - public boolean contains(Rectangle r) { - // has to contain all border points of the rectangle, to contain it - // completely - return contains(r.getTopLeft()) && contains(r.getTopRight()) - && contains(r.getBottomLeft()) && contains(r.getBottomRight()); + public boolean contains(IGeometry g) { + return CurveUtils.contains(this, g); } /** @@ -248,42 +186,17 @@ public class Ellipse extends AbstractRectangleBasedGeometry implements IShape { } /** - * Calculates the points of intersection of this {@link Ellipse} and the - * given {@link Arc}. + * Calculates the intersections of this {@link Ellipse} with the given + * {@link ICurve}. * - * @param arc - * The {@link Arc} to compute intersection points with - * @return the points of intersection of this {@link Ellipse} and the given - * {@link Arc}. - */ - public Point[] getIntersections(Arc arc) { - HashSet intersections = new HashSet(); - - for (CubicCurve mySeg : getOutlineSegments()) { - for (CubicCurve arcSeg : arc.getSegments()) { - intersections.addAll(Arrays.asList(mySeg - .getIntersections(arcSeg))); - } - } - - 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 + * @param c + * @return {@link Point}s of intersection */ - public Point[] getIntersections(CubicCurve curve) { - HashSet intersections = new HashSet(); - - for (CubicCurve seg : getOutlineSegments()) { - intersections.addAll(Arrays.asList(curve.getIntersections(seg))); + public Point[] getIntersections(ICurve c) { + if (c instanceof Line) { + return getIntersections((Line) c); } - - return intersections.toArray(new Point[] {}); + return CurveUtils.getIntersections(c, this); } /** @@ -291,7 +204,7 @@ public class Ellipse extends AbstractRectangleBasedGeometry implements IShape { * {@link Ellipse}. * * @param e2 - * @return points of intersection + * @return {@link Point}s of intersection */ public Point[] getIntersections(Ellipse e2) { if (equals(e2)) { @@ -419,117 +332,8 @@ public class Ellipse extends AbstractRectangleBasedGeometry implements IShape { return intersections.toArray(new Point[] {}); } - /** - * Returns the intersection points of this {@link Ellipse}'s outline with - * the given {@link Polygon}'s outline. - * - * @param polygon - * the {@link Polygon} to test for intersection - * @return an array containing the intersection points of this - * {@link Ellipse}'s outline with the given {@link Polyline}'s - * outline in case such intersection points exist, an empty array - * otherwise - */ - public Point[] getIntersections(Polygon polygon) { - HashSet intersections = new HashSet(); - - for (Line segment : polygon.getOutlineSegments()) { - for (Point intersection : getIntersections(segment)) { - intersections.add(intersection); - } - } - - return intersections.toArray(new Point[] {}); - } - - /** - * Returns the intersection points of this {@link Ellipse}'s outline with - * the given {@link Polyline}. - * - * @param polyline - * the {@link Polyline} to test for intersection - * @return an array containing the intersection points of this - * {@link Ellipse}'s outline with the given {@link Polyline} in case - * such intersection points exist, an empty array otherwise - */ - public Point[] getIntersections(Polyline polyline) { - List intersections = new ArrayList(); - - for (Line segment : polyline.getCurves()) { - for (Point intersection : getIntersections(segment)) { - intersections.add(intersection); - } - } - - return intersections.toArray(new Point[] {}); - } - - /** - * Computes and returns the points of intersection of this {@link Ellipse}'s - * outline with the given {@link QuadraticCurve}. - * - * @param c - * @return the points of intersection of this {@link Ellipse}'s outline with - * the given {@link QuadraticCurve} - */ - public Point[] getIntersections(QuadraticCurve c) { - return getIntersections(c.getElevated()); - } - - /** - * Returns the intersection points of this {@link Ellipse}'s outline with - * the given {@link Rectangle}'s outline. - * - * @param rectangle - * the {@link Rectangle} to test for intersection - * @return an array containing the intersection points of this - * {@link Ellipse}'s outline with the given {@link Rectangle}'s - * outline in case such intersection points exist, an empty array - * otherwise - */ - public Point[] getIntersections(Rectangle rectangle) { - HashSet intersections = new HashSet(); - - for (Line segment : rectangle.getOutlineSegments()) { - for (Point intersection : getIntersections(segment)) { - intersections.add(intersection); - } - } - - return intersections.toArray(new Point[] {}); - } - - /** - * Returns the intersection points of this {@link Ellipse}'s outline with - * the given {@link RoundedRectangle}'s outline. - * - * @param rr - * The {@link RoundedRectangle} to test for intersection - * @return an array containing the {@link Point}s of intersection of this - * {@link Ellipse}'s outline with the given {@link RoundedRectangle} - * 's outline in case such intersection points exist, an empty array - * otherwise. - */ - public Point[] getIntersections(RoundedRectangle rr) { - HashSet intersections = new HashSet(); - - // line segments - intersections.addAll(Arrays.asList(getIntersections(rr.getTop()))); - intersections.addAll(Arrays.asList(getIntersections(rr.getLeft()))); - intersections.addAll(Arrays.asList(getIntersections(rr.getBottom()))); - intersections.addAll(Arrays.asList(getIntersections(rr.getRight()))); - - // arc segments - intersections - .addAll(Arrays.asList(getIntersections(rr.getTopRightArc()))); - intersections - .addAll(Arrays.asList(getIntersections(rr.getTopLeftArc()))); - intersections.addAll(Arrays.asList(getIntersections(rr - .getBottomLeftArc()))); - intersections.addAll(Arrays.asList(getIntersections(rr - .getBottomRightArc()))); - - return intersections.toArray(new Point[] {}); + public IPolyCurve getOutline() { + return CurveUtils.getOutline(this); } /** @@ -585,82 +389,6 @@ public class Ellipse extends AbstractRectangleBasedGeometry implements IShape { } /** - * Does the given {@link CubicCurve} intersect (including containment) this - * {@link Ellipse}? - * - * @param c - * @return true if they intersect, false otherwise - */ - public boolean intersects(CubicCurve c) { - return contains(c.getP1()) && contains(c.getP2()) - || getIntersections(c).length > 0; - } - - /** - * At least one common point, which includes containment (check - * getIntersections() to check if this is a true intersection). - * - * @param l - * The {@link Line} to test for intersection. - * @return true in case this {@link Ellipse} and the given - * {@link Line} share at least one common point, false - * otherwise - */ - public boolean intersects(Line l) { - return contains(l) || getIntersections(l).length > 0; - } - - /** - * 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.getOutlineSegments()) { - if (intersects(segment)) { - return true; - } - } - return false; - } - - /** - * 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.getCurves()) { - if (intersects(segment)) { - return true; - } - } - return false; - } - - /** - * @see IGeometry#intersects(Rectangle) - */ - public boolean intersects(Rectangle r) { - // quick rejection test - if (!getBounds().intersects(r)) { - return false; - } - - Line[] segments = r.getOutlineSegments(); - for (int i = 0; i < segments.length; i++) { - if (intersects(segments[i])) { - return true; - } - } - return false; - } - - /** * Returns a path representation of this {@link Ellipse}, which is an * approximation of the four segments by means of cubic Bezier curves. * @@ -690,4 +418,5 @@ public class Ellipse extends AbstractRectangleBasedGeometry implements IShape { return "Ellipse: (" + x + ", " + y + ", " + //$NON-NLS-3$//$NON-NLS-2$//$NON-NLS-1$ width + ", " + height + ")";//$NON-NLS-2$//$NON-NLS-1$ } + } diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/ICurve.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/ICurve.java index 3563531..04900f5 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/ICurve.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/ICurve.java @@ -7,6 +7,7 @@ * * Contributors: * Alexander Nyßen (itemis AG) - initial API and implementation + * Matthias Wienand (itemis AG) - contribution for Bugzilla #355997 * *******************************************************************************/ package org.eclipse.gef4.geometry.planar; @@ -68,6 +69,52 @@ public interface ICurve extends IGeometry { */ public Point getP1(); + /** + * A list of {@link BezierCurve}s that approximate the {@link ICurve}. For + * example, a {@link Line} or a {@link BezierCurve} in general, could return + * a list with the curve itself as its only element. But an {@link Ellipse} + * or an {@link Arc} may return a list of consecutive {@link BezierCurve}s + * which approximate the curve. + * + * @return a list of {@link BezierCurve}s that approximate the + * {@link ICurve} + */ + public BezierCurve[] toBezier(); + + /** + * Returns the points of intersection between this {@link ICurve} and the + * given {@link ICurve}. + * + * @param c + * the {@link ICurve} to compute intersection points for + * @return the points of intersection. + */ + public Point[] getIntersections(final ICurve c); + + /** + * Tests if this {@link ICurve} and the given {@link ICurve} intersect, i.e. + * whether a final set of intersection points exists. Two curves intersect + * if they touch (see {@link IGeometry#touches(IGeometry)}) but not overlap + * (see {@link ICurve#overlaps(ICurve)}). + * + * @param c + * the {@link ICurve} to test for intersections + * @return true if they intersect, false otherwise + */ + boolean intersects(final ICurve c); + + /** + * Tests if this {@link ICurve} and the given {@link ICurve} overlap, i.e. + * whether an infinite set of intersection points exists. Two curves overlap + * if they touch (see {@link IGeometry#touches(IGeometry)}) but not + * intersect (see {@link ICurve#intersects(ICurve)}). + * + * @param c + * the {@link ICurve} to test for overlap + * @return true if they overlap, false otherwise + */ + boolean overlaps(final ICurve c); + // start point, end point, control points (optional) // TODO: need to elevate } diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/IGeometry.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/IGeometry.java index 7fbb907..1dab610 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/IGeometry.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/IGeometry.java @@ -7,6 +7,7 @@ * * Contributors: * Alexander Nyßen (itemis AG) - initial API and implementation + * Matthias Wienand (itemis AG) - contribution for Bugzilla #355997 * *******************************************************************************/ package org.eclipse.gef4.geometry.planar; @@ -37,17 +38,6 @@ public interface IGeometry extends Cloneable, Serializable { boolean contains(final Point p); /** - * Returns true if the given {@link Rectangle} is contained - * within {@link IGeometry}, false otherwise. - * - * @param r - * The {@link Rectangle} to test - * @return true if the {@link Rectangle} is fully contained - * within this {@link IGeometry} - */ - boolean contains(final Rectangle r); - - /** * Returns the smallest {@link Rectangle} fully enclosing this * {@link IGeometry}. * @@ -56,6 +46,19 @@ public interface IGeometry extends Cloneable, Serializable { */ Rectangle getBounds(); + // TODO: getTightBounds() : Polygon + + /** + * Returns true if the input {@link IGeometry} touches this + * {@link IGeometry}, i.e. there is at least one common point. + * + * @param g + * The {@link IGeometry} for the intersection test + * @return true if the input {@link IGeometry} and this + * {@link IGeometry} have at least one common point. + */ + boolean touches(IGeometry g); + /** * Returns a new {@link IGeometry}, which represents the given * {@link IGeometry} after the application of the given @@ -72,18 +75,6 @@ public interface IGeometry extends Cloneable, Serializable { IGeometry getTransformed(final AffineTransform t); /** - * Returns true if the input Rectangle intersects this - * Geometry, i.e. there is at least one common point. This includes the case - * that the given rectangle is fully contained. - * - * @param r - * The {@link Rectangle} for the intersection test - * @return true if the input {@link Rectangle} and this - * {@link IGeometry} have at least one common point. - */ - boolean intersects(final Rectangle r); - - /** * Converts this {@link IGeometry} into a {@link Path} representation. * * @return A new {@link Path} representation for this {@link IGeometry}. diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/IPolyCurve.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/IPolyCurve.java index 9c3c216..6669c43 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/IPolyCurve.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/IPolyCurve.java @@ -11,14 +11,20 @@ *******************************************************************************/ package org.eclipse.gef4.geometry.planar; -public interface IPolyCurve extends IGeometry { +/** + * An {@link IPolyCurve} is an {@link ICurve} that is constituted by multiple + * connected {@link ICurve} segments. + */ +public interface IPolyCurve extends ICurve { /** - * Returns a sequence of {@link ICurve}s, representing the curve segments of - * this {@link IPolyCurve}. + * Returns the {@link ICurve} segments of this {@link IPolyCurve}. * * @return an array of {@link ICurve}s, representing the segments that make * up this {@link IPolyCurve} */ public ICurve[] getCurves(); + + // TODO: intersects(), getIntersections(), overlaps() + } diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/IPolyShape.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/IPolyShape.java index 077bd46..3898f50 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/IPolyShape.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/IPolyShape.java @@ -11,7 +11,20 @@ *******************************************************************************/ package org.eclipse.gef4.geometry.planar; +/** + * An {@link IPolyShape} is the common abstraction over all {@link IGeometry}s + * that are constituted by several {@link IShape} parts. + */ public interface IPolyShape extends IGeometry { + /** + * Returns the {@link IShape}s that constitute this {@link IPolyShape}. + * + * @return an array of {@link IShape}s, representing the parts that make up + * this {@link IPolyShape}. + */ IShape[] getShapes(); + + // contains() + } diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/IShape.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/IShape.java index 464dc71..ae0252d 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/IShape.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/IShape.java @@ -7,13 +7,42 @@ * * Contributors: * Alexander Nyßen (itemis AG) - initial API and implementation + * Matthias Wienand (itemis AG) - contribution for Bugzilla #355997 * *******************************************************************************/ package org.eclipse.gef4.geometry.planar; +/** + * An {@link IShape} is an {@link IGeometry} with a closed outline consisting of + * multiple {@link ICurve} segments. + */ public interface IShape extends IGeometry { - // IPolyCurve getOutline(); + /** + * Returns an {@link IPolyCurve} representing the outline of this + * {@link IShape}. + * + * @return an {@link IPolyCurve} representing the outline + */ + public IPolyCurve getOutline(); + + /** + * Returns the individual {@link ICurve} segments, which constitute the + * outline of this {@link IShape}. + * + * @return the {@link ICurve} segments of this {@link IShape}'s outline + */ + public ICurve[] getOutlineSegments(); + + /** + * Tests whether the given {@link IGeometry} is fully contained by this + * {@link IShape}. + * + * @param g + * the {@link IGeometry} to test for containment + * @return true if the given {@link IGeometry} is fully + * contained by this {@link IShape}, false otherwise + */ + public boolean contains(final IGeometry g); - ICurve[] getOutlineSegments(); } diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Line.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Line.java index 15546cd..4d4ef4b 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Line.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Line.java @@ -12,12 +12,10 @@ *******************************************************************************/ package org.eclipse.gef4.geometry.planar; -import java.util.Arrays; -import java.util.HashSet; - import org.eclipse.gef4.geometry.Point; import org.eclipse.gef4.geometry.euclidean.Straight; import org.eclipse.gef4.geometry.euclidean.Vector; +import org.eclipse.gef4.geometry.projective.Vector3D; import org.eclipse.gef4.geometry.transform.AffineTransform; import org.eclipse.gef4.geometry.utils.PointListUtils; import org.eclipse.gef4.geometry.utils.PrecisionUtils; @@ -66,16 +64,13 @@ public class Line extends BezierCurve { } public boolean contains(Point p) { - // TODO: optimize w.r.t object creation - Point p1 = getP1(); - Point p2 = getP2(); - - if (p1.equals(p2)) { - return p.equals(p1); + if (p == null) { + return false; } - return new Straight(p1, p2).containsWithinSegment(new Vector(p1), - new Vector(p2), new Vector(p)); + double distance = Math.abs(new Straight(getP1(), getP2()) + .getSignedDistanceCCW(new Vector(p))); + return PrecisionUtils.equal(distance, 0) && getBounds().contains(p); } /** @@ -133,69 +128,7 @@ public class Line extends BezierCurve { * @return a new {@link Line} with the same start and end point coordinates */ public Line getCopy() { - return new Line(getX1(), getY1(), getX2(), getY2()); - } - - public Point[] getIntersections(Arc a) { - return a.getIntersections(this); - } - - public Point[] getIntersections(CubicCurve c) { - return c.getIntersections(this); - } - - public Point[] getIntersections(Ellipse e) { - return e.getIntersections(this); - } - - public Point[] getIntersections(Polygon p) { - return p.getIntersections(this); - } - - public Point[] getIntersections(Polyline p) { - HashSet intersections = new HashSet(); - - for (Line seg : p.getCurves()) { - intersections.add(getIntersection(seg)); - } - - return intersections.toArray(new Point[] {}); - } - - public Point[] getIntersections(QuadraticCurve c) { - return c.getIntersections(this); - } - - public Point[] getIntersections(Rectangle r) { - HashSet intersections = new HashSet(); - - for (Line seg : r.getOutlineSegments()) { - intersections.addAll(Arrays.asList(getIntersection(seg))); - } - - return intersections.toArray(new Point[] {}); - } - - public Point[] getIntersections(RoundedRectangle rr) { - HashSet intersections = new HashSet(); - - // line segments - intersections.add(getIntersection(rr.getTop())); - intersections.add(getIntersection(rr.getLeft())); - intersections.add(getIntersection(rr.getBottom())); - intersections.add(getIntersection(rr.getRight())); - - // arc segments - intersections - .addAll(Arrays.asList(getIntersections(rr.getTopRightArc()))); - intersections - .addAll(Arrays.asList(getIntersections(rr.getTopLeftArc()))); - intersections.addAll(Arrays.asList(getIntersections(rr - .getBottomLeftArc()))); - intersections.addAll(Arrays.asList(getIntersections(rr - .getBottomRightArc()))); - - return intersections.toArray(new Point[] {}); + return new Line(getP1(), getP2()); } /** @@ -210,32 +143,68 @@ public class Line extends BezierCurve { * given one, in case it intersects, null instead */ public Point getIntersection(Line l) { - // if there is no intersection we may not return an intersection point - if (!intersects(l)) { + Point p1 = getP1(); + Point p2 = getP2(); + + // degenerated case + if (p1.equals(p2)) { + if (l.contains(p1)) { + return p1; + } else if (l.contains(p2)) { + return p2; + } return null; } - Point p1 = getP1(); - Point p2 = getP2(); - Straight s1 = new Straight(p1, p2); Point lp1 = l.getP1(); Point lp2 = l.getP2(); + + // degenerated case + if (lp1.equals(lp2)) { + if (contains(lp1)) { + return lp1; + } else if (contains(lp2)) { + return lp2; + } + return null; + } + + Straight s1 = new Straight(p1, p2); Straight s2 = new Straight(lp1, lp2); - // handle overlap in exactly one point - if (s1.equals(s2)) { - // lines may overlap in exactly one end point - if ((p1.equals(lp1) || p1.equals(lp2)) - && !(p2.equals(lp1) || p2.equals(lp2))) { - return p1; - } else if (p2.equals(lp1) || p2.equals(lp2) - && !(p1.equals(lp1) || p1.equals(lp2))) { - return p2; - } else { - return null; + + if (s1.isParallelTo(s2)) { + if (s1.contains(new Vector(lp1))) { + // end-point-intersection? (no overlap) + double u1 = s1.getParameterAt(lp1); + double u2 = s1.getParameterAt(lp2); + + if (PrecisionUtils.equal(u1, 0) && u2 < u1 + || PrecisionUtils.equal(u1, 1) && u2 > u1) { + return lp1; + } else if (PrecisionUtils.equal(u2, 0) && u1 < u2 + || PrecisionUtils.equal(u2, 1) && u1 > u2) { + return lp2; + } + } + + return null; + } + + Point intersection = s1.getIntersection(s2).toPoint(); + return contains(intersection) && l.contains(intersection) ? intersection + : null; + } + + @Override + public Point[] getIntersections(BezierCurve curve) { + if (curve instanceof Line) { + Point poi = getIntersection((Line) curve); + if (poi != null) { + return new Point[] { poi }; } + return new Point[] {}; } - // return the single intersection point - return s1.getIntersection(s2).toPoint(); + return super.getIntersections(curve); } /** @@ -257,52 +226,100 @@ public class Line extends BezierCurve { return new Line(transformed[0], transformed[1]); } + public boolean touches(IGeometry g) { + if (g instanceof Line) { + return touches((Line) g); + } + return super.touches(g); + } + /** - * Tests whether this {@link Line} and the given one intersect, i.e. if they - * share at least one common point. + * Tests whether this {@link Line} and the given one share at least one + * common point. * * @param l * The {@link Line} to test. * @return true if this {@link Line} and the given one share at * least one common point, false otherwise. */ - public boolean intersects(Line l) { + public boolean touches(Line l) { // TODO: optimize w.r.t. object creation + + /* + * 1) check degenerated (the start and end point imprecisely fall + * together) and special cases where the lines have to be regarded as + * intersecting, because they touch within the used imprecision, though + * they would not intersect with absolute precision. + */ Point p1 = getP1(); Point p2 = getP2(); - if (p1.equals(p2)) { - return l.contains(p1); + boolean touches = l.contains(p1) || l.contains(p2); + if (touches || p1.equals(p2)) { + return touches; } Point lp1 = l.getP1(); Point lp2 = l.getP2(); - if (lp1.equals(lp2)) { - return contains(lp1); + touches = contains(lp1) || contains(lp2); + if (touches || lp1.equals(lp2)) { + return touches; } - - Straight s1 = new Straight(p1, p2); - Straight s2 = new Straight(lp1, lp2); - Vector v1 = new Vector(p1); - Vector v2 = new Vector(p2); - Vector lv1 = new Vector(lp1); - Vector lv2 = new Vector(lp2); - // intersection within Straight does not cover overlap, therefore we - // have to check this separately here (via equality and containment of - // the end points) - return s1.equals(s2) - && (s1.containsWithinSegment(v1, v2, lv1) || s1 - .containsWithinSegment(v1, v2, lv2)) - || s1.intersectsWithinSegment(v1, v2, s2) - && s2.intersectsWithinSegment(lv1, lv2, s1); + Vector3D l1 = new Vector3D(p1).getCrossed(new Vector3D(p2)); + Vector3D l2 = new Vector3D(lp1).getCrossed(new Vector3D(lp2)); + + /* + * 2) non-degenerated case. If the two respective straight lines + * intersect, the intersection has to be contained by both line segments + * for the segments to intersect. If the two respective straight lines + * do not intersect, because they are parallel, the getIntersection() + * method returns null. + */ + Point intersection = l1.getCrossed(l2).toPoint(); + return intersection != null && contains(intersection) + && l.contains(intersection); } /** - * @see IGeometry#intersects(Rectangle) + * Tests whether this {@link Line} and the given other {@link Line} overlap, + * i.e. they share an infinite number of {@link Point}s. + * + * @param l + * the other {@link Line} to test for overlap with this + * {@link Line} + * @return true if this {@link Line} and the other {@link Line} + * overlap, otherwise false + * @see ICurve#overlaps(ICurve) */ - public boolean intersects(Rectangle r) { - return r.intersects(this); + public boolean overlaps(Line l) { + return touches(l) && !intersects(l); + } + + @Override + public boolean overlaps(BezierCurve c) { + if (c instanceof Line) { + return overlaps((Line) c); + } + + // BezierCurve: in order to overlap, all control points have to lie on a + // straight through its base line + Straight s = new Straight(this); + for (Line seg : PointListUtils.toSegmentsArray(c.getPoints(), false)) { + if (!s.equals(new Straight(seg))) { + return false; + } + } + + // if the base line overlaps, we are done + if (overlaps(new Line(c.getP1(), c.getP2()))) { + return true; + } else { + // otherwise, we have to delegate to the general implementation for + // Bezier curves to take care of a degenerated curve, where the + // handle points are outside the base line of the Bezier curve. + return super.touches(c); + } } /** @@ -319,10 +336,8 @@ public class Line extends BezierCurve { * the y-coordinate of the end point */ public void setLine(double x1, double y1, double x2, double y2) { - setX1(x1); - setY1(y1); - setX2(x2); - setY2(y2); + setP1(new Point(x1, y1)); + setP2(new Point(x2, y2)); } /** @@ -353,12 +368,52 @@ public class Line extends BezierCurve { } /** + * Sets the x-coordinate of the start {@link Point} of this {@link Line} to + * the given value. + * + * @param x1 + */ + public void setX1(double x1) { + setP1(new Point(x1, getY1())); + } + + /** + * Sets the y-coordinate of the start {@link Point} of this {@link Line} to + * the given value. + * + * @param y1 + */ + public void setY1(double y1) { + setP1(new Point(getX1(), y1)); + } + + /** + * Sets the x-coordinate of the end {@link Point} of this {@link Line} to + * the given value. + * + * @param x2 + */ + public void setX2(double x2) { + setP2(new Point(x2, getY2())); + } + + /** + * Sets the y-coordinate of the end {@link Point} of this {@link Line} to + * the given value. + * + * @param y2 + */ + public void setY2(double y2) { + setP2(new Point(getX2(), y2)); + } + + /** * @see IGeometry#toPath() */ public Path toPath() { Path path = new Path(); path.moveTo(getX1(), getY1()); - path.lineTo(getX2(), getY1()); + path.lineTo(getX2(), getY2()); return path; } diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Path.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Path.java index fd7546c..64d5537 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Path.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Path.java @@ -121,7 +121,15 @@ public class Path extends AbstractGeometry implements IGeometry { } /** - * @see IGeometry#contains(Rectangle) + * Returns true if the given {@link Rectangle} is contained + * within {@link IGeometry}, false otherwise. + * + * TODO: Generalize to arbitrary {@link IGeometry} objects. + * + * @param r + * The {@link Rectangle} to test + * @return true if the {@link Rectangle} is fully contained + * within this {@link IGeometry} */ public boolean contains(Rectangle r) { return delegate.contains(Geometry2AWT.toAWTRectangle(r)); @@ -177,9 +185,17 @@ public class Path extends AbstractGeometry implements IGeometry { } /** - * {@link IGeometry#intersects(Rectangle)} + * Tests whether this {@link Path} and the given {@link Rectangle} touch, + * i.e. they have at least one {@link Point} in common. + * + * @param r + * the {@link Rectangle} to test for at least one {@link Point} + * in common with this {@link Path} + * @return true if this {@link Path} and the {@link Rectangle} + * touch, otherwise false + * @see IGeometry#touches(IGeometry) */ - public boolean intersects(Rectangle r) { + public boolean touches(Rectangle r) { return delegate.intersects(Geometry2AWT.toAWTRectangle(r)); } diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Pie.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Pie.java index 83dc2c8..effb8f7 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Pie.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Pie.java @@ -19,18 +19,10 @@ public class Pie extends AbstractGeometry implements IGeometry { throw new UnsupportedOperationException("Not yet implemented."); } - public boolean contains(Rectangle r) { - throw new UnsupportedOperationException("Not yet implemented."); - } - public Rectangle getBounds() { throw new UnsupportedOperationException("Not yet implemented."); } - public boolean intersects(Rectangle r) { - throw new UnsupportedOperationException("Not yet implemented."); - } - public Path toPath() { throw new UnsupportedOperationException("Not yet implemented."); } diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/PolyBezier.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/PolyBezier.java new file mode 100644 index 0000000..5124233 --- /dev/null +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/PolyBezier.java @@ -0,0 +1,120 @@ +/******************************************************************************* + * 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.planar; + +import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.utils.CurveUtils; + +/** + * A {@link PolyBezier} is an {@link IPolyCurve} which consists of one or more + * connected {@link BezierCurve}s. + */ +public class PolyBezier extends AbstractGeometry implements IPolyCurve { + + private static final long serialVersionUID = 1L; + private BezierCurve[] beziers; + + /** + * Constructs a new {@link PolyBezier} of the given {@link BezierCurve}s. + * The {@link BezierCurve}s are expected to be connected with each other. + * + * @param beziers + * the {@link BezierCurve}s which will constitute this + * {@link PolyBezier} + */ + public PolyBezier(BezierCurve... beziers) { + this.beziers = copy(beziers); + } + + public boolean contains(Point p) { + for (BezierCurve c : beziers) { + if (c.contains(p)) { + return true; + } + } + return false; + } + + public Rectangle getBounds() { + Rectangle bounds = new Rectangle(); + + for (BezierCurve c : beziers) { + bounds.union(c.getBounds()); + } + + return bounds; + } + + public Path toPath() { + // TODO: need a Path.append(Path) + throw new UnsupportedOperationException("Not yet implemented."); + } + + public IGeometry getCopy() { + return new PolyBezier(beziers); + } + + public double getY2() { + return getP2().y; + } + + public double getY1() { + return getP1().y; + } + + public double getX2() { + return getP2().x; + } + + public double getX1() { + return getP1().x; + } + + public Point getP2() { + return beziers[beziers.length - 1].getP2(); + } + + public Point getP1() { + return beziers[0].getP1(); + } + + public BezierCurve[] toBezier() { + return copy(beziers); + } + + public Point[] getIntersections(ICurve g) { + return CurveUtils.getIntersections(g, this); + } + + public boolean intersects(ICurve c) { + return CurveUtils.intersects(c, this); + } + + public boolean overlaps(ICurve c) { + return CurveUtils.overlaps(c, this); + } + + public BezierCurve[] getCurves() { + return copy(beziers); + } + + private static BezierCurve[] copy(BezierCurve... beziers) { + BezierCurve[] copy = new BezierCurve[beziers.length]; + + for (int i = 0; i < beziers.length; i++) { + copy[i] = beziers[i].getCopy(); + } + + return copy; + } + +} diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Polygon.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Polygon.java index 32d6986..fbfe3ae 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Polygon.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Polygon.java @@ -13,14 +13,10 @@ package org.eclipse.gef4.geometry.planar; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -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; import org.eclipse.gef4.geometry.transform.AffineTransform; +import org.eclipse.gef4.geometry.utils.CurveUtils; import org.eclipse.gef4.geometry.utils.PointListUtils; import org.eclipse.gef4.geometry.utils.PrecisionUtils; @@ -35,7 +31,8 @@ import org.eclipse.gef4.geometry.utils.PrecisionUtils; * @author anyssen * */ -public class Polygon extends AbstractPointListBasedGeometry implements IShape { +public class Polygon extends AbstractPointListBasedGeometry implements + IShape { /** * Pair of {@link Line} segment and integer counter to count segments of @@ -105,25 +102,6 @@ public class Polygon extends AbstractPointListBasedGeometry implements IShape { } /** - * 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 : getOutlineSegments()) { - if (curve.intersects(seg)) { - return false; - } - } - return true; - } - return false; - } - - /** * Checks whether the point that is represented by its x- and y-coordinates * is contained within this {@link Polygon}. * @@ -140,59 +118,6 @@ public class Polygon extends AbstractPointListBasedGeometry implements IShape { } /** - * 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.getOutlineSegments()) { - if (!contains(curve)) { - return false; - } - } - return true; - } - - /** - * Checks whether the given {@link Line} is fully contained within this - * {@link Polygon}. - * - * @param line - * The {@link Line} to test for containment - * @return true if the given {@link Line} is fully contained, - * false otherwise - */ - public boolean contains(Line line) { - // quick rejection test: if the end points are not contained, the line - // may not be contained - if (!contains(line.getP1()) || !contains(line.getP2())) { - return false; - } - - // check for intersections with the segments of this polygon - for (int i = 0; i < points.length; i++) { - Point p1 = points[i]; - Point p2 = i + 1 < points.length ? points[i + 1] : points[0]; - Line segment = new Line(p1, p2); - if (line.intersects(segment)) { - Point intersection = line.getIntersection(segment); - if (intersection != null && !line.getP1().equals(intersection) - && !line.getP2().equals(intersection) - && !segment.getP1().equals(intersection) - && !segment.getP2().equals(intersection)) { - // if we have a single intersection point and this does not - // match one of the end points of the line, the line is not - // contained - return false; - } - } - } - return true; - } - - /** * @see IGeometry#contains(Point) */ public boolean contains(Point p) { @@ -209,111 +134,81 @@ public class Polygon extends AbstractPointListBasedGeometry implements IShape { return false; } - // choose a point p' outside the polygon not too close to its bounds - // (have it outside at 1% of its width and height) and construct a - // straight through p and p' - Vector u1 = new Vector(p); - Vector u2 = new Vector(bounds.getTopLeft().x - - (bounds.getWidth() / 100), bounds.getTopLeft().y - - (bounds.getHeight() / 100)); - Straight s1 = new Straight(u1, u2.getSubtracted((u1))); - - // compute if there is an even or odd number of intersection of - // p->p' with all sides of the polygon; handle the special case the - // point is located on one of the sides + /* + * choose a point p' outside the polygon: + */ + Point pp = new Point(p.x + bounds.getWidth() + 1, p.y); + + /* + * construct a line from p to p': + */ + Line testLine = new Line(p, pp); + + /* + * compute if there is an even or odd number of intersection of the + * test line with all sides of the polygon; handle the special case + * the point is located on one of the sides + */ boolean odd = false; for (int i = 0; i < points.length; i++) { - Vector v1 = new Vector(points[i]); - Vector v2 = new Vector( - points[i + 1 < points.length ? i + 1 : 0]); - Vector direction = v2.getSubtracted(v1); + Point p1 = points[i]; + Point p2 = points[i + 1 < points.length ? i + 1 : 0]; - if (direction.isNull()) { - if (v1.equals(u1)) { + // check whether the point is located on the current side + if (p1.equals(p2)) { + if (p1.equals(p)) { return true; } continue; } - Straight s2 = new Straight(v1, direction); + Line link = new Line(p1, p2); - // check whether the point is located on the current side - if (s2.containsWithinSegment(v1, v2, u1)) { + if (link.contains(p)) { return true; } - // check if there is an intersection within the given segment - if (s2.intersectsWithinSegment(v1, v2, s1) - && s1.intersectsWithinSegment(u1, u2, s2)) { - odd = !odd; + /* + * check if one of the two vertices of the link line is + * contained by the test line. the containment test is done to + * handle special cases where the intersection has to be counted + * appropriately. + * + * 1) if the vertex is above (greater y-component) the other + * point of the line, it is counted once. + * + * 2) if the vertex is below (lower y-component) or on the same + * height as the other point of the line, it is omitted. + */ + boolean p1contained = testLine.contains(p1); + boolean p2contained = testLine.contains(p2); + + // TODO: is imprecision needed for this test? + if (p1contained || p2contained) { + if (p1contained) { + if (p1.y > p2.y) { + odd = !odd; + } + } + if (p2contained) { + if (p2.y > p1.y) { + odd = !odd; + } + } + continue; } - } - return odd; - } - } - /** - * Checks whether the given {@link Polygon} is fully contained within this - * {@link Polygon}. - * - * @param p - * The {@link Polygon} to test for containment - * @return true if the given {@link Polygon} is fully - * contained, false otherwise. - */ - public boolean contains(Polygon p) { - // all segments of the given polygon have to be contained - Line[] otherSegments = p.getOutlineSegments(); - for (int i = 0; i < otherSegments.length; i++) { - if (!contains(otherSegments[i])) { - return false; - } - } - return true; - } - - /** - * 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.getCurves(); - for (int i = 0; i < otherSegments.length; i++) { - if (!contains(otherSegments[i])) { - return false; - } - } - return true; - } - - /** - * 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 : getOutlineSegments()) { - if (curve.intersects(seg)) { - return false; + /* + * check the current link for an intersection with the test + * line. if there is an intersection, change state. + */ + Point poi = testLine.getIntersection(link); + if (poi != null) { + odd = !odd; } } - return true; + return odd; } - return false; - } - - /** - * @see IGeometry#contains(Rectangle) - */ - public boolean contains(Rectangle rect) { - return contains(rect.toPolygon()); } @Override @@ -372,191 +267,36 @@ public class Polygon extends AbstractPointListBasedGeometry implements IShape { } /** - * Returns a copy of this {@link Polygon}, which is made up by the same - * points. - * - * @return a new {@link Polygon} with an identical set of points. - */ - public Polygon getCopy() { - return new Polygon(getPoints()); - } - - /** - * Returns the points of intersection between this {@link Polygon} and the - * given {@link Arc} arc. + * Computes the area of this {@link Polygon}. * - * @param arc - * The {@link Arc} to test for intersections - * @return the points of intersection. + * @return the area of this {@link Polygon} */ - public Point[] getIntersections(Arc arc) { - HashSet intersections = new HashSet(); - - for (CubicCurve seg : arc.getSegments()) { - intersections.addAll(Arrays.asList(getIntersections(seg))); + public double getArea() { + if (points.length < 3) { + return 0; } - return intersections.toArray(new Point[] {}); - } - - /** - * Returns the points of intersection between this {@link Polygon} and the - * given {@link CubicCurve} c. - * - * @param c - * @return The points of intersection. - */ - public Point[] getIntersections(CubicCurve c) { - HashSet intersections = new HashSet(); - - for (Line seg : getOutlineSegments()) { - for (Point poi : c.getIntersections(seg)) { - intersections.add(poi); - } + double area = 0; + for (int i = 0; i < points.length - 1; i++) { + area += points[i].x * points[i + 1].y - points[i].y + * points[i + 1].x; } - return intersections.toArray(new Point[] {}); - } + // closing segment + area += points[points.length - 2].x * points[points.length - 1].y + - points[points.length - 2].y * points[points.length - 1].x; - /** - * 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); + return Math.abs(area) * 0.5; } /** - * 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) { - HashSet intersections = new HashSet(); - - for (Line segment : getOutlineSegments()) { - 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) { - HashSet intersections = new HashSet(); - - for (Line segment : polygon.getOutlineSegments()) { - 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) { - HashSet intersections = new HashSet(); - - for (Line segment : polyline.getCurves()) { - 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 QuadraticCurve} c. - * - * @param c - * @return The points of intersection. - */ - public Point[] getIntersections(QuadraticCurve c) { - HashSet intersections = new HashSet(); - - for (Line seg : getOutlineSegments()) { - for (Point poi : c.getIntersections(seg)) { - 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) { - HashSet intersections = new HashSet(); - - for (Line segment : rect.getOutlineSegments()) { - for (Point poi : getIntersections(segment)) { - intersections.add(poi); - } - } - - return intersections.toArray(new Point[] {}); - } - - /** - * Calculates the points of intersection of this {@link Polygon} and the - * given {@link RoundedRectangle}. + * Returns a copy of this {@link Polygon}, which is made up by the same + * points. * - * @param r - * The {@link RoundedRectangle} to compute intersection points - * with - * @return an array containing the points of intersection of this - * {@link Polygon} and the given {@link RoundedRectangle}. An empty - * array if there are no points of intersection or indefinitely many - * ones. + * @return a new {@link Polygon} with an identical set of points. */ - public Point[] getIntersections(RoundedRectangle r) { - HashSet intersections = new HashSet(); - - // line segments - intersections.addAll(Arrays.asList(getIntersections(r.getTop()))); - intersections.addAll(Arrays.asList(getIntersections(r.getLeft()))); - intersections.addAll(Arrays.asList(getIntersections(r.getBottom()))); - intersections.addAll(Arrays.asList(getIntersections(r.getRight()))); - - // arc segments - intersections - .addAll(Arrays.asList(getIntersections(r.getTopRightArc()))); - intersections - .addAll(Arrays.asList(getIntersections(r.getTopLeftArc()))); - intersections.addAll(Arrays.asList(getIntersections(r - .getBottomLeftArc()))); - intersections.addAll(Arrays.asList(getIntersections(r - .getBottomRightArc()))); - - return intersections.toArray(new Point[] {}); + public Polygon getCopy() { + return new Polygon(getPoints()); } /** @@ -580,267 +320,139 @@ public class Polygon extends AbstractPointListBasedGeometry implements IShape { } /** - * Returns a new Polygon which is shifted along each axis by the passed - * values. - * - * @param dx - * Displacement along X axis - * @param dy - * Displacement along Y axis - * @return The new translated rectangle - * @since 2.0 + * @see IGeometry#toPath() */ - public Polygon getTranslated(double dx, double dy) { - return getCopy().translate(dx, dy); + public Path toPath() { + Path path = new Path(); + if (points.length > 0) { + path.moveTo(points[0].x, points[0].y); + for (int i = 1; i < points.length; i++) { + path.lineTo(points[i].x, points[i].y); + } + path.close(); + } + return path; } - /** - * Returns a new Polygon which is shifted by the position of the given - * Point. - * - * @param pt - * Point providing the amount of shift along each axis - * @return The new translated Polygon - */ - public Polygon getTranslated(Point pt) { - return getCopy().translate(pt); + @Override + public String toString() { + StringBuffer stringBuffer = new StringBuffer("Polygon: "); + if (points.length > 0) { + for (int i = 0; i < points.length; i++) { + stringBuffer.append("(" + points[i].x + ", " + points[i].y + + ")"); + stringBuffer.append(" -> "); + } + stringBuffer.append("(" + points[0].x + ", " + points[0].y + ")"); + } else { + stringBuffer.append(""); + } + return stringBuffer.toString(); } - /** - * 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); + public Polyline getOutline() { + return new Polyline(PointListUtils.toSegmentsArray(points, true)); } /** - * Checks if there is at least one common point between this {@link Polygon} - * and the given {@link Line}, which includes the case that the given - * {@link Line} is fully contained. + * Checks whether the given {@link Line} is fully contained within this + * {@link Polygon}. * * @param line - * The {@link Line} to test for intersection - * @return true if this {@link Polygon} and the given - * {@link Line} share at least one common point, false - * otherwise. + * The {@link Line} to test for containment + * @return true if the given {@link Line} is fully contained, + * false otherwise */ - public boolean intersects(Line line) { - // quick acceptance test: if the end points of the line are contained, - // we already have a common point and may return - if (contains(line.getP1()) || contains(line.getP2())) { - return true; + public boolean contains(Line line) { + // quick rejection test: if the end points are not contained, the line + // may not be contained + if (!contains(line.getP1()) || !contains(line.getP2())) { + return false; } - // check for intersection with the segments - Line[] segments = getOutlineSegments(); - for (int i = 0; i < segments.length; i++) { - if (segments[i].intersects(line)) { - return true; + // check for intersections with the segments of this polygon + for (int i = 0; i < points.length; i++) { + Point p1 = points[i]; + Point p2 = i + 1 < points.length ? points[i + 1] : points[0]; + Line segment = new Line(p1, p2); + if (line.intersects(segment)) { + Point intersection = line.getIntersection(segment); + if (intersection != null && !line.getP1().equals(intersection) + && !line.getP2().equals(intersection) + && !segment.getP1().equals(intersection) + && !segment.getP2().equals(intersection)) { + // if we have a single intersection point and this does not + // match one of the end points of the line, the line is not + // contained + return false; + } } } - return false; + return true; } /** - * Checks if there is at least a common point between this {@link Polygon} - * and the given {@link Polygon}, which includes the case that the given - * {@link Polygon} is fully contained within this {@link Polygon} or vice - * versa. + * Checks whether the given {@link Polygon} is fully contained within this + * {@link Polygon}. * * @param p - * the {@link Polygon} to test for intersection - * @return true in case this {@link Polygon} and the given - * {@link Polygon} share at least one common point. + * The {@link Polygon} to test for containment + * @return true if the given {@link Polygon} is fully + * contained, false otherwise. */ - public boolean intersects(Polygon p) { - // reduce to segment intersection test + public boolean contains(Polygon p) { + // all segments of the given polygon have to be contained Line[] otherSegments = p.getOutlineSegments(); 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 p.contains(this); - } - - /** - * 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.getCurves(); - for (int i = 0; i < otherSegments.length; i++) { - if (intersects(otherSegments[i])) { - return true; + if (!contains(otherSegments[i])) { + return false; } } - // no intersection, so we still need to check for containment - return contains(p); - } - - /** - * Checks if there is at least a common point between this {@link Polygon} - * and the given {@link Rectangle}, which includes the case that the given - * {@link Rectangle} is fully contained within this {@link Polygon} or vice - * versa. - * - * @see IGeometry#intersects(Rectangle) - */ - public boolean intersects(Rectangle rect) { - return intersects(rect.toPolygon()); - } - - /** - * 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; + return true; } /** - * 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. - *
+ * Checks whether the given {@link Rectangle} is fully contained within this + * {@link Polygon}. * - * @param alpha - * The rotation {@link Angle}. - * @param center - * The {@link Point} to rotate around. - * @return This (clock-wise-rotated) {@link Polygon} object. + * @param r + * the {@link Rectangle} to test for containment + * @return true if the given {@link Rectangle} is fully + * contained, false otherwise. */ - 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; + public boolean contains(Rectangle r) { + return contains(r.toPolygon()); } /** - * 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. - *
+ * Tests if the given {@link Polyline} p is contained in this + * {@link Polygon}. * - * @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; - } - - /** - * @see IGeometry#toPath() + * @param p + * @return true if it is contained, false otherwise */ - public Path toPath() { - Path path = new Path(); - if (points.length > 0) { - path.moveTo(points[0].x, points[0].y); - for (int i = 1; i < points.length; i++) { - path.lineTo(points[i].x, points[i].y); + public boolean contains(Polyline p) { + // all segments of the given polygon have to be contained + Line[] otherSegments = p.getCurves(); + for (int i = 0; i < otherSegments.length; i++) { + if (!contains(otherSegments[i])) { + return false; } - path.close(); } - return path; + return true; } - @Override - public String toString() { - StringBuffer stringBuffer = new StringBuffer("Polygon: "); - if (points.length > 0) { - for (int i = 0; i < points.length; i++) { - stringBuffer.append("(" + points[i].x + ", " + points[i].y - + ")"); - stringBuffer.append(" -> "); - } - stringBuffer.append("(" + points[0].x + ", " + points[0].y + ")"); - } else { - stringBuffer.append(""); + public boolean contains(IGeometry g) { + if (g instanceof Line) { + return contains((Line) g); + } else if (g instanceof Polygon) { + return contains((Polygon) g); + } else if (g instanceof Polyline) { + return contains((Polyline) g); + } else if (g instanceof Rectangle) { + return contains((Rectangle) g); } - return stringBuffer.toString(); - } - - /** - * Moves this Polygon horizontally by dx and vertically by dy, then returns - * this Rectangle for convenience. - * - * @param dx - * Shift along X axis - * @param dy - * Shift along Y axis - * @return this for convenience - */ - public Polygon translate(double dx, double dy) { - PointListUtils.translate(points, dx, dy); - return this; - } - - /** - * Moves this Polygon horizontally by the x value of the given Point and - * vertically by the y value of the given Point, then returns this Rectangle - * for convenience. - * - * @param p - * Point which provides translation information - * @return this for convenience - */ - public Polygon translate(Point p) { - return translate(p.x, p.y); + return CurveUtils.contains(this, g); } // TODO: union point, rectangle, polygon, etc. diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Polyline.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Polyline.java index d2e475b..bc90f6e 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Polyline.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Polyline.java @@ -14,6 +14,7 @@ package org.eclipse.gef4.geometry.planar; import org.eclipse.gef4.geometry.Point; import org.eclipse.gef4.geometry.transform.AffineTransform; +import org.eclipse.gef4.geometry.utils.CurveUtils; import org.eclipse.gef4.geometry.utils.PointListUtils; import org.eclipse.gef4.geometry.utils.PrecisionUtils; @@ -27,7 +28,8 @@ import org.eclipse.gef4.geometry.utils.PrecisionUtils; * * @author anyssen */ -public class Polyline extends AbstractPointListBasedGeometry implements IPolyCurve { +public class Polyline extends AbstractPointListBasedGeometry + implements IPolyCurve { private static final long serialVersionUID = 1L; @@ -61,6 +63,16 @@ public class Polyline extends AbstractPointListBasedGeometry implements IPolyCur } /** + * Constructs a new {@link Polyline} from the given array of {@link Line} + * segments. + * + * @param segmentsArray + */ + public Polyline(Line[] segmentsArray) { + super(PointListUtils.toPointsArray(segmentsArray, false)); + } + + /** * Checks whether the point that is represented by its x- and y-coordinates * is contained within this {@link Polyline}. * @@ -90,14 +102,6 @@ public class Polyline extends AbstractPointListBasedGeometry implements IPolyCur return false; } - /** - * @see IGeometry#contains(Rectangle) - */ - public boolean contains(Rectangle r) { - // may contain the rectangle only in case it is degenerated - return false; - } - @Override public boolean equals(Object o) { if (this == o) @@ -148,9 +152,16 @@ public class Polyline extends AbstractPointListBasedGeometry implements IPolyCur } /** - * @see IGeometry#intersects(Rectangle) + * Tests whether this {@link Polyline} and the given {@link Rectangle} + * touch, i.e. they have at least one {@link Point} in common. + * + * @param rect + * the {@link Rectangle} to test + * @return true if this {@link Polyline} and the + * {@link Rectangle} touch, otherwise false + * @see IGeometry#touches(IGeometry) */ - public boolean intersects(Rectangle rect) { + public boolean touches(Rectangle rect) { throw new UnsupportedOperationException("Not yet implemented."); } @@ -168,6 +179,16 @@ public class Polyline extends AbstractPointListBasedGeometry implements IPolyCur return path; } + /** + * Transforms this {@link Polyline} into a {@link PolyBezier}. + * + * @return a {@link PolyBezier} representing this {@link Polyline} + */ + public PolyBezier toPolyBezier() { + Line[] segments = PointListUtils.toSegmentsArray(points, false); + return new PolyBezier(segments); + } + @Override public String toString() { StringBuffer stringBuffer = new StringBuffer("Polyline: "); @@ -192,4 +213,44 @@ public class Polyline extends AbstractPointListBasedGeometry implements IPolyCur return new Polyline(getPoints()); } + public double getY2() { + return getP2().y; + } + + public double getY1() { + return getP1().y; + } + + public double getX2() { + return getP2().x; + } + + public double getX1() { + return getP1().x; + } + + public Point getP2() { + return points[points.length - 1].getCopy(); + } + + public Point getP1() { + return points[0].getCopy(); + } + + public Line[] toBezier() { + return PointListUtils.toSegmentsArray(points, false); + } + + public Point[] getIntersections(ICurve c) { + return CurveUtils.getIntersections(c, this); + } + + public boolean intersects(ICurve c) { + return CurveUtils.intersects(c, this); + } + + public boolean overlaps(ICurve c) { + return CurveUtils.overlaps(c, this); + } + } diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/QuadraticCurve.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/QuadraticCurve.java index 022391f..20a3a3a 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/QuadraticCurve.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/QuadraticCurve.java @@ -12,13 +12,8 @@ *******************************************************************************/ package org.eclipse.gef4.geometry.planar; -import java.util.Arrays; -import java.util.HashSet; - import org.eclipse.gef4.geometry.Point; -import org.eclipse.gef4.geometry.utils.CurveUtils; import org.eclipse.gef4.geometry.utils.PolynomCalculationUtils; -import org.eclipse.gef4.geometry.utils.PrecisionUtils; /** * Represents the geometric shape of a quadratic Bézier curve. @@ -30,110 +25,6 @@ public class QuadraticCurve extends BezierCurve { private static final long serialVersionUID = 1L; - 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(polygon.getBounds().getArea(), 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 = pPoly.getBounds().getArea(); - double qArea = qPoly.getBounds().getArea(); - - 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[] {}; - } - /** * Constructs a new {@link QuadraticCurve} from the given sequence of x- and * y-coordinates of the start-, the control-, and the end-point. @@ -207,41 +98,19 @@ public class QuadraticCurve extends BezierCurve { * @return the {@link QuadraticCurve} on the interval [t1, t2] */ public QuadraticCurve clip(double t1, double t2) { - return CurveUtils.clip(this, t1, t2); - } - - /** - * 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) { - return CurveUtils.contains(this, p); + return super.getClipped(t1, t2).toQuadratic(); } public boolean equals(Object other) { QuadraticCurve o = (QuadraticCurve) other; - AbstractPointListBasedGeometry myPoly = getControlPolygon(); - AbstractPointListBasedGeometry otherPoly = o.getControlPolygon(); + Polygon myPoly = getControlPolygon(); + Polygon otherPoly = o.getControlPolygon(); return myPoly.equals(otherPoly); } /** - * 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) { - return CurveUtils.get(this, t); - } - - /** * 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. @@ -335,7 +204,7 @@ public class QuadraticCurve extends BezierCurve { * @return the control point's x-coordinate */ public double getCtrlX() { - return getCtrlX(0); + return getPoint(1).x; } /** @@ -344,7 +213,7 @@ public class QuadraticCurve extends BezierCurve { * @return the control point's y-coordinate */ public double getCtrlY() { - return getCtrlY(0); + return getPoint(1).y; } /** @@ -369,60 +238,6 @@ public class QuadraticCurve extends BezierCurve { } /** - * 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); - } - - /** - * 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 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; - } - - /** - * 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; - } - - public boolean intersects(Rectangle r) { - for (Line l : r.getOutlineSegments()) { - if (intersects(l)) { - return true; - } - } - return false; - } - - /** * Sets the curve's control point. * * @param ctrl @@ -438,7 +253,7 @@ public class QuadraticCurve extends BezierCurve { * @param ctrlX */ public void setCtrlX(double ctrlX) { - setCtrlX(0, ctrlX); + setPoint(1, new Point(ctrlX, getCtrlY())); } /** @@ -447,7 +262,7 @@ public class QuadraticCurve extends BezierCurve { * @param ctrlY */ public void setCtrlY(double ctrlY) { - setCtrlY(0, ctrlY); + setPoint(1, new Point(getCtrlX(), ctrlY)); } /** @@ -461,7 +276,9 @@ public class QuadraticCurve extends BezierCurve { * [0, t] 2. [t, 1] */ public QuadraticCurve[] split(double t) { - return CurveUtils.split(this, t); + BezierCurve[] split = super.split(t); + return new QuadraticCurve[] { split[0].toQuadratic(), + split[1].toQuadratic() }; } /** diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Rectangle.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Rectangle.java index 76e38dd..32a6e02 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Rectangle.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Rectangle.java @@ -17,6 +17,8 @@ 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; +import org.eclipse.gef4.geometry.utils.CurveUtils; +import org.eclipse.gef4.geometry.utils.PointListUtils; import org.eclipse.gef4.geometry.utils.PrecisionUtils; /** @@ -37,8 +39,8 @@ import org.eclipse.gef4.geometry.utils.PrecisionUtils; * @author ahunter * @author anyssen */ -public final class Rectangle extends AbstractRectangleBasedGeometry implements - IShape { +public final class Rectangle extends AbstractRectangleBasedGeometry + implements IShape { private static final long serialVersionUID = 1L; @@ -147,29 +149,6 @@ public final class Rectangle extends AbstractRectangleBasedGeometry implements } /** - * Returns true in case the rectangle specified by (x, y, width, height) is - * contained within this {@link Rectangle}. - * - * @param x - * The x coordinate of the rectangle to be tested for containment - * @param y - * The y coordinate of the rectangle to be tested for containment - * @param width - * The width of the rectangle to be tested for containment - * @param height - * The height of the rectangle to be tested for containment - * @return true if the rectangle characterized by (x,y, width, - * height) is (imprecisely) fully contained within this - * {@link Rectangle}, false otherwise - */ - 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.width, width) - && PrecisionUtils.greaterEqual(this.height, height); - } - - /** * Returns whether the given point is within the boundaries of this * Rectangle. The boundaries are inclusive of the top and left edges, but * exclusive of the bottom and right edges. @@ -184,13 +163,6 @@ public final class Rectangle extends AbstractRectangleBasedGeometry implements } /** - * @see IGeometry#contains(Rectangle) - */ - public boolean contains(Rectangle r) { - return contains(r.x, r.y, r.width, r.height); - } - - /** * Returns true if this Rectangle's x, y, width, and height * values are identical to the provided ones. * @@ -297,44 +269,6 @@ public final class Rectangle extends AbstractRectangleBasedGeometry implements } /** - * Returns a new {@link Rectangle}, whose location is identical to the - * location of this {@link Rectangle}, but whose size is scaled by the given - * factor. - * - * @param scale - * the scale factor, with which to multiply the height and width - * of this {@link Rectangle} when computing the size of the new - * {@link Rectangle} to be returned - * @return a new {@link Rectangle} with a location identical to this one's - * and a size that is computed by multiplying width and height of - * this {@link Rectangle} by the given factor. - */ - public Rectangle getScaled(double scale) { - return getScaled(scale, scale); - } - - /** - * Returns a new {@link Rectangle}, whose location is identical to the - * location of this {@link Rectangle}, but whose size is scaled by the given - * factors. - * - * @param scaleX - * the factor, with which to multiply the width of this - * {@link Rectangle}, when computing the size of the new - * {@link Rectangle} to be returned - * @param scaleY - * the factor, with which to multiply the height of this - * {@link Rectangle} when computing the size of the new - * {@link Rectangle} to be returned - * @return a new {@link Rectangle} with a location identical to this one's - * and a size that is computed by multiplying width and height of - * this {@link Rectangle} by the given factor. - */ - public Rectangle getScaled(double scaleX, double scaleY) { - return getCopy().scale(scaleX, scaleY); - } - - /** * Returns an array of {@link Line}s representing the top, right, bottom, * and left borders of this {@link Rectangle}. * @@ -380,15 +314,6 @@ public final class Rectangle extends AbstractRectangleBasedGeometry implements } /** - * Returns a new point representing the center of this Rectangle. - * - * @return Point at the center of the rectangle - */ - public Point getCenter() { - return new Point(x + width / 2, y + height / 2); - } - - /** * Returns a new Rectangle which has the exact same parameters as this * Rectangle. * @@ -458,15 +383,6 @@ public final class Rectangle extends AbstractRectangleBasedGeometry implements } /** - * Returns the location of this {@link Rectangle}. - * - * @return The current location - */ - public Point getLocation() { - return new Point(x, y); - } - - /** * Returns a new Point which represents the middle point of the right hand * side of this Rectangle. * @@ -478,38 +394,64 @@ public final class Rectangle extends AbstractRectangleBasedGeometry implements /** * Rotates this {@link Rectangle} clock-wise by the given {@link Angle} - * alpha around the {@link Point} center. + * around the center ({@link #getCentroid()}) of this {@link Rectangle}. + * + * @see #getRotatedCW(Angle, Point) + * @param alpha + * the rotation {@link Angle} + * @return the resulting {@link Polygon} + */ + public Polygon getRotatedCW(Angle alpha) { + return getRotatedCW(alpha, getCentroid()); + } + + /** + * Rotates this {@link Rectangle} clock-wise by the given {@link Angle} + * alpha around the given {@link Point}. * - * If the rotation {@link Angle} is not an integer multiple 90\u00b0, the - * resulting figure cannot be expressed as a {@link Rectangle} object. + * If the rotation {@link Angle} is not an integer multiple of 90 degrees, + * 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}) + * the center point for the rotation + * @return the resulting {@link Polygon} */ public Polygon getRotatedCW(Angle alpha, Point center) { - return toPolygon().rotateCW(alpha, center); + return (Polygon) toPolygon().rotateCW(alpha, center); + } + + /** + * Rotates this {@link Rectangle} counter-clock-wise by the given + * {@link Angle} around the center {@link Point} of this {@link Rectangle} + * (see {@link #getCentroid()}). + * + * @see #getRotatedCCW(Angle, Point) + * @param alpha + * @return the resulting {@link Polygon} + */ + public Polygon getRotatedCCW(Angle alpha) { + return getRotatedCCW(alpha, getCentroid()); } /** * Rotates this {@link Rectangle} counter-clock-wise by the given - * {@link Angle} alpha around the {@link Point} center. + * {@link Angle} around the given {@link Point}. * - * If the rotation {@link Angle} is not an integer multiple 90\u00b0, the - * resulting figure cannot be expressed as a {@link Rectangle} object. + * If the rotation {@link Angle} is not an integer multiple of 90 degrees, + * 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}) + * the center point for the rotation + * @return the resulting {@link Polygon} */ public Polygon getRotatedCCW(Angle alpha, Point center) { - return toPolygon().rotateCCW(alpha, center); + return (Polygon) toPolygon().rotateCCW(alpha, center); } /** @@ -589,34 +531,6 @@ public final class Rectangle extends AbstractRectangleBasedGeometry implements } /** - * Returns a new Rectangle which is shifted along each axis by the passed - * values. - * - * @param dx - * Displacement along X axis - * @param dy - * Displacement along Y axis - * @return The new translated rectangle - * - */ - public Rectangle getTranslated(double dx, double dy) { - return getCopy().translate(dx, dy); - } - - /** - * Returns a new Rectangle which is shifted by the position of the given - * Point. - * - * @param pt - * Point providing the amount of shift along each axis - * @return The new translated Rectangle - * - */ - public Rectangle getTranslated(Point pt) { - return getCopy().translate(pt); - } - - /** * Returns a new rectangle whose width and height have been interchanged, as * well as its x and y values. This can be useful in orientation changes. * @@ -676,8 +590,8 @@ public final class Rectangle extends AbstractRectangleBasedGeometry implements } /** - * Tests whether this {@link Rectangle} and the given {@link Line} - * intersect, i.e. whether they have at least one point in common. + * Tests whether this {@link Rectangle} and the given {@link Line} touch, + * i.e. whether they have at least one point in common. * * @param l * The {@link Line} to test. @@ -685,7 +599,7 @@ public final class Rectangle extends AbstractRectangleBasedGeometry implements * {@link Line} share at least one common point, false * otherwise. */ - public boolean intersects(Line l) { + public boolean touches(Line l) { if (contains(l.getP1()) || contains(l.getP2())) { return true; } @@ -699,10 +613,31 @@ public final class Rectangle extends AbstractRectangleBasedGeometry implements } /** - * @see IGeometry#intersects(Rectangle) - */ - public boolean intersects(Rectangle r) { - return !getIntersected(r).isEmpty(); + * Tests whether this {@link Rectangle} and the given other + * {@link Rectangle} touch, i.e. whether they have at least one point in + * common. + * + * @param r + * The {@link Rectangle} to test + * @return true if this {@link Rectangle} and the given + * {@link Rectangle} share at least one common point, + * false otherwise. + * @see IGeometry#touches(IGeometry) + */ + public boolean touches(Rectangle r) { + return PrecisionUtils.smallerEqual(r.x, x + width) + && PrecisionUtils.smallerEqual(r.y, y + height) + && PrecisionUtils.greaterEqual(r.x + r.width, x) + && PrecisionUtils.greaterEqual(r.y + r.height, y); + } + + public boolean touches(IGeometry g) { + if (g instanceof Line) { + return touches((Line) g); + } else if (g instanceof Rectangle) { + return touches((Rectangle) g); + } + return super.touches(g); } /** @@ -719,34 +654,6 @@ public final class Rectangle extends AbstractRectangleBasedGeometry implements } /** - * Scales the size of this Rectangle by the given scale and returns this for - * convenience. - * - * @param scaleFactor - * The factor by which this size will be scaled - * @return this Rectangle for convenience - */ - public Rectangle scale(double scaleFactor) { - return scale(scaleFactor, scaleFactor); - } - - /** - * Scales the size of this Rectangle by the given scales and returns this - * for convenience. - * - * @param scaleX - * the factor by which the width has to be scaled - * @param scaleY - * the factor by which the height has to be scaled - * @return this Rectangle for convenience - */ - public Rectangle scale(double scaleX, double scaleY) { - width *= scaleX; - height *= scaleY; - return this; - } - - /** * Shrinks the sides of this Rectangle by the horizontal and vertical values * provided as input, and returns this Rectangle for convenience. The center * of this Rectangle is kept constant. @@ -807,7 +714,7 @@ public final class Rectangle extends AbstractRectangleBasedGeometry implements * @return A {@link Polygon} representation for this {@link Rectangle} */ public Polygon toPolygon() { - return new Polygon(getPoints()); + return new Polygon(PointListUtils.copy(getPoints())); } @Override @@ -835,33 +742,6 @@ public final class Rectangle extends AbstractRectangleBasedGeometry implements } /** - * Moves this {@link Rectangle} horizontally by dx and vertically by dy. - * - * @param dx - * Shift along X axis - * @param dy - * Shift along Y axis - * @return this for convenience - */ - public Rectangle translate(double dx, double dy) { - x += dx; - y += dy; - return this; - } - - /** - * Moves this {@link Rectangle} horizontally by the x value of the given - * {@link Point} and vertically by the y value of the given {@link Point}. - * - * @param p - * The {@link Point} which provides the translation information - * @return this for convenience - */ - public Rectangle translate(Point p) { - return translate(p.x, p.y); - } - - /** * Switches the x and y values, as well as the width and height of this * Rectangle. Useful for orientation changes. * @@ -953,4 +833,54 @@ public final class Rectangle extends AbstractRectangleBasedGeometry implements return union(r.x, r.y, r.width, r.height); } + public Polyline getOutline() { + return new Polyline(x, y, x + width, y, x + width, y + height, x, y + + height, x, y); + } + + public boolean contains(IGeometry g) { + if (g instanceof Rectangle) { + return contains((Rectangle) g); + } + return CurveUtils.contains(this, g); + } + + /** + * Returns true in case the rectangle specified by (x, y, width, height) is + * contained within this {@link Rectangle}. + * + * @param x + * The x coordinate of the rectangle to be tested for containment + * @param y + * The y coordinate of the rectangle to be tested for containment + * @param width + * The width of the rectangle to be tested for containment + * @param height + * The height of the rectangle to be tested for containment + * @return true if the rectangle characterized by (x,y, width, + * height) is (imprecisely) fully contained within this + * {@link Rectangle}, false otherwise + */ + 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.width, width) + && PrecisionUtils.greaterEqual(this.height, height); + } + + /** + * Tests whether this {@link Rectangle} fully contains the given other + * {@link Rectangle}. + * + * @param r + * the other {@link Rectangle} to test for being contained by + * this {@link Rectangle} + * @return true if this {@link Rectangle} contains the other + * {@link Rectangle}, otherwise false + * @see IShape#contains(IGeometry) + */ + public boolean contains(Rectangle r) { + return contains(r.x, r.y, r.width, r.height); + } + } diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Region.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Region.java index d0855b1..b821610 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Region.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Region.java @@ -37,7 +37,7 @@ public class Region extends AbstractGeometry implements IPolyShape { throw new UnsupportedOperationException("Not yet implemented."); } - public boolean intersects(Rectangle r) { + public boolean touches(Rectangle r) { throw new UnsupportedOperationException("Not yet implemented."); } diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Ring.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Ring.java index 1e1afec..403c453 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Ring.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Ring.java @@ -38,7 +38,7 @@ public class Ring extends AbstractGeometry implements IPolyShape { throw new UnsupportedOperationException("Not yet implemented."); } - public boolean intersects(Rectangle r) { + public boolean touches(Rectangle r) { throw new UnsupportedOperationException("Not yet implemented."); } @@ -49,4 +49,5 @@ public class Ring extends AbstractGeometry implements IPolyShape { public IGeometry getCopy() { throw new UnsupportedOperationException("Not yet implemented."); } + } diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/RoundedRectangle.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/RoundedRectangle.java index e25e6c0..028d581 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/RoundedRectangle.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/RoundedRectangle.java @@ -14,6 +14,7 @@ package org.eclipse.gef4.geometry.planar; import org.eclipse.gef4.geometry.Angle; import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.utils.CurveUtils; import org.eclipse.gef4.geometry.utils.PrecisionUtils; /** @@ -27,8 +28,8 @@ import org.eclipse.gef4.geometry.utils.PrecisionUtils; * * @author anyssen */ -public final class RoundedRectangle extends AbstractRectangleBasedGeometry implements - IShape { +public final class RoundedRectangle extends + AbstractRectangleBasedGeometry implements IShape { private static final long serialVersionUID = 1L; @@ -126,15 +127,6 @@ public final class RoundedRectangle extends AbstractRectangleBasedGeometry imple } /** - * @see IGeometry#contains(Rectangle) - */ - public boolean contains(final Rectangle r) { - // check that all border points of the rectangle are contained. - return contains(r.getTopLeft()) && contains(r.getTopRight()) - && contains(r.getBottomLeft()) && contains(r.getBottomRight()); - } - - /** * Returns the arc height of this {@link RoundedRectangle}, which is the * height of the arc used to define its rounded corners. * @@ -155,47 +147,6 @@ public final class RoundedRectangle extends AbstractRectangleBasedGeometry imple } /** - * @see IGeometry#intersects(Rectangle) - */ - public boolean intersects(final Rectangle r) { - // quick rejection via bounds - final Rectangle testRect = getBounds(); - if (!testRect.intersects(r)) { - return false; - } - - // check for intersection within the two inner rectangles - testRect.setBounds(x, y + arcHeight, width, height - 2 * arcHeight); - if (testRect.intersects(r)) { - return true; - } - testRect.setBounds(x + arcWidth, y, width - 2 * arcWidth, height); - if (testRect.contains(r)) { - return true; - } - - // check the arcs - final Ellipse e = new Ellipse(x, y, 2 * arcWidth, 2 * arcHeight); - if (e.intersects(r)) { - return true; - } - e.setBounds(x, y + height - 2 * arcHeight, 2 * arcWidth, 2 * arcHeight); - if (e.intersects(r)) { - return true; - } - e.setBounds(x + width - 2 * arcWidth, y, 2 * arcWidth, 2 * arcHeight); - if (e.intersects(r)) { - return true; - } - e.setBounds(x + width - 2 * arcWidth, y + height - 2 * arcHeight, - 2 * arcWidth, 2 * arcHeight); - if (e.intersects(r)) { - return true; - } - return false; - } - - /** * Sets the arc height of this {@link RoundedRectangle}, which is the height * of the arc used to define its rounded corners. * @@ -344,4 +295,13 @@ public final class RoundedRectangle extends AbstractRectangleBasedGeometry imple public RoundedRectangle getCopy() { return new RoundedRectangle(x, y, width, height, arcWidth, arcHeight); } + + public IPolyCurve getOutline() { + return CurveUtils.getOutline(this); + } + + public boolean contains(IGeometry g) { + return CurveUtils.contains(this, g); + } + } diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/projective/Straight3D.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/projective/Straight3D.java new file mode 100644 index 0000000..ae679af --- /dev/null +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/projective/Straight3D.java @@ -0,0 +1,116 @@ +/******************************************************************************* + * 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.projective; + +import org.eclipse.gef4.geometry.Point; + +/** + *

+ * A two-dimensional infinite line that is defined by three coordinates of which + * the third is a so called homogeneous coordinate. Calculations are easier to + * do on such lines: + *

    + *
  • the point of intersection between two lines is the cross product of their + * respective three dimensional vectors
  • + *
  • the distance from a point to the line is the scalar product of both three + * dimensional vectors
  • + *
+ *

+ *

+ * This is the complement to the {@link Vector3D} which represents a + * {@link Point} with a third, homogeneous coordinate. + *

+ * + * @author wienand + */ +public final class Straight3D { + private Vector3D sp, line; + private double f; + + private Straight3D() { + } + + /** + * Constructs a new {@link Straight3D} through the given start and end + * {@link Vector3D}s. + * + * @param start + * @param end + * @return a new {@link Straight3D} through start and end {@link Vector3D}s + */ + public static Straight3D through(Vector3D start, Vector3D end) { + Straight3D self = new Straight3D(); + self.sp = start; + self.line = self.sp.getCrossed(end); + + self.f = Math.sqrt(self.line.x * self.line.x + self.line.y + * self.line.y); + if (self.f == 0d) { + return null; + } + + return self; + } + + /** + * Returns the orthogonal {@link Straight3D} through this {@link Straight3D} + * 's start {@link Vector3D}. + * + * @return the orthogonal {@link Straight3D} through this {@link Straight3D} + * 's start {@link Vector3D} + */ + public Straight3D getOrtho() { + return getOrtho(sp); + } + + /** + * Returns the orthogonal {@link Straight3D} through the given + * {@link Vector3D}. + * + * @param vp + * @return the orthogonal {@link Straight3D} through the given + * {@link Vector3D} + */ + public Straight3D getOrtho(Vector3D vp) { + return Straight3D.through(vp, new Vector3D(vp.x + line.x, + vp.y + line.y, vp.z)); + } + + /** + * Returns the clock-wise signed distance of the given {@link Vector3D} to + * this {@link Straight3D}. The clock-wise signed distance is the dot + * product of the both {@link Vector3D}s divided by the length of the line's + * (x,y) vector: |(x,y)|. + * + * @param vp + * @return the clock-wise signed distance of the {@link Vector3D} to this + * {@link Straight3D} + */ + public double getSignedDistanceCW(Vector3D vp) { + Point p = vp.toPoint(); + return (line.x * p.x + line.y * p.y + line.z) / f; + } + + /** + * Returns the intersection between this and the given other + * {@link Straight3D}. The intersection is the cross product of both + * {@link Vector3D}s. + * + * @param other + * @return the intersection between this and the given other + * {@link Straight3D} + */ + public Vector3D getIntersection(Straight3D other) { + return line.getCrossed(other.line); + } + +} \ No newline at end of file diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/projective/Vector3D.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/projective/Vector3D.java new file mode 100644 index 0000000..833f61f --- /dev/null +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/projective/Vector3D.java @@ -0,0 +1,195 @@ +/******************************************************************************* + * 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.projective; + +import org.eclipse.gef4.geometry.Point; + +/** + * The Vector3D class implements a three dimensional vector (components x, y, z) + * with its standard operations: addition and multiplication (scalar, + * dot-product, cross-product). + * + * It is used to represent planar lines and planar points which are represented + * by three dimensional planes and three dimensional lines through the origin, + * respectively. + * + * @author wienand + */ +public final class Vector3D { + /** + * the x-coordinate of this {@link Vector3D}. + */ + public double x; + + /** + * the y-coordinate of this {@link Vector3D}. + */ + public double y; + + /** + * the homogeneous coordinate of this {@link Vector3D}. + */ + public double z; + + /** + * Constructs a new {@link Vector3D} from the given {@link Point}, setting z + * to 1. + * + * @param p + */ + public Vector3D(Point p) { + this(p.x, p.y, 1); + } + + /** + * Constructs a new {@link Vector3D} object with the given component values. + * + * @param px + * @param py + * @param pz + */ + public Vector3D(double px, double py, double pz) { + x = px; + y = py; + z = pz; + } + + /** + * Returns a copy of this {@link Vector3D}. + * + * @return a copy of this {@link Vector3D} + */ + public Vector3D getCopy() { + return new Vector3D(x, y, z); + } + + @Override + public boolean equals(Object other) { + if (other instanceof Vector3D) { + Vector3D o = (Vector3D) other; + Point tmp = this.toPoint(); + if (tmp == null) { + return o.toPoint() == null; + } + return tmp.equals(o.toPoint()); + } + return false; + } + + /** + * Returns a new {@link Vector3D} object with its components set to the sum + * of the individual x, y and z components of this {@link Vector3D} and the + * given other {@link Vector3D}. + * + * @param other + * @return a new {@link Vector3D} object representing the sum of this + * {@link Vector3D} and the given other {@link Vector3D} + */ + public Vector3D getAdded(Vector3D other) { + return new Vector3D(this.x + other.x, this.y + other.y, this.z + + other.z); + } + + /** + * Returns a new {@link Vector3D} object with its components set to the + * difference of the individual x, y and z components of this + * {@link Vector3D} and the given other {@link Vector3D}. + * + * @param other + * @return a new {@link Vector3D} object representing the difference of this + * {@link Vector3D} and the given other {@link Vector3D} + */ + public Vector3D getSubtracted(Vector3D other) { + return new Vector3D(this.x - other.x, this.y - other.y, this.z + - other.z); + } + + /** + * Returns a new {@link Vector3D} object with its components set to the x, y + * and z components of this {@link Vector3D} scaled by the given factor. + * + * @param f + * The scaling factor. + * @return a new {@link Vector3D} object with its components set to the x, y + * and z components of this {@link Vector3D} scaled by the given + * factor + */ + public Vector3D getScaled(double f) { + return new Vector3D(x * f, y * f, z * f); + } + + /** + * Returns a new {@link Vector3D} object with its components set to the + * given ratio between this {@link Vector3D} and the given other + * {@link Vector3D}. + * + * @param other + * The other {@link Vector3D}. + * @param t + * The ratio. + * @return a new {@link Vector3D} object with its components set to the + * given ratio between this {@link Vector3D} and the given other + * {@link Vector3D} + */ + public Vector3D getRatio(Vector3D other, double t) { + return getAdded(other.getSubtracted(this).getScaled(t)); + } + + /** + * Returns a new {@link Vector3D} object that is the cross product of this + * and the given other {@link Vector3D}. + * + * @param other + * @return a new {@link Vector3D} object that is the cross product of this + * and the given other {@link Vector3D} + */ + public Vector3D getCrossed(Vector3D other) { + return new Vector3D(this.y * other.z - this.z * other.y, this.z + * other.x - this.x * other.z, this.x * other.y - this.y + * other.x); + } + + /** + * Returns the dot-product of this and the given other {@link Vector3D}. + * + * @param other + * @return the dot-product of this and the given other {@link Vector3D} + */ + public double getDot(Vector3D other) { + return this.x * other.x + this.y * other.y + this.z * other.z; + } + + /** + * Returns a new {@link Point} object that is represented by this + * {@link Vector3D}. + * + * @return a new {@link Point} object that is represented by this + * {@link Vector3D} + */ + public Point toPoint() { + if (this.z == 0) { + return null; + } + return new Point(this.x / this.z, this.y / this.z); + } + + public String toString() { + return "Vector3D (" + x + ", " + y + ", " + z + ")"; + } + + @Override + public int hashCode() { + // cannot generate a good hash-code because of the imprecise + // comparisons + return 0; + } +} \ No newline at end of file diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/transform/AffineTransform.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/transform/AffineTransform.java index f7a8093..16b5859 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/transform/AffineTransform.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/transform/AffineTransform.java @@ -17,71 +17,179 @@ import org.eclipse.gef4.geometry.Point; import org.eclipse.gef4.geometry.convert.AWT2Geometry; import org.eclipse.gef4.geometry.convert.Geometry2AWT; +/** + * TODO + */ public class AffineTransform { // TODO: implement affine transform locally to get rid of dependency on // awt.geom. private java.awt.geom.AffineTransform delegate = new java.awt.geom.AffineTransform(); + /** + * TODO + * + * @return + */ public double[] getMatrix() { double[] flatmatrix = new double[6]; delegate.getMatrix(flatmatrix); return flatmatrix; } + /** + * TODO + */ public AffineTransform() { } + /** + * TODO + * + * @param m00 + * @param m10 + * @param m01 + * @param m11 + * @param m02 + * @param m12 + */ public AffineTransform(double m00, double m10, double m01, double m11, double m02, double m12) { delegate = new java.awt.geom.AffineTransform(m00, m10, m01, m11, m02, m12); } + /** + * TODO + * + * @param flatmatrix + */ public AffineTransform(double[] flatmatrix) { delegate = new java.awt.geom.AffineTransform(flatmatrix); } + /** + * TODO + * + * @see java.awt.geom.AffineTransform#getType() + * + * @return + */ public int getType() { return delegate.getType(); } + /** + * Computes the determinant of the transformation matrix of this + * {@link AffineTransform}. + * + * @return the determinant of the transformation matrix of this + * {@link AffineTransform} + */ public double getDeterminant() { return delegate.getDeterminant(); } + /** + * Returns the x-coordinate scaling of this {@link AffineTransform}'s + * transformation matrix. + * + * @return the x-coordinate scaling of this {@link AffineTransform}'s + * transformation matrix + */ public double getScaleX() { return delegate.getScaleX(); } + /** + * Returns the y-coordinate scaling of this {@link AffineTransform}'s + * transformation matrix. + * + * @return the y-coordinate scaling of this {@link AffineTransform}'s + * transformation matrix + */ public double getScaleY() { return delegate.getScaleY(); } + /** + * Returns the x-coordinate shearing of this {@link AffineTransform}'s + * transformation matrix. + * + * @return the x-coordinate shearing of this {@link AffineTransform}'s + * transformation matrix + */ public double getShearX() { return delegate.getShearX(); } + /** + * Returns the y-coordinate shearing of this {@link AffineTransform}'s + * transformation matrix. + * + * @return the y-coordinate shearing of this {@link AffineTransform}'s + * transformation matrix + */ public double getShearY() { return delegate.getShearY(); } + /** + * Returns the x-coordinate translation of this {@link AffineTransform}'s + * transformation matrix. + * + * @return the x-coordinate translation of this {@link AffineTransform}'s + * transformation matrix + */ public double getTranslateX() { return delegate.getTranslateX(); } + /** + * Returns the y-coordinate translation of this {@link AffineTransform}'s + * transformation matrix. + * + * @return the y-coordinate translation of this {@link AffineTransform}'s + * transformation matrix + */ public double getTranslateY() { return delegate.getTranslateY(); } + /** + * Sets the translation values of the x- and y-coordinates in the + * transformation matrix of this {@link AffineTransform}. + * + * @param tx + * the x-coordinate translation + * @param ty + * the y-coordinate translation + */ public void translate(double tx, double ty) { delegate.translate(tx, ty); } + /** + * Adds a rotation with the given angle (in radians) to the transformation + * matrix of this {@link AffineTransform}. + * + * @param theta + * the rotation angle in radians + */ public void rotate(double theta) { delegate.rotate(theta); } + /** + * Adds a rotation with the given angle (in radians) to the transformation + * matrix of this {@link AffineTransform}. + * + * TODO + * + * @param theta + * @param anchorx + * @param anchory + */ public void rotate(double theta, double anchorx, double anchory) { delegate.rotate(theta, anchorx, anchory); } diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/utils/CurveUtils.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/utils/CurveUtils.java index 5e5b6cf..4605a63 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/utils/CurveUtils.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/utils/CurveUtils.java @@ -11,15 +11,23 @@ *******************************************************************************/ package org.eclipse.gef4.geometry.utils; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; import java.util.HashSet; import java.util.Set; import org.eclipse.gef4.geometry.Point; import org.eclipse.gef4.geometry.euclidean.Straight; -import org.eclipse.gef4.geometry.planar.CubicCurve; +import org.eclipse.gef4.geometry.planar.BezierCurve; +import org.eclipse.gef4.geometry.planar.BezierCurve.IntervalPair; +import org.eclipse.gef4.geometry.planar.ICurve; +import org.eclipse.gef4.geometry.planar.IGeometry; +import org.eclipse.gef4.geometry.planar.IPolyCurve; +import org.eclipse.gef4.geometry.planar.IPolyShape; +import org.eclipse.gef4.geometry.planar.IShape; import org.eclipse.gef4.geometry.planar.Line; -import org.eclipse.gef4.geometry.planar.QuadraticCurve; -import org.eclipse.gef4.geometry.planar.Rectangle; +import org.eclipse.gef4.geometry.planar.PolyBezier; /** * The {@link CurveUtils} class provides functionality that can be used for @@ -32,1219 +40,451 @@ import org.eclipse.gef4.geometry.planar.Rectangle; public class CurveUtils { /** - * The Vector3D class implements a three dimensional vector (components x, - * y, z) with its standard operations: addition and multiplication (scalar, - * dot-product, cross-product). + * Delegates to the BezierCurve.getIntersections(ICurve) method. * - * It is used to represent planar lines and planar points which are - * represented by three dimensional planes and three dimensional lines - * through the origin, respectively. - * - * @author wienand + * @param curve1 + * @param curve2 + * @return points of intersection */ - private static final class Vector3D { - public double x, y, z; - - /** - * Constructs a new {@link Vector3D} from the given {@link Point}, - * setting z to 1. - * - * @param p - */ - public Vector3D(Point p) { - this(p.x, p.y, 1); - } - - /** - * Constructs a new {@link Vector3D} object with the given component - * values. - * - * @param px - * @param py - * @param pz - */ - public Vector3D(double px, double py, double pz) { - x = px; - y = py; - z = pz; - } + public static Point[] getIntersections(ICurve curve1, ICurve curve2) { + Set intersections = new HashSet(); - /** - * Returns a copy of this {@link Vector3D}. - * - * @return a copy of this {@link Vector3D} - */ - public Vector3D getCopy() { - return new Vector3D(x, y, z); - } - - @Override - public boolean equals(Object other) { - if (other instanceof Vector3D) { - Vector3D o = (Vector3D) other; - Point tmp = this.toPoint(); - if (tmp == null) { - return o.toPoint() == null; - } - return tmp.equals(o.toPoint()); - } - return false; - } - - /** - * Returns a new {@link Vector3D} object with its components set to the - * sum of the individual x, y and z components of this {@link Vector3D} - * and the given other {@link Vector3D}. - * - * @param other - * @return a new {@link Vector3D} object representing the sum of this - * {@link Vector3D} and the given other {@link Vector3D} - */ - public Vector3D getAdded(Vector3D other) { - return new Vector3D(this.x + other.x, this.y + other.y, this.z - + other.z); - } - - /** - * Returns a new {@link Vector3D} object with its components set to the - * difference of the individual x, y and z components of this - * {@link Vector3D} and the given other {@link Vector3D}. - * - * @param other - * @return a new {@link Vector3D} object representing the difference of - * this {@link Vector3D} and the given other {@link Vector3D} - */ - public Vector3D getSubtracted(Vector3D other) { - return new Vector3D(this.x - other.x, this.y - other.y, this.z - - other.z); - } - - /** - * Returns a new {@link Vector3D} object with its components set to the - * x, y and z components of this {@link Vector3D} scaled by the given - * factor. - * - * @param f - * The scaling factor. - * @return a new {@link Vector3D} object with its components set to the - * x, y and z components of this {@link Vector3D} scaled by the - * given factor - */ - public Vector3D getScaled(double f) { - return new Vector3D(x * f, y * f, z * f); - } - - /** - * Returns a new {@link Vector3D} object with its components set to the - * given ratio between this {@link Vector3D} and the given other - * {@link Vector3D}. - * - * @param other - * The other {@link Vector3D}. - * @param t - * The ratio. - * @return a new {@link Vector3D} object with its components set to the - * given ratio between this {@link Vector3D} and the given other - * {@link Vector3D} - */ - public Vector3D getRatio(Vector3D other, double t) { - return getAdded(other.getSubtracted(this).getScaled(t)); - } - - /** - * Returns a new {@link Vector3D} object that has the same direction as - * this {@link Vector3D} but the x- and y-components are normalized so - * that x*x + y*y = 1. - * - * @return a new {@link Vector3D} object with x- and y-components - * normalized to fulfill x*x + y*y = 1. - */ - public Vector3D getLineNormalized() { - double f = Math.sqrt(x * x + y * y); - if (f == 0) { - return null; - } - return new Vector3D(x / f, y / f, z / f); - } - - /** - * Returns a new {@link Vector3D} object that is the cross product of - * this and the given other {@link Vector3D}. - * - * @param other - * @return a new {@link Vector3D} object that is the cross product of - * this and the given other {@link Vector3D} - */ - public Vector3D getCrossed(Vector3D other) { - return new Vector3D(this.y * other.z - this.z * other.y, this.z - * other.x - this.x * other.z, this.x * other.y - this.y - * other.x); - } - - /** - * Returns the dot-product of this and the given other {@link Vector3D}. - * - * @param other - * @return the dot-product of this and the given other {@link Vector3D} - */ - public double getDot(Vector3D other) { - return this.x * other.x + this.y * other.y + this.z * other.z; + for (BezierCurve bezier : curve1.toBezier()) { + intersections + .addAll(Arrays.asList(bezier.getIntersections(curve2))); } - /** - * Returns a new {@link Point} object that is represented by this - * {@link Vector3D}. - * - * @return a new {@link Point} object that is represented by this - * {@link Vector3D} - */ - public Point toPoint() { - if (this.z == 0) { - return null; - } - return new Point(this.x / this.z, this.y / this.z); - } - - public String toString() { - return "Vector3D (" + x + ", " + y + ", " + z + ")"; - } - - @Override - public int hashCode() { - // cannot generate a good hash-code because of the imprecise - // comparisons - return 0; - } + return intersections.toArray(new Point[] {}); } /** - * The {@link BezierCurve} provides a common representation for arbitrary - * Bezier curves. - * - * It can evaluate points on the curve, check points for containment and - * compute intersection points for one curve with another. + * Delegates to the getIntersections(ICurve, ICurve) method. * - * It uses homogeneous coordinates (represented by {@link Vector3D} objects) - * to represent planar lines and points and to compute line/line - * intersections and point/line distances. - * - * @author wienand + * @param curve + * @param shape + * @return points of intersection */ - private static final class BezierCurve { - - private static final double UNRECOGNIZABLE_PRECISION_FRACTION = PrecisionUtils - .calculateFraction(0) / 10; + public static Point[] getIntersections(ICurve curve, IShape shape) { + Set intersections = new HashSet(); - private Vector3D[] points; - - /** - * Constructs a new {@link BezierCurve} object from the given control - * points. - * - * @param controlPoints - */ - public BezierCurve(Point... controlPoints) { - points = new Vector3D[controlPoints.length]; - for (int i = 0; i < points.length; i++) { - points[i] = new Vector3D(controlPoints[i].x, - controlPoints[i].y, 1); - } + for (ICurve curve2 : shape.getOutlineSegments()) { + intersections + .addAll(Arrays.asList(getIntersections(curve, curve2))); } - /** - * Constructs a new {@link BezierCurve} object from the given control - * points. - * - * Note that a Point(2, 3) is represented by a Vector3D(2, 3, 1). So for - * a Point(x, y) the corresponding vector is Vector(x, y, 1). - * - * @param controlPoints - */ - public BezierCurve(Vector3D... controlPoints) { - points = new Vector3D[controlPoints.length]; - for (int i = 0; i < points.length; i++) { - points[i] = controlPoints[i].getCopy(); - } - } - - /** - * Constructs a new {@link BezierCurve} from the given - * {@link QuadraticCurve}. - * - * @param c - */ - public BezierCurve(QuadraticCurve c) { - this(c.getP1(), c.getCtrl(), c.getP2()); - } + return intersections.toArray(new Point[] {}); + } - /** - * Constructs a new {@link BezierCurve} from the given - * {@link CubicCurve}. - * - * @param c - */ - public BezierCurve(CubicCurve c) { - this(c.getP1(), c.getCtrl1(), c.getCtrl2(), c.getP2()); - } + /** + * Delegates to the getIntersections(ICurve, ICurve) method. + * + * @param curve + * @param polyCurve + * @return points of intersection + */ + public static Point[] getIntersections(ICurve curve, IPolyCurve polyCurve) { + Set intersections = new HashSet(); - /** - * Returns a copy of this {@link BezierCurve}'s points. - * - * @return a copy of this {@link BezierCurve}'s points - */ - private Vector3D[] getPointsCopy() { - Vector3D[] copy = new Vector3D[points.length]; - for (int i = 0; i < points.length; i++) { - copy[i] = points[i].getCopy(); - } - return copy; + for (ICurve curve2 : polyCurve.getCurves()) { + intersections + .addAll(Arrays.asList(getIntersections(curve, curve2))); } - /** - * Constructs the explicit Bézier curve for this curve's x-components. - * - * @return the explicit Bézier curve for this curve's x-components - */ - public BezierCurve getExplicitX() { - Vector3D[] explicit = new Vector3D[points.length]; + return intersections.toArray(new Point[] {}); + } - for (int i = 0; i < points.length; i++) { - explicit[i] = new Vector3D((double) i - / ((double) points.length - 1d), points[i].toPoint().x, - 1); - } + /** + * Delegates to the getIntersections(ICurve, IShape) method. + * + * @param curve + * @param polyShape + * @return points of intersection + */ + public static Point[] getIntersections(ICurve curve, IPolyShape polyShape) { + Set intersections = new HashSet(); - return new BezierCurve(explicit); + for (IShape shape : polyShape.getShapes()) { + intersections.addAll(Arrays.asList(getIntersections(curve, shape))); } - /** - * Constructs the explicit Bézier curve for this curve's y-components. - * - * @return the explicit Bézier curve for this curve's y-components - */ - public BezierCurve getExplicitY() { - Vector3D[] explicit = new Vector3D[points.length]; - - for (int i = 0; i < points.length; i++) { - explicit[i] = new Vector3D((double) i - / ((double) points.length - 1d), points[i].toPoint().y, - 1); - } + return intersections.toArray(new Point[] {}); + } - return new BezierCurve(explicit); + /** + * Delegates to one of the above getIntersections() methods. + * + * @param curve + * @param geom + * @return points of intersection + */ + public static Point[] getIntersections(ICurve curve, IGeometry geom) { + if (geom instanceof ICurve) { + return getIntersections(curve, (ICurve) geom); + } else if (geom instanceof IShape) { + return getIntersections(curve, (IShape) geom); + } else if (geom instanceof IPolyCurve) { + return getIntersections(curve, (IPolyCurve) geom); + } else if (geom instanceof IPolyShape) { + return getIntersections(curve, (IPolyShape) geom); + } else { + throw new UnsupportedOperationException("Not yet implemented."); } + } - /** - * Checks if all y-components of this {@link BezierCurve}'s points have - * the same sign. - * - * Returns true if either all y-components are positive or all - * y-components are negative. - * - * Returns false, otherwise. - * - * @param c - * @return true if all y-components are either positive or negative, - * otherwise false - */ - private static boolean sameSign(BezierCurve c) { - double sign = c.points[0].toPoint().y; - if (sign == 0) { - return false; - } - for (int i = 1; i < c.points.length; i++) { - if (sign < 0) { - if (c.points[i].toPoint().y >= 0) { - return false; - } - } else if (sign > 0) { - if (c.points[i].toPoint().y <= 0) { - return false; - } + /** + * Delegates to one of the above getIntersections() methods. + * + * @param geom1 + * @param geom2 + * @return points of intersection + */ + public static Point[] getIntersections(IGeometry geom1, IGeometry geom2) { + if (geom1 instanceof ICurve) { + return getIntersections((ICurve) geom1, geom2); + } else { + Set intersections = new HashSet(); + + if (geom1 instanceof IPolyCurve) { + for (ICurve curve : ((IPolyCurve) geom1).getCurves()) { + intersections.addAll(Arrays.asList(getIntersections(curve, + geom2))); } - } - return true; - } - - /** - * Used to store parameter values in a HashSet with an imprecise - * equals() operation. - * - * @author wienand - */ - private static final class ImpreciseDouble { - private double value; - private int shift; - - public ImpreciseDouble(double a) { - value = a; - shift = 1; - } - - /** - * Returns the double value represented by this - * {@link ImpreciseDouble}. - * - * @return the double value represented by this - * {@link ImpreciseDouble} - */ - public double getValue() { - return value; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof ImpreciseDouble) { - ImpreciseDouble o = (ImpreciseDouble) obj; - return PrecisionUtils.equal(value, o.value, shift); + } else if (geom1 instanceof IShape) { + for (ICurve curve : ((IShape) geom1).getOutlineSegments()) { + intersections.addAll(Arrays.asList(getIntersections(curve, + geom2))); } - return false; - } - } - - /** - * Calculates the roots of the given explicit {@link BezierCurve} on the - * interval [a;b]. - * - * You can get an explicit {@link BezierCurve} from an arbitrary - * {@link BezierCurve} for either its x- or y-components using the - * {@link BezierCurve#getExplicitX()} or - * {@link BezierCurve#getExplicitY()} methods, respectively. - * - * @param c - * @param a - * start of the parameter interval - * @param b - * end of the parameter interval - * @return the roots of the given explicit {@link BezierCurve} on the - * interval [a;b] - */ - private static HashSet getRoots(BezierCurve c, - double a, double b) { - BezierCurve clipped = c.getClipped(a, b); - - if (sameSign(clipped)) { - return new HashSet(); - } - - if (PrecisionUtils.equal(a, b, +2)) { - HashSet root = new HashSet(); - root.add(new ImpreciseDouble((a + b) / 2)); - return root; - } - - HashSet left = getRoots(c, a, (a + b) / 2); - HashSet right = getRoots(c, (a + b) / 2, b); - - left.addAll(right); - - return left; - } - - /** - * Calculates the roots of the given {@link BezierCurve} which is - * expected to be explicit. - * - * You can get an explicit {@link BezierCurve} from an arbitrary - * {@link BezierCurve} for either its x- or y-components using the - * {@link BezierCurve#getExplicitX()} or - * {@link BezierCurve#getExplicitY()} methods, respectively. - * - * @param c - * @return the roots of the given explicit {@link BezierCurve} - */ - private static double[] getRoots(BezierCurve c) { - // TODO: check that the given BezierCurve is explicit - HashSet roots = getRoots(c, 0, 1); - ImpreciseDouble[] rootsFuzzyDouble = roots - .toArray(new ImpreciseDouble[] {}); - double[] rootsDouble = new double[rootsFuzzyDouble.length]; - for (int i = 0; i < rootsDouble.length; i++) { - rootsDouble[i] = rootsFuzzyDouble[i].getValue(); - } - return rootsDouble; - } - - /** - * Computes all real roots of this {@link BezierCurve}'s x(t) function. - * - * @return all real roots of this {@link BezierCurve}'s x(t) function - */ - public double[] getRootsX() { - return getRoots(getExplicitX()); - } - - /** - * Computes all real roots of this {@link BezierCurve}'s y(t) function. - * - * @return all real roots of this {@link BezierCurve}'s y(t) function - */ - public double[] getRootsY() { - return getRoots(getExplicitY()); - } - - /** - * Computes the real planar {@link Point}s for this {@link BezierCurve}. - * - * @return the real planar {@link Point}s for this {@link BezierCurve} - */ - public Point[] getRealPoints() { - Point[] realPoints = new Point[points.length]; - for (int i = 0; i < points.length; i++) { - realPoints[i] = points[i].toPoint(); - } - return realPoints; - } - - /** - * Returns the {@link Point} at the given parameter value t. - * - * @param t - * Parameter value - * @return {@link Point} at parameter value t - */ - public Vector3D get(double t) { - if (t < 0 || t > 1) { - throw new IllegalArgumentException("t out of range"); - } - - // using horner's scheme: - int n = points.length; - if (n < 1) { - return null; - } - - double bn = 1, tn = 1, d = 1d - t; - Vector3D pn = points[0].getScaled(bn * tn); - for (int i = 1; i < n; i++) { - bn = bn * (n - i) / i; - tn = tn * t; - pn = pn.getScaled(d).getAdded(points[i].getScaled(bn * tn)); - } - - return pn; - } - - /** - * Creates a new {@link BezierCurve} with all points translated by the - * given {@link Point}. - * - * @param p - * @return a new {@link BezierCurve} with all points translated by the - * given {@link Point} - */ - public BezierCurve getTranslated(Point p) { - Point[] translated = new Point[points.length]; - - for (int i = 0; i < translated.length; i++) { - translated[i] = points[i].toPoint().getTranslated(p); - } - - return new BezierCurve(translated); - } - - /** - * Returns true if the given {@link Point} lies on this - * {@link BezierCurve}. Returns false, otherwise. - * - * @param p - * the {@link Point} to test for containment - * @return true if the {@link Point} is contained, false otherwise - */ - public boolean contains(Point p) { - if (p == null) { - return false; - } - - BezierCurve test = this.getTranslated(p.getNegated()); - double[] xts = test.getRootsX(); - double[] yts = test.getRootsY(); - - for (double xt : xts) { - for (double yt : yts) { - if (PrecisionUtils.equal(xt, yt)) { - return true; - } else { - Point qx = get(xt).toPoint(); - Point qy = get(yt).toPoint(); - // qx != null && qy != null && - if (qx.equals(qy)) { - return true; - } + } else if (geom1 instanceof IPolyShape) { + for (IShape shape : ((IPolyShape) geom1).getShapes()) { + for (ICurve curve : shape.getOutlineSegments()) { + intersections.addAll(Arrays.asList(getIntersections( + curve, geom2))); } } + } else { + throw new UnsupportedOperationException("Not yet implemented."); } - return false; - } - - /** - * Subdivides this {@link BezierCurve} at the given parameter value t - * into two new {@link BezierCurve}. The left-of t and the right-of t - * {@link BezierCurve} objects. - * - * @param t - * Parameter value - * @return The left-of t and right-of t {@link BezierCurve} objects - */ - public BezierCurve[] split(double t) { - Vector3D[] leftPoints = new Vector3D[points.length]; - Vector3D[] rightPoints = new Vector3D[points.length]; - - Vector3D[] ratioPoints = getPointsCopy(); - - for (int i = 0; i < points.length; i++) { - leftPoints[i] = ratioPoints[0]; - rightPoints[points.length - 1 - i] = ratioPoints[points.length - - 1 - i]; - - for (int j = 0; j < points.length - i - 1; j++) { - ratioPoints[j] = ratioPoints[j].getRatio( - ratioPoints[j + 1], t); - } - } - - return new BezierCurve[] { new BezierCurve(leftPoints), - new BezierCurve(rightPoints) }; - } - /** - * Returns a new {@link BezierCurve} object representing this bezier - * curve on the interval [s;e]. - * - * @param s - * @param e - * @return a new {@link BezierCurve} object representing this bezier - * curve on the interval [s;e] - */ - public BezierCurve getClipped(double s, double e) { - BezierCurve right = split(s)[1]; - double rightT2 = (e - s) / (1 - s); - return right.split(rightT2)[0]; + return intersections.toArray(new Point[] {}); } + } - /** - * Checks if the parameters are considered equal on both curves and adds - * the point of intersection of the mid lines of the curves. - * - * @param p - * @param q - * @param intersections - * @return true if the parameters are considered equal and false - * otherwise - */ - private static Vector3D parameterConvergence(BezierCurve p, double ps, - double pe, BezierCurve q, double qs, double qe) { - // localEndPointsCheck(); - if (PrecisionUtils.equal(ps, pe, +2)) { - Vector3D poi = p.get((ps + pe) / 2); - return poi; - } - if (PrecisionUtils.equal(qs, qe, +2)) { - Vector3D poi = q.get((qs + qe) / 2); - return poi; - } - return null; - } - - /** - * Returns the bounds of the control polygon of this {@link BezierCurve} - * . - * - * @return a {@link Rectangle} representing the bounds of the control - * polygon of this {@link BezierCurve} - */ - public Rectangle getControlBounds() { - Point[] realPoints = getRealPoints(); - - double xmin = realPoints[0].x, xmax = realPoints[0].x, ymin = realPoints[0].y, ymax = realPoints[0].y; - - for (int i = 1; i < realPoints.length; i++) { - if (realPoints[i].x < xmin) { - xmin = realPoints[i].x; - } else if (realPoints[i].x > xmax) { - xmax = realPoints[i].x; - } + /** + * Tests the given {@link ICurve}s for a finite number of intersections. + * Returns true if the given {@link ICurve}s have a finite + * number of intersection points. Otherwise, returns false. + * + * @param c1 + * @param c2 + * @return true if both {@link ICurve}s have a finite set of + * intersection points, otherwise false + */ + public static boolean intersects(ICurve c1, ICurve c2) { + return getIntersections(c1, c2).length > 0; + } - if (realPoints[i].y < ymin) { - ymin = realPoints[i].y; - } else if (realPoints[i].y > ymax) { - ymax = realPoints[i].y; + /** + * Tests the given {@link ICurve}s for an infinite number of intersections. + * Returns true if the given {@link ICurve}s have an infinite + * number of intersection points. Otherwise, returns false. + * + * @param c1 + * @param c2 + * @return true if both {@link ICurve}s have an infinite set of + * intersection points, otherwise false + */ + public static boolean overlaps(ICurve c1, ICurve c2) { + for (BezierCurve seg1 : c1.toBezier()) { + for (BezierCurve seg2 : c2.toBezier()) { + if (seg1.overlaps(seg2)) { + return true; } } - - return new Rectangle(xmin, ymin, xmax - xmin, ymax - ymin); - } - - /** - * Generates the difference points of this {@link BezierCurve} to the - * given line. - * - * The difference points are the control points of a Bezier curve that - * yields the signed difference of the point on this curve at a - * determinate parameter value to the given line. - * - * @param line - * @return the difference curve's control points - */ - private Vector3D[] genDifferencePoints(Vector3D line) { - Vector3D[] D = new Vector3D[points.length]; - for (int i = 0; i < points.length; i++) { - double y = line.getDot(points[i]); - D[i] = new Vector3D( - (double) (i) / (double) (points.length - 1), y, 1); - } - return D; - } - - public Set getIntersections(BezierCurve other) { - // end point intersections - Set endPointIntersections = getEndPointIntersections( - this, other); - - // TODO: tangential intersections - - // simple intersections - // TODO: recursion => iteration - Set intersections = getIntersections( - endPointIntersections, this, 0, 1, other, 0, 1); - - intersections.addAll(endPointIntersections); - return intersections; } + return false; + } - private Set getEndPointIntersections(BezierCurve p, - BezierCurve q) { - Set intersections = new HashSet(); - for (int i : new int[] { 0, p.points.length - 1 }) { - if (q.contains(p.points[i].toPoint())) { - intersections.add(p.points[i]); - } - } - for (int i : new int[] { 0, q.points.length - 1 }) { - if (p.contains(q.points[i].toPoint())) { - intersections.add(q.points[i]); - } - } - return intersections; + /** + *

+ * Tests if the given {@link BezierCurve} is fully contained by the given + * {@link IShape}. Returns true if the given + * {@link BezierCurve} is fully contained by the given {@link IShape}. + * Otherwise, returns false. + *

+ * + *

+ * At first, the algorithm checks if start and end {@link Point} of the + * {@link BezierCurve} are contained by the {@link IShape}. If this is not + * the case, false is returned. + *

+ * + *

+ * Subsequently, the {@link Point}s of intersection of the + * {@link BezierCurve} and the {@link IShape} are computed. If there are + * less then two intersection {@link Point}s, true is returned. + *

+ * + *

+ * Alternatively, the {@link BezierCurve}'s parameter values for the + * individual {@link Point}s of intersection are sorted. For every two + * consecutive parameter values, a {@link Point} on the {@link BezierCurve} + * between those two parameter values is computed. If any of those + * {@link Point}s is not contained by the {@link IShape}, false + * is returned. Otherwise, true is returned. + *

+ * + *

+ * Self-intersection-problem: If the {@link BezierCurve} has a + * self-intersection p and p lies on an outline segment of the + * {@link IShape} ( {@link IShape#getOutlineSegments()}), true + * is returned, although false would be the right answer. + *

+ * + * @param shape + * the {@link IShape} that is tested to contain the given + * {@link BezierCurve} + * @param c + * the {@link BezierCurve} that is tested to be contained by the + * given {@link IShape} + * @return true if the given {@link BezierCurve} is fully + * contained by the given {@link IShape} + */ + public static boolean contains(IShape shape, BezierCurve c) { + if (!(shape.contains(c.getP1()) && shape.contains(c.getP2()))) { + return false; } - private static class FatLine { - public Vector3D line; - public double dmin, dmax; - - private FatLine() { - line = new Vector3D(0, 0, 0); - dmin = dmax = 0; - } - - public static FatLine from(BezierCurve c, boolean ortho) { - FatLine L = new FatLine(); - L.dmin = L.dmax = 0; - - L.line = c.points[0].getCrossed(c.points[c.points.length - 1]); + Set intersectionParams = new HashSet(); - if (ortho) { - L.line = c.points[0].getCrossed(c.points[0] - .getAdded(new Vector3D(L.line.x, L.line.y, 0))); + for (ICurve segC : shape.getOutlineSegments()) { + for (BezierCurve seg : segC.toBezier()) { + Set inters = new HashSet(); + Set ips = c.getIntersectionIntervalPairs( + new BezierCurve(seg.getP1(), seg.getP2()), inters); + for (IntervalPair ip : ips) { + intersectionParams.add(ip.p == c ? ip.pi.getMid() : ip.qi + .getMid()); } - - L.line = L.line.getLineNormalized(); - - if (L.line == null) { - return null; + for (Point poi : inters) { + intersectionParams.add(c.getParameterAt(poi)); } - - for (int i = 0; i < c.points.length; i++) { - double d = L.line.getDot(c.points[i]); - if (d < L.dmin) - L.dmin = d; - else if (d > L.dmax) - L.dmax = d; - } - - return L; - } - } - - private static double intersectXAxisParallel(Point p, Point q, double y) { - // p.y != q.y because this routine is only called when either the - // lower or the higher fat line bound is crossed. - return new Vector3D(p).getCrossed(new Vector3D(q)) - .getCrossed(new Vector3D(0, 1, -y)).toPoint().x; - // double dy = q.y - p.y; - // double s = (y - p.y) / dy; - // return (q.x - p.x) * s + p.x; - } - - private static Point[] getConvexHull(Vector3D[] points) { - Point[] chPoints = new Point[points.length]; - for (int i = 0; i < points.length; i++) { - chPoints[i] = points[i].toPoint(); - } - return PointListUtils.getConvexHull(chPoints); - } - - /** - * @param L - * @return - */ - private double[] clipTo(FatLine L) { - double[] interval = new double[] { 1, 0 }; - - Point[] D = getConvexHull(genDifferencePoints(L.line)); - - // we do not know which point is returned first by the - // getConvexHull() method. That's why we have to check the "first" - // point, too. - boolean isBelow = D[0].y < L.dmin; - boolean isAbove = D[0].y > L.dmax; - insideFatLineCheck(interval, D, 0, isBelow, isAbove); - - boolean wasBelow = isBelow, wasAbove = isAbove; - - for (int i = 1; i < D.length; i++) { - isBelow = D[i].y < L.dmin; - isAbove = D[i].y > L.dmax; - - insideFatLineCheck(interval, D, i, isBelow, isAbove); - wasBelow = belowFatLineCheck(interval, L, D, i - 1, i, isBelow, - wasBelow); - wasAbove = aboveFatLineCheck(interval, L, D, i - 1, i, isAbove, - wasAbove); - } - - // closing segment - isBelow = D[0].y < L.dmin; - isAbove = D[0].y > L.dmax; - belowFatLineCheck(interval, L, D, D.length - 1, 0, isBelow, - wasBelow); - aboveFatLineCheck(interval, L, D, D.length - 1, 0, isAbove, - wasAbove); - - return interval; - } - - private boolean aboveFatLineCheck(double[] interval, FatLine L, - Point[] D, int i, int j, boolean isAbove, boolean wasAbove) { - if (isAbove != wasAbove) { - // crosses higher - double x = intersectXAxisParallel(D[i], D[j], L.dmax); - moveInterval(interval, x); - wasAbove = isAbove; - } - return wasAbove; - } - - private boolean belowFatLineCheck(double[] interval, FatLine L, - Point[] D, int i, int j, boolean isBelow, boolean wasBelow) { - if (isBelow != wasBelow) { - // crosses lower - double x = intersectXAxisParallel(D[i], D[j], L.dmin); - moveInterval(interval, x); - wasBelow = isBelow; } - return wasBelow; } - private void insideFatLineCheck(double[] interval, Point[] D, int i, - boolean isBelow, boolean isAbove) { - if (!(isBelow || isAbove)) { - // inside - moveInterval(interval, D[i].x); - } - } - - private void moveInterval(double[] interval, double x) { - if (interval[0] > x) - interval[0] = x; - if (interval[1] < x) - interval[1] = x; - } - - /** - * Computes and returns the points of intersection between this - * {@link BezierCurve} and the given other {@link BezierCurve}. + /* + * Start and end point of the curve are guaranteed to lie inside the + * IShape. If the curve would not be contained by the shape, at least + * two intersections could be found. * - * @param endPointIntersections - * all points of intersections that are end-points of one of - * the curves - * @param p - * first {@link BezierCurve} - * @param ps - * start value of the first {@link BezierCurve}'s parameter - * interval - * @param pe - * end value of the first {@link BezierCurve}'s parameter - * interval - * @param q - * second {@link BezierCurve} - * @param qs - * start value of the second {@link BezierCurve}'s parameter - * interval - * @param qe - * end value of the second {@link BezierCurve}'s parameter - * interval - * @return the intersections between this {@link BezierCurve} and the - * given other {@link BezierCurve} + * TODO: Special case! There is a special case where the Bezier curve + * leaves and enters the shape in the same point. This is only possible + * if the Bezier curve has a self intersections at that point. */ - public static Set getIntersections( - Set endPointIntersections, BezierCurve p, double ps, - double pe, BezierCurve q, double qs, double qe) { - BezierCurve pClipped = p.getClipped(ps, pe); - BezierCurve qClipped = q.getClipped(qs, qe); - - // end point intersection check - if (endPointIntersectionConvergence(endPointIntersections, - pClipped, ps, pe, qClipped, qs, qe)) { - Set no_intersections = new HashSet(0); - return no_intersections; - } - - // TODO: tangential intersection check - - // parameter convergence check - Vector3D poi = parameterConvergence(p, ps, pe, q, qs, qe); - if (poi != null) { - // "exactly" one intersection - if (p.contains(poi.toPoint()) && q.contains(poi.toPoint())) { - Set intersection = new HashSet(1); - intersection.add(poi); - return intersection; - } - Set no_intersections = new HashSet(0); - return no_intersections; - } - - Set intersections = new HashSet(); - - // construct "parallel" and "orthogonal" fat lines - FatLine L1 = FatLine.from(qClipped, false); - FatLine L2 = FatLine.from(qClipped, true); - - // curve implosion check - if (L1 == null || L2 == null) { - // qClipped is too small to construct a fat line from it - // therefore, return its mid-point if it is contained by the - // other curve - Vector3D mid = q.get((qs + qe) / 2); - if (p.contains(mid.toPoint())) { - Set intersection = new HashSet(1); - intersection.add(mid); - return intersection; - } - Set no_intersections = new HashSet(0); - return no_intersections; - } - - // clip to the fat lines - double[] interval = pClipped.clipTo(L1); - double[] interval_ortho = pClipped.clipTo(L2); + if (intersectionParams.size() <= 1) { + return true; + } - // pick smaller interval range - if ((interval[1] - interval[0]) > (interval_ortho[1] - interval_ortho[0])) { - interval[0] = interval_ortho[0]; - interval[1] = interval_ortho[1]; + Double[] poiParams = intersectionParams.toArray(new Double[] {}); + Arrays.sort(poiParams, new Comparator() { + public int compare(Double t, Double u) { + double d = t - u; + return d < 0 ? -1 : d > 0 ? 1 : 0; } + }); - // re-calculate s and e from the clipped interval - double news = ps + interval[0] * (pe - ps); - double newe = ps + interval[1] * (pe - ps); - double ratio = (newe - news) / (pe - ps); - ps = news; - pe = newe; - - if (ratio < 0) { - // no more intersections - return intersections; - } else if (ratio > 0.8) { - // split longer curve and find intersections for both halves - if ((pe - ps) > (qe - qs)) { - double pm = (ps + pe) / 2; - intersections.addAll(getIntersections( - endPointIntersections, p, ps, pm, q, qs, qe)); - intersections.addAll(getIntersections( - endPointIntersections, p, pm - + UNRECOGNIZABLE_PRECISION_FRACTION, pe, q, - qs, qe)); - } else { - double qm = (qs + qe) / 2; - intersections.addAll(getIntersections( - endPointIntersections, q, qs, qm, p, ps, pe)); - intersections.addAll(getIntersections( - endPointIntersections, q, qm - + UNRECOGNIZABLE_PRECISION_FRACTION, qe, p, - ps, pe)); - } - - return intersections; - } else { - // clipped more than 20% - return getIntersections(endPointIntersections, q, qs, qe, p, - ps, pe); - } + // check the points between the intersections for containment + if (!shape.contains(c.get(poiParams[0] / 2))) { + return false; } - - private static boolean endPointIntersectionConvergence( - Set endPointIntersections, BezierCurve pClipped, - double ps, double pe, BezierCurve qClipped, double qs, double qe) { - if (PrecisionUtils.equal(ps, pe, -3)) { - if (endPointIntersections.contains(pClipped.points[0]) - || endPointIntersections - .contains(pClipped.points[pClipped.points.length - 1])) { - return true; - } - } - if (PrecisionUtils.equal(qs, qe, -3)) { - if (endPointIntersections.contains(qClipped.points[0]) - || endPointIntersections - .contains(qClipped.points[qClipped.points.length - 1])) { - return true; - } + for (int i = 0; i < poiParams.length - 1; i++) { + if (!shape.contains(c.get((poiParams[i] + poiParams[i + 1]) / 2))) { + return false; } - return false; } + return shape.contains(c.get((poiParams[poiParams.length - 1] + 1) / 2)); } /** - * Computes and returns the points of intersection between two - * {@link CubicCurve}s. + * Returns true if the given {@link IShape} fully contains the + * given {@link ICurve}. Otherwise, false is returned. A + * {@link ICurve} is contained by a {@link IShape} if the {@link ICurve}'s + * Bezier approximation ({@link ICurve#toBezier()}) is contained by the + * {@link IShape} ({@link CurveUtils#contains(IShape, BezierCurve)}). * - * @param p - * the first {@link CubicCurve} to intersect - * @param q - * the second {@link CubicCurve} to intersect - * @return the intersections between two {@link CubicCurve}s + * @param shape + * the {@link IShape} that is tested to contain the + * {@link ICurve} + * @param curve + * the {@link ICurve} that is tested to be contained by the + * {@link IShape} + * @return true if the given {@link IShape} contains the + * {@link ICurve}, otherwise false */ - public static Point[] getIntersections(CubicCurve p, CubicCurve q) { - Set intersections = new BezierCurve(p) - .getIntersections(new BezierCurve(q)); - - Set pois = new HashSet(); - for (CurveUtils.Vector3D poi : intersections) { - if (poi.z != 0) { - pois.add(poi.toPoint()); + public static boolean contains(IShape shape, ICurve curve) { + for (BezierCurve seg : curve.toBezier()) { + if (!contains(shape, seg)) { + return false; } } - - return pois.toArray(new Point[] {}); + return true; } /** - * Computes the {@link Point} of intersection of two {@link Straight}s. + * Returns true if the second {@link IShape} is fully contained + * by the first {@link IShape}. Otherwise, false is returned. * - * @param s1 - * the first {@link Straight} to test for intersection - * @param s2 - * the second {@link Straight} to test for intersection - * @return the {@link Point} of intersection if it exists, null - * otherwise - */ - public static Point getIntersection(Straight s1, Straight s2) { - Vector3D l1 = new Vector3D(s1.position.toPoint()) - .getCrossed(new Vector3D(s1.position.getAdded(s1.direction) - .toPoint())); - Vector3D l2 = new Vector3D(s2.position.toPoint()) - .getCrossed(new Vector3D(s2.position.getAdded(s2.direction) - .toPoint())); - - return l1.getCrossed(l2).toPoint(); - } - - /** - * Computes the signed distance of the third {@link Point} to the line - * through the first two {@link Point}s. - * - * The signed distance is positive if the three {@link Point}s are in - * counter-clockwise order and negative if the {@link Point}s are in - * clockwise order. It is zero if the third {@link Point} lies on the line. - * - * If the first two {@link Point}s do not form a line (i.e. they are equal) - * this function returns the distance of the first and the last - * {@link Point}. + * A {@link IShape} is contained by another {@link IShape} if all of its + * outline segments ({@link IShape#getOutlineSegments()}) are contained by + * the other {@link IShape} ({@link CurveUtils#contains(IShape, ICurve)}). * - * @param p - * the start-{@link Point} of the line - * @param q - * the end-{@link Point} of the line - * @param r - * the relative {@link Point} to test for - * @return the signed distance of {@link Point} r to the line through - * {@link Point}s p and q + * @param shape1 + * the {@link IShape} that is tested to contain the other + * {@link IShape} + * @param shape2 + * the {@link IShape} that is tested to be contained by the other + * {@link IShape} + * @return true if the second {@link IShape} is contained by + * the first {@link IShape}, otherwise false */ - public static double getSignedDistance(Point p, Point q, Point r) { - Vector3D normalizedLine = new Vector3D(p).getCrossed(new Vector3D(q)) - .getLineNormalized(); - - if (normalizedLine == null) { - return p.getDistance(r); + public static boolean contains(IShape shape1, IShape shape2) { + for (ICurve seg : shape2.getOutlineSegments()) { + if (!contains(shape1, seg)) { + return false; + } } - - double dot = normalizedLine.getDot(new Vector3D(r)); - return -dot; + return true; } /** - * Returns the signed distance of the {@link Point} to the {@link Straight}. - * - * {@link Point}s that are to the left of the {@link Straight} in the - * direction of the {@link Straight}'s direction vector have a positive - * distance whereas {@link Point}s to the right of the {@link Straight} in - * the direction of the {@link Straight}'s direction vector have a negative - * distance. + * Returns true if the given {@link IPolyCurve} is fully + * contained by the given {@link IShape}. Otherwise, false is + * returned. * - * The absolute value of the signed distance is the actual distance of the - * {@link Point} to the {@link Straight}. + * A {@link IPolyCurve} is contained by a {@link IShape} if all of its sub- + * {@link ICurve}s are contained by the shape (see + * {@link IPolyCurve#getCurves()} and + * {@link CurveUtils#contains(IShape, ICurve)}). * - * @param s - * @param p - * @return the signed distance of the {@link Point} to the {@link Straight} - * - * @see CurveUtils#getSignedDistance(Point, Point, Point) + * @param shape + * the {@link IShape} that is tested to contain the + * {@link IPolyCurve} + * @param polyCurve + * the {@link IPolyCurve} that is tested to be contained by the + * {@link IShape} + * @return true if the {@link IShape} contains the + * {@link IPolyCurve}, otherwise false */ - public static double getSignedDistance(Straight s, Point p) { - return getSignedDistance(s.position.toPoint(), - s.position.getAdded(s.direction).toPoint(), p); + public static boolean contains(IShape shape, IPolyCurve polyCurve) { + for (ICurve seg : polyCurve.getCurves()) { + if (!contains(shape, seg)) { + return false; + } + } + return true; } /** - * Tests if the given {@link Point} lies on the given {@link CubicCurve}. + * Returns true if the given {@link IShape} fully contains the + * given {@link IPolyShape}. Otherwise, false is returned. * - * @param c - * the {@link CubicCurve} to test - * @param p - * the {@link Point} to test for containment - * @return true if the {@link Point} lies on the {@link CubicCurve}, false - * otherwise - */ - public static boolean contains(CubicCurve c, Point p) { - return new BezierCurve(c).contains(p); - } - - /** - * Tests if the given {@link Point} lies on the given {@link QuadraticCurve} - * . + * A {@link IPolyShape} is contained by a {@link IShape} if all of its sub- + * {@link IShape}s are contained by the {@link IShape} (see + * {@link IPolyShape#getShapes()} and + * {@link CurveUtils#contains(IShape, IShape)}). * - * @param c - * the {@link QuadraticCurve} to test - * @param p - * the {@link Point} to test for containment - * @return true if the {@link Point} lies on the {@link QuadraticCurve}, - * false otherwise + * @param shape + * the {@link IShape} that is tested to contain the + * {@link IPolyShape} + * @param polyShape + * the {@link IPolyShape} that is tested to be contained by the + * {@link IShape} + * @return true if the {@link IShape} contains the + * {@link IPolyShape}, otherwise false */ - public static boolean contains(QuadraticCurve c, Point p) { - return new BezierCurve(c).contains(p); + public static boolean contains(IShape shape, IPolyShape polyShape) { + for (IShape seg : polyShape.getShapes()) { + if (!contains(shape, seg)) { + return false; + } + } + return true; } /** - * Subdivides the given {@link CubicCurve} at parameter value t in the left - * and right sub-curves. + * Returns true if the given {@link IShape} fully contains the + * given {@link IGeometry}. Otherwise, false is returned. * - * @param c - * the {@link CubicCurve} to subdivide - * @param t - * the parameter value to subdivide at - * @return the left and right sub-curves as an array of {@link CubicCurve} - */ - public static CubicCurve[] split(CubicCurve c, double t) { - BezierCurve[] split = new BezierCurve(c).split(t); - return new CubicCurve[] { new CubicCurve(split[0].getRealPoints()), - new CubicCurve(split[1].getRealPoints()) }; - } - - /** - * Subdivides the given {@link QuadraticCurve} at parameter value t in the - * left and right sub-curves. + * An instanceof test delegates to the appropriate method. * - * @param c - * the {@link QuadraticCurve} to subdivide - * @param t - * the parameter value to subdivide at - * @return the left and right sub-curves as an array of - * {@link QuadraticCurve} + * @see CurveUtils#contains(IShape, ICurve) + * @see CurveUtils#contains(IShape, IPolyCurve) + * @see CurveUtils#contains(IShape, IShape) + * @see CurveUtils#contains(IShape, IPolyShape) + * @param shape + * the {@link IShape} that is tested to contain the + * {@link IGeometry} + * @param geom + * the {@link IGeometry} that is tested to be contained by the + * {@link IShape} + * @return true if the {@link IShape} contains the + * {@link IGeometry}, otherwise false */ - public static QuadraticCurve[] split(QuadraticCurve c, double t) { - BezierCurve[] split = new BezierCurve(c).split(t); - return new QuadraticCurve[] { - new QuadraticCurve(split[0].getRealPoints()), - new QuadraticCurve(split[1].getRealPoints()) }; + public static boolean contains(IShape shape, IGeometry geom) { + if (geom instanceof ICurve) { + return contains(shape, (ICurve) geom); + } else if (geom instanceof IPolyCurve) { + return contains(shape, (IPolyCurve) geom); + } else if (geom instanceof IShape) { + return contains(shape, (IShape) geom); + } else if (geom instanceof IPolyShape) { + return contains(shape, (IPolyShape) geom); + } else { + throw new UnsupportedOperationException("Not yet implemented."); + } } /** - * Returns a new {@link QuadraticCurve} that represents the given - * {@link QuadraticCurve} on the parameter interval [t1;t2]. + * Returns true if the second {@link IGeometry} is fully + * contained by the first {@link IGeometry}. Otherwise, false + * is returned. * - * @param c - * the {@link QuadraticCurve} to clip - * @param t1 - * lower parameter bound - * @param t2 - * upper parameter bound - * @return a new {@link QuadraticCurve} that represents the given - * {@link QuadraticCurve} on the interval [t1;t2] + * @param geom1 + * @param geom2 + * @return true if the first {@link IGeometry} contains the + * second {@link IGeometry}, otherwise false */ - public static QuadraticCurve clip(QuadraticCurve c, double t1, double t2) { - BezierCurve bc = new BezierCurve(c); - return new QuadraticCurve(bc.getClipped(t1, t2).getRealPoints()); + public static boolean contains(IGeometry geom1, IGeometry geom2) { + if (geom1 instanceof IShape) { + return contains((IShape) geom1, geom2); + } else if (geom1 instanceof IPolyShape) { + throw new UnsupportedOperationException("Not yet implemented."); + } else { + return false; + } } /** - * Returns a new {@link CubicCurve} that represents the given - * {@link CubicCurve} on the parameter interval [t1;t2]. + * Returns a {@link PolyBezier} that is constructed from the outline + * segments ( {@link IShape#getOutlineSegments()}) of the given + * {@link IShape}. * - * @param c - * the {@link CubicCurve} to clip - * @param t1 - * lower parameter bound - * @param t2 - * upper parameter bound - * @return a new {@link CubicCurve} that represents the given - * {@link CubicCurve} on the interval [t1;t2] + * @param shape + * the {@link IShape} to compute the outline for + * @return a {@link PolyBezier} that is constructed from the outline + * segments of the {@link IShape} */ - public static CubicCurve clip(CubicCurve c, double t1, double t2) { - BezierCurve bc = new BezierCurve(c); - return new CubicCurve(bc.getClipped(t1, t2).getRealPoints()); - } + public static PolyBezier getOutline(IShape shape) { + ICurve[] curves = shape.getOutlineSegments(); - /** - * Evaluates the {@link Point} on the given {@link CubicCurve} at parameter - * value t. - * - * @param c - * the {@link CubicCurve} - * @param t - * the parameter value - * @return the {@link Point} on the given {@link CubicCurve} at parameter - * value t - */ - public static Point get(CubicCurve c, double t) { - return new BezierCurve(c).get(t).toPoint(); - } + ArrayList beziers = new ArrayList( + curves.length); - /** - * Evaluates the {@link Point} on the given {@link QuadraticCurve} at - * parameter value t. - * - * @param c - * the {@link QuadraticCurve} - * @param t - * the parameter value - * @return the {@link Point} on the given {@link QuadraticCurve} at - * parameter value t - */ - public static Point get(QuadraticCurve c, double t) { - return new BezierCurve(c).get(t).toPoint(); - } + for (ICurve c : curves) { + for (BezierCurve b : c.toBezier()) { + beziers.add(b); + } + } - /** - * Computes and returns the bounds of the control polygon of the given - * {@link CubicCurve}. The control polygon is the convex hull of the start-, - * end-, and control points of the {@link CubicCurve}. - * - * @param c - * the {@link CubicCurve} to compute the control bounds for - * @return the bounds of the control polygon of the given {@link CubicCurve} - */ - public static Rectangle getControlBounds(CubicCurve c) { - return new BezierCurve(c).getControlBounds(); + return new PolyBezier(beziers.toArray(new BezierCurve[] {})); } } diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/utils/PointListUtils.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/utils/PointListUtils.java index 410af5c..5911ef9 100644 --- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/utils/PointListUtils.java +++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/utils/PointListUtils.java @@ -7,6 +7,7 @@ * * Contributors: * Alexander Nyßen (itemis AG) - initial API and implementation + * Matthias Wienand (itemis AG) - contribution for Bugzilla #355997 * *******************************************************************************/ package org.eclipse.gef4.geometry.utils; @@ -16,6 +17,7 @@ import java.util.Arrays; import java.util.Comparator; import org.eclipse.gef4.geometry.Point; +import org.eclipse.gef4.geometry.euclidean.Straight; import org.eclipse.gef4.geometry.planar.Line; import org.eclipse.gef4.geometry.planar.Polygon; import org.eclipse.gef4.geometry.planar.Polyline; @@ -87,7 +89,7 @@ public class PointListUtils { * @return a new array, which contains copies of the given {@link Point}s at * the respective index positions */ - public static final Point[] getCopy(Point[] points) { + public static final Point[] copy(Point[] points) { Point[] copy = new Point[points.length]; for (int i = 0; i < points.length; i++) { copy[i] = points[i].getCopy(); @@ -102,7 +104,7 @@ public class PointListUtils { * the array of coordinates to copy * @return a new array containing identical coordinates */ - public static final double[] getCopy(double[] coordinates) { + public static final double[] copy(double[] coordinates) { double[] copy = new double[coordinates.length]; for (int i = 0; i < coordinates.length; i++) { copy[i] = coordinates[i]; @@ -158,8 +160,8 @@ public class PointListUtils { for (int i = 3; i < points.length; i++) { // do always turn right while (stack.size() > 2 - && CurveUtils.getSignedDistance(stack.get(1), stack.get(0), - points[i]) > 0) { + && Straight.getSignedDistanceCCW(stack.get(1), + stack.get(0), points[i]) > 0) { stack.remove(0); } stack.add(0, points[i]); @@ -290,4 +292,35 @@ public class PointListUtils { // this class should not be instantiated by clients } + /** + * Transforms a sequence of {@link Line}s into a list of {@link Point}s. + * Consecutive {@link Line}s are expected to share one of their end + * {@link Point}s. The start {@link Point}s of the {@link Line}s are + * returned. Additionally, the end {@link Point} of the last {@link Line} is + * returned, too if the given boolean flag open is set to + * false. + * + * @param segmentsArray + * @param open + * indicates whether to omit the end {@link Point} of the last + * {@link Line} + * @return the start {@link Point}s of the {@link Line}s and the end + * {@link Point} of the last {@link Line} according to + * open + */ + public static Point[] toPointsArray(Line[] segmentsArray, boolean open) { + Point[] points = new Point[segmentsArray.length + (open ? 0 : 1)]; + + for (int i = 0; i < segmentsArray.length; i++) { + points[i] = segmentsArray[i].getP1(); + } + + if (!open) { + points[points.length - 1] = segmentsArray[segmentsArray.length - 1] + .getP2(); + } + + return points; + } + } -- 1.7.4.1