Download
Getting Started
Members
Projects
Community
Marketplace
Events
Planet Eclipse
Newsletter
Videos
Participate
Report a Bug
Forums
Mailing Lists
Wiki
IRC
How to Contribute
Working Groups
Automotive
Internet of Things
LocationTech
Long-Term Support
PolarSys
Science
OpenMDM
More
Community
Marketplace
Events
Planet Eclipse
Newsletter
Videos
Participate
Report a Bug
Forums
Mailing Lists
Wiki
IRC
How to Contribute
Working Groups
Automotive
Internet of Things
LocationTech
Long-Term Support
PolarSys
Science
OpenMDM
Toggle navigation
Bugzilla – Attachment 216482 Details for
Bug 355997
[GEF4] Provide new Geometry-API
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
Log In
[x]
|
Terms of Use
|
Copyright Agent
[patch]
Implements Region and Ring and adds transformation interfaces and their implementations
corrected.patch (text/plain), 569.53 KB, created by
Matthias Wienand
on 2012-05-30 10:49:38 EDT
(
hide
)
Description:
Implements Region and Ring and adds transformation interfaces and their implementations
Filename:
MIME Type:
Creator:
Matthias Wienand
Created:
2012-05-30 10:49:38 EDT
Size:
569.53 KB
patch
obsolete
>From 4c226b17be18ebb1c949a08370c7d30ce3f2f05b Mon Sep 17 00:00:00 2001 >From: mwienand <matthias.wienand@itemis.de> >Date: Sun, 27 May 2012 11:55:15 +0200 >Subject: [PATCH] [355997] IPolyShape implementations and transformation interfaces > >--- > org.eclipse.gef4.geometry.doc/javadocOptions.txt | 2 +- > .../image_src/IGeometry_Planar_Abstractions.PNG | Bin 85320 -> 0 bytes > .../image_src/IGeometry_Planar_Abstractions.ucls | 20 +- > .../image_src/IGeometry_Planar_Overview.PNG | Bin 74485 -> 0 bytes > .../image_src/IGeometry_Planar_Overview.ucls | 163 ++-- > .../reference/image_src/inheritance-hierarchy.ucls | 283 +++++ > .../reference/image_src/transform-overview.ucls | 44 + > .../examples/demos/BezierApproximationExample.java | 81 ++ > .../geometry/examples/demos/ConvexHullExample.java | 84 ++ > .../demos/CubicCurveDeCasteljauExample.java | 136 +++ > .../geometry/examples/demos/RegionExample.java | 100 ++ > .../examples/demos/RegionOutlineExample.java | 113 ++ > .../gef4/geometry/examples/demos/RingExample.java | 128 +++ > .../examples/demos/TriangulationExample.java | 109 ++ > .../intersection/AbstractIntersectionExample.java | 5 +- > .../intersection/BezierApproximationExample.java | 80 -- > .../examples/intersection/ConvexHullExample.java | 82 -- > .../intersection/CubicCurveDeCasteljauExample.java | 134 --- > .../scalerotate/CubicCurveScaleRotate.java | 62 ++ > .../org/eclipse/gef4/geometry/tests/AllTests.java | 14 +- > .../eclipse/gef4/geometry/tests/AngleTests.java | 2 + > .../gef4/geometry/tests/BezierCurveTests.java | 345 ++++++ > .../gef4/geometry/tests/CurveUtilsTests.java | 4 +- > .../gef4/geometry/tests/DimensionTests.java | 21 +- > .../eclipse/gef4/geometry/tests/EllipseTests.java | 170 +++- > .../org/eclipse/gef4/geometry/tests/LineTests.java | 8 + > .../eclipse/gef4/geometry/tests/PointTests.java | 4 + > .../eclipse/gef4/geometry/tests/PolygonTests.java | 23 + > .../eclipse/gef4/geometry/tests/PolylineTests.java | 22 +- > .../gef4/geometry/tests/RectangleTests.java | 36 +- > .../eclipse/gef4/geometry/tests/RegionTests.java | 103 ++ > .../org/eclipse/gef4/geometry/tests/RingTests.java | 1120 ++++++++++++++++++++ > .../gef4/geometry/tests/RoundedRectangleTests.java | 214 ++++ > .../eclipse/gef4/geometry/tests/StraightTests.java | 4 + > .../eclipse/gef4/geometry/tests/Vector3DTests.java | 115 ++ > org.eclipse.gef4.geometry/META-INF/MANIFEST.MF | 2 + > .../src/org/eclipse/gef4/geometry/Point.java | 3 +- > .../eclipse/gef4/geometry/euclidean/Straight.java | 4 +- > .../geometry/planar/AbstractArcBasedGeometry.java | 300 ++++++ > .../planar/AbstractPointListBasedGeometry.java | 73 +- > .../gef4/geometry/planar/AbstractPolyShape.java | 181 ++++ > .../planar/AbstractRectangleBasedGeometry.java | 96 +- > .../src/org/eclipse/gef4/geometry/planar/Arc.java | 246 ++---- > .../eclipse/gef4/geometry/planar/BezierCurve.java | 923 ++++++++-------- > .../eclipse/gef4/geometry/planar/BezierSpline.java | 2 +- > .../eclipse/gef4/geometry/planar/CubicCurve.java | 99 +-- > .../org/eclipse/gef4/geometry/planar/Ellipse.java | 96 +- > .../eclipse/gef4/geometry/planar/IPolyShape.java | 26 +- > .../src/org/eclipse/gef4/geometry/planar/Line.java | 47 + > .../src/org/eclipse/gef4/geometry/planar/Pie.java | 96 ++- > .../eclipse/gef4/geometry/planar/PolyBezier.java | 208 ++++- > .../org/eclipse/gef4/geometry/planar/Polygon.java | 409 +++++--- > .../org/eclipse/gef4/geometry/planar/Polyline.java | 135 ++-- > .../gef4/geometry/planar/QuadraticCurve.java | 101 +-- > .../eclipse/gef4/geometry/planar/Rectangle.java | 345 ++++--- > .../org/eclipse/gef4/geometry/planar/Region.java | 359 ++++++- > .../src/org/eclipse/gef4/geometry/planar/Ring.java | 506 +++++++++- > .../gef4/geometry/planar/RoundedRectangle.java | 221 +++-- > .../eclipse/gef4/geometry/projective/Vector3D.java | 4 +- > .../gef4/geometry/transform/IRotatable.java | 134 +++ > .../eclipse/gef4/geometry/transform/IScalable.java | 207 ++++ > .../gef4/geometry/transform/ITranslatable.java | 81 ++ > .../eclipse/gef4/geometry/utils/CurveUtils.java | 263 +++++- > .../gef4/geometry/utils/PointListUtils.java | 126 +++- > .../geometry/utils/PolynomCalculationUtils.java | 4 +- > 65 files changed, 7253 insertions(+), 1875 deletions(-) > delete mode 100644 org.eclipse.gef4.geometry.doc/reference/image_src/IGeometry_Planar_Abstractions.PNG > delete mode 100644 org.eclipse.gef4.geometry.doc/reference/image_src/IGeometry_Planar_Overview.PNG > create mode 100644 org.eclipse.gef4.geometry.doc/reference/image_src/inheritance-hierarchy.ucls > create mode 100644 org.eclipse.gef4.geometry.doc/reference/image_src/transform-overview.ucls > create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/demos/BezierApproximationExample.java > create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/demos/ConvexHullExample.java > create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/demos/CubicCurveDeCasteljauExample.java > create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/demos/RegionExample.java > create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/demos/RegionOutlineExample.java > create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/demos/RingExample.java > create mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/demos/TriangulationExample.java > delete mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/BezierApproximationExample.java > delete mode 100644 org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/ConvexHullExample.java > delete 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/scalerotate/CubicCurveScaleRotate.java > create mode 100644 org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/BezierCurveTests.java > create mode 100644 org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/RegionTests.java > create mode 100644 org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/RingTests.java > create mode 100644 org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/RoundedRectangleTests.java > create mode 100644 org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/Vector3DTests.java > create mode 100644 org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractArcBasedGeometry.java > create mode 100644 org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractPolyShape.java > create mode 100644 org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/transform/IRotatable.java > create mode 100644 org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/transform/IScalable.java > create mode 100644 org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/transform/ITranslatable.java > >diff --git a/org.eclipse.gef4.geometry.doc/javadocOptions.txt b/org.eclipse.gef4.geometry.doc/javadocOptions.txt >index 7f77240..7118fc9 100644 >--- a/org.eclipse.gef4.geometry.doc/javadocOptions.txt >+++ b/org.eclipse.gef4.geometry.doc/javadocOptions.txt >@@ -26,4 +26,4 @@ org.eclipse.gef4.geometry.euclidean > org.eclipse.gef4.geometry.planar > org.eclipse.gef4.geometry.projective > org.eclipse.gef4.geometry.transform >-org.eclipse.gef4.geometry.utils >\ No newline at end of file >+org.eclipse.gef4.geometry.utils >diff --git a/org.eclipse.gef4.geometry.doc/reference/image_src/IGeometry_Planar_Abstractions.PNG b/org.eclipse.gef4.geometry.doc/reference/image_src/IGeometry_Planar_Abstractions.PNG >deleted file mode 100644 >index 33e933d39e1d1fec1dae90857b0dd9a20d3ee99e..0000000000000000000000000000000000000000 >GIT binary patch >literal 0 >HcmV?d00001 > >literal 85320 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 >P1bk_!>7RP4ViWLxBD;Y4 > >diff --git a/org.eclipse.gef4.geometry.doc/reference/image_src/IGeometry_Planar_Abstractions.ucls b/org.eclipse.gef4.geometry.doc/reference/image_src/IGeometry_Planar_Abstractions.ucls >index 39fa299..ac6f3a7 100644 >--- a/org.eclipse.gef4.geometry.doc/reference/image_src/IGeometry_Planar_Abstractions.ucls >+++ b/org.eclipse.gef4.geometry.doc/reference/image_src/IGeometry_Planar_Abstractions.ucls >@@ -1,5 +1,5 @@ >-<class-diagram version="1.0.7" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true" >- realizations="true" associations="true" dependencies="false" nesting-relationships="true"> >+<class-diagram version="1.0.7" icons="true" always-add-relationships="false" generalizations="true" realizations="true" >+ associations="true" dependencies="false" nesting-relationships="true"> > <interface id="1" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.IGeometry" > project="org.eclipse.gef4.geometry" > file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/IGeometry.java" binary="false"> >@@ -55,24 +55,24 @@ > </display> > </class> > <generalization id="7"> >- <end type="SOURCE" refId="5"/> >+ <end type="SOURCE" refId="4"/> > <end type="TARGET" refId="1"/> > </generalization> >- <realization id="8"> >+ <generalization id="8"> >+ <end type="SOURCE" refId="3"/> >+ <end type="TARGET" refId="2"/> >+ </generalization> >+ <realization id="9"> > <end type="SOURCE" refId="6"/> > <end type="TARGET" refId="1"/> > </realization> >- <generalization id="9"> >- <end type="SOURCE" refId="4"/> >- <end type="TARGET" refId="1"/> >- </generalization> > <generalization id="10"> > <end type="SOURCE" refId="2"/> > <end type="TARGET" refId="1"/> > </generalization> > <generalization id="11"> >- <end type="SOURCE" refId="3"/> >- <end type="TARGET" refId="2"/> >+ <end type="SOURCE" refId="5"/> >+ <end type="TARGET" refId="1"/> > </generalization> > <classifier-display autosize="true" package="true" initial-value="false" signature="true" visibility="true"> > <attributes public="true" package="true" protected="true" private="true"/> >diff --git a/org.eclipse.gef4.geometry.doc/reference/image_src/IGeometry_Planar_Overview.PNG b/org.eclipse.gef4.geometry.doc/reference/image_src/IGeometry_Planar_Overview.PNG >deleted file mode 100644 >index 089d141b289c15ffc2d3286317dad9fe683e0ed7..0000000000000000000000000000000000000000 >GIT binary patch >literal 0 >HcmV?d00001 > >literal 74485 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 > >diff --git a/org.eclipse.gef4.geometry.doc/reference/image_src/IGeometry_Planar_Overview.ucls b/org.eclipse.gef4.geometry.doc/reference/image_src/IGeometry_Planar_Overview.ucls >index 9f4e067..caae498 100644 >--- a/org.eclipse.gef4.geometry.doc/reference/image_src/IGeometry_Planar_Overview.ucls >+++ b/org.eclipse.gef4.geometry.doc/reference/image_src/IGeometry_Planar_Overview.ucls >@@ -1,5 +1,5 @@ >-<class-diagram version="1.0.7" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true" >- realizations="true" associations="false" dependencies="false" nesting-relationships="false"> >+<class-diagram version="1.0.7" icons="true" always-add-relationships="false" generalizations="true" realizations="true" >+ associations="false" dependencies="false" nesting-relationships="false"> > <interface id="1" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.IGeometry" > project="org.eclipse.gef4.geometry" > file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/IGeometry.java" binary="false"> >@@ -21,7 +21,7 @@ > <interface id="3" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.IPolyShape" > project="org.eclipse.gef4.geometry" > file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/IPolyShape.java" binary="false"> >- <position height="-1" width="-1" x="932" y="403"/> >+ <position height="-1" width="-1" x="930" y="344"/> > <display autosize="true" package="true" initial-value="false" signature="true" visibility="true"> > <attributes public="false" package="false" protected="false" private="false"/> > <operations public="false" package="false" protected="false" private="false"/> >@@ -72,16 +72,7 @@ > <operations public="false" package="false" protected="false" private="false"/> > </display> > </class> >- <class id="9" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.Ring" >- project="org.eclipse.gef4.geometry" file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Ring.java" >- binary="false"> >- <position height="-1" width="-1" x="1132" y="366"/> >- <display autosize="true" package="true" initial-value="false" signature="true" visibility="true"> >- <attributes public="false" package="false" protected="false" private="false"/> >- <operations public="false" package="false" protected="false" private="false"/> >- </display> >- </class> >- <class id="10" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.RoundedRectangle" >+ <class id="9" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.RoundedRectangle" > project="org.eclipse.gef4.geometry" > file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/RoundedRectangle.java" binary="false"> > <position height="-1" width="-1" x="1139" y="281"/> >@@ -90,7 +81,7 @@ > <operations public="false" package="false" protected="false" private="false"/> > </display> > </class> >- <class id="11" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.QuadraticCurve" >+ <class id="10" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.QuadraticCurve" > project="org.eclipse.gef4.geometry" > file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/QuadraticCurve.java" binary="false"> > <position height="-1" width="-1" x="91" y="120"/> >@@ -99,7 +90,7 @@ > <operations public="false" package="false" protected="false" private="false"/> > </display> > </class> >- <class id="12" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.Polyline" >+ <class id="11" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.Polyline" > project="org.eclipse.gef4.geometry" > file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Polyline.java" binary="false"> > <position height="-1" width="-1" x="313" y="296"/> >@@ -108,7 +99,7 @@ > <operations public="false" package="false" protected="false" private="false"/> > </display> > </class> >- <class id="13" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.Polygon" >+ <class id="12" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.Polygon" > project="org.eclipse.gef4.geometry" > file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Polygon.java" binary="false"> > <position height="-1" width="-1" x="1138" y="228"/> >@@ -117,7 +108,7 @@ > <operations public="false" package="false" protected="false" private="false"/> > </display> > </class> >- <class id="14" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.Path" >+ <class id="13" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.Path" > project="org.eclipse.gef4.geometry" file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Path.java" > binary="false"> > <position height="-1" width="-1" x="723" y="259"/> >@@ -126,7 +117,7 @@ > <operations public="false" package="false" protected="false" private="false"/> > </display> > </class> >- <class id="15" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.Line" >+ <class id="14" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.Line" > project="org.eclipse.gef4.geometry" file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Line.java" > binary="false"> > <position height="-1" width="-1" x="91" y="64"/> >@@ -135,7 +126,7 @@ > <operations public="false" package="false" protected="false" private="false"/> > </display> > </class> >- <class id="16" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.CubicCurve" >+ <class id="15" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.CubicCurve" > project="org.eclipse.gef4.geometry" > file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/CubicCurve.java" binary="false"> > <position height="-1" width="-1" x="91" y="176"/> >@@ -144,7 +135,7 @@ > <operations public="false" package="false" protected="false" private="false"/> > </display> > </class> >- <class id="17" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.BezierSpline" >+ <class id="16" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.BezierSpline" > project="org.eclipse.gef4.geometry" > file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/BezierSpline.java" binary="false"> > <position height="-1" width="-1" x="314" y="173"/> >@@ -153,7 +144,7 @@ > <operations public="false" package="false" protected="false" private="false"/> > </display> > </class> >- <class id="18" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.BezierCurve" >+ <class id="17" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.BezierCurve" > project="org.eclipse.gef4.geometry" > file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/BezierCurve.java" binary="false"> > <position height="-1" width="-1" x="315" y="119"/> >@@ -162,7 +153,7 @@ > <operations public="false" package="false" protected="false" private="false"/> > </display> > </class> >- <class id="19" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.Arc" >+ <class id="18" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.Arc" > project="org.eclipse.gef4.geometry" file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Arc.java" > binary="false"> > <position height="-1" width="-1" x="314" y="227"/> >@@ -171,7 +162,7 @@ > <operations public="false" package="false" protected="false" private="false"/> > </display> > </class> >- <class id="20" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.Pie" >+ <class id="19" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.Pie" > project="org.eclipse.gef4.geometry" file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Pie.java" > binary="false"> > <position height="-1" width="-1" x="1138" y="176"/> >@@ -180,7 +171,7 @@ > <operations public="false" package="false" protected="false" private="false"/> > </display> > </class> >- <class id="21" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.PolyBezier" >+ <class id="20" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.PolyBezier" > project="org.eclipse.gef4.geometry" > file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/PolyBezier.java" binary="false"> > <position height="-1" width="-1" x="314" y="351"/> >@@ -189,86 +180,108 @@ > <operations public="false" package="false" protected="false" private="false"/> > </display> > </class> >- <realization id="22"> >- <end type="SOURCE" refId="6"/> >- <end type="TARGET" refId="4"/> >- </realization> >- <realization id="23"> >+ <class id="21" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.Ring" >+ project="org.eclipse.gef4.geometry" file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Ring.java" >+ binary="false"> >+ <position height="-1" width="-1" x="1132" y="370"/> >+ <display autosize="true" package="true" initial-value="false" signature="true" visibility="true"> >+ <attributes public="false" package="false" protected="false" private="false"/> >+ <operations public="false" package="false" protected="false" private="false"/> >+ </display> >+ </class> >+ <class id="22" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.AbstractPolyShape" >+ project="org.eclipse.gef4.geometry" >+ file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractPolyShape.java" binary="false"> >+ <position height="-1" width="-1" x="930" y="410"/> >+ <display autosize="true" package="true" initial-value="false" signature="true" visibility="true"> >+ <attributes public="false" package="false" protected="false" private="false"/> >+ <operations public="false" package="false" protected="false" private="false"/> >+ </display> >+ </class> >+ <generalization id="23"> > <end type="SOURCE" refId="8"/> >- <end type="TARGET" refId="3"/> >- </realization> >+ <end type="TARGET" refId="22"/> >+ </generalization> > <realization id="24"> >- <end type="SOURCE" refId="21"/> >- <end type="TARGET" refId="2"/> >- </realization> >- <generalization id="25"> >- <end type="SOURCE" refId="2"/> >+ <end type="SOURCE" refId="17"/> > <end type="TARGET" refId="5"/> >- </generalization> >- <realization id="26"> >- <end type="SOURCE" refId="7"/> >- <end type="TARGET" refId="4"/> > </realization> >- <realization id="27"> >- <end type="SOURCE" refId="10"/> >- <end type="TARGET" refId="4"/> >+ <realization id="25"> >+ <end type="SOURCE" refId="13"/> >+ <end type="TARGET" refId="1"/> > </realization> >- <generalization id="28"> >+ <generalization id="26"> > <end type="SOURCE" refId="5"/> > <end type="TARGET" refId="1"/> > </generalization> >- <realization id="29"> >- <end type="SOURCE" refId="13"/> >+ <realization id="27"> >+ <end type="SOURCE" refId="9"/> > <end type="TARGET" refId="4"/> > </realization> >+ <realization id="28"> >+ <end type="SOURCE" refId="7"/> >+ <end type="TARGET" refId="4"/> >+ </realization> >+ <generalization id="29"> >+ <end type="SOURCE" refId="14"/> >+ <end type="TARGET" refId="17"/> >+ </generalization> > <realization id="30"> >- <end type="SOURCE" refId="9"/> >- <end type="TARGET" refId="3"/> >+ <end type="SOURCE" refId="11"/> >+ <end type="TARGET" refId="2"/> > </realization> > <generalization id="31"> >- <end type="SOURCE" refId="16"/> >- <end type="TARGET" refId="18"/> >+ <end type="SOURCE" refId="4"/> >+ <end type="TARGET" refId="1"/> > </generalization> > <realization id="32"> >- <end type="SOURCE" refId="17"/> >+ <end type="SOURCE" refId="16"/> > <end type="TARGET" refId="5"/> > </realization> >- <generalization id="33"> >- <end type="SOURCE" refId="4"/> >- <end type="TARGET" refId="1"/> >- </generalization> >- <generalization id="34"> >- <end type="SOURCE" refId="15"/> >- <end type="TARGET" refId="18"/> >- </generalization> >+ <realization id="33"> >+ <end type="SOURCE" refId="19"/> >+ <end type="TARGET" refId="4"/> >+ </realization> >+ <realization id="34"> >+ <end type="SOURCE" refId="6"/> >+ <end type="TARGET" refId="4"/> >+ </realization> > <generalization id="35"> >- <end type="SOURCE" refId="11"/> >- <end type="TARGET" refId="18"/> >+ <end type="SOURCE" refId="21"/> >+ <end type="TARGET" refId="22"/> > </generalization> > <generalization id="36"> >- <end type="SOURCE" refId="3"/> >- <end type="TARGET" refId="1"/> >+ <end type="SOURCE" refId="10"/> >+ <end type="TARGET" refId="17"/> > </generalization> > <realization id="37"> >- <end type="SOURCE" refId="20"/> >- <end type="TARGET" refId="4"/> >+ <end type="SOURCE" refId="18"/> >+ <end type="TARGET" refId="5"/> > </realization> >- <realization id="38"> >- <end type="SOURCE" refId="14"/> >+ <generalization id="38"> >+ <end type="SOURCE" refId="3"/> > <end type="TARGET" refId="1"/> >- </realization> >+ </generalization> > <realization id="39"> >- <end type="SOURCE" refId="19"/> >- <end type="TARGET" refId="5"/> >+ <end type="SOURCE" refId="22"/> >+ <end type="TARGET" refId="3"/> > </realization> >- <realization id="40"> >- <end type="SOURCE" refId="12"/> >+ <generalization id="40"> >+ <end type="SOURCE" refId="2"/> >+ <end type="TARGET" refId="5"/> >+ </generalization> >+ <realization id="41"> >+ <end type="SOURCE" refId="20"/> > <end type="TARGET" refId="2"/> > </realization> >- <realization id="41"> >- <end type="SOURCE" refId="18"/> >- <end type="TARGET" refId="5"/> >+ <realization id="42"> >+ <end type="SOURCE" refId="12"/> >+ <end type="TARGET" refId="4"/> > </realization> >+ <generalization id="43"> >+ <end type="SOURCE" refId="15"/> >+ <end type="TARGET" refId="17"/> >+ </generalization> > <classifier-display autosize="true" package="true" initial-value="false" signature="true" visibility="true"> > <attributes public="false" package="false" protected="false" private="false"/> > <operations public="false" package="false" protected="false" private="false"/> >diff --git a/org.eclipse.gef4.geometry.doc/reference/image_src/inheritance-hierarchy.ucls b/org.eclipse.gef4.geometry.doc/reference/image_src/inheritance-hierarchy.ucls >new file mode 100644 >index 0000000..13f62da >--- /dev/null >+++ b/org.eclipse.gef4.geometry.doc/reference/image_src/inheritance-hierarchy.ucls >@@ -0,0 +1,283 @@ >+<class-diagram version="1.0.7" icons="true" always-add-relationships="false" generalizations="true" realizations="true" >+ associations="true" dependencies="false" nesting-relationships="true"> >+ <class id="1" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.Region" >+ project="org.eclipse.gef4.geometry" >+ file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Region.java" binary="false"> >+ <position height="-1" width="-1" x="845" y="210"/> >+ <display autosize="true" package="false" initial-value="false" signature="true" visibility="true"> >+ <attributes public="false" package="false" protected="false" private="false"/> >+ <operations public="false" package="false" protected="false" private="false"/> >+ </display> >+ </class> >+ <class id="2" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.PolyBezier" >+ project="org.eclipse.gef4.geometry" >+ file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/PolyBezier.java" binary="false"> >+ <position height="-1" width="-1" x="226" y="14"/> >+ <display autosize="true" package="false" initial-value="false" signature="true" visibility="true"> >+ <attributes public="false" package="false" protected="false" private="false"/> >+ <operations public="false" package="false" protected="false" private="false"/> >+ </display> >+ </class> >+ <class id="3" corner="BOTTOM_RIGHT" language="java" >+ name="org.eclipse.gef4.geometry.planar.AbstractRectangleBasedGeometry" project="org.eclipse.gef4.geometry" >+ file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractRectangleBasedGeometry.java" >+ binary="false"> >+ <position height="-1" width="-1" x="294" y="125"/> >+ <display autosize="true" package="false" initial-value="false" signature="true" visibility="true"> >+ <attributes public="false" package="false" protected="false" private="false"/> >+ <operations public="false" package="false" protected="false" private="false"/> >+ </display> >+ </class> >+ <class id="4" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.QuadraticCurve" >+ project="org.eclipse.gef4.geometry" >+ file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/QuadraticCurve.java" binary="false"> >+ <position height="-1" width="-1" x="697" y="164"/> >+ <display autosize="true" package="false" initial-value="false" signature="true" visibility="true"> >+ <attributes public="false" package="false" protected="false" private="false"/> >+ <operations public="false" package="false" protected="false" private="false"/> >+ </display> >+ </class> >+ <class id="5" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.BezierCurve" >+ project="org.eclipse.gef4.geometry" >+ file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/BezierCurve.java" binary="false"> >+ <position height="-1" width="-1" x="561" y="126"/> >+ <display autosize="true" package="false" initial-value="false" signature="true" visibility="true"> >+ <attributes public="false" package="false" protected="false" private="false"/> >+ <operations public="false" package="false" protected="false" private="false"/> >+ </display> >+ </class> >+ <class id="6" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.AbstractGeometry" >+ project="org.eclipse.gef4.geometry" >+ file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractGeometry.java" binary="false"> >+ <position height="-1" width="-1" x="474" y="25"/> >+ <display autosize="true" package="false" initial-value="false" signature="true" visibility="true"> >+ <attributes public="false" package="false" protected="false" private="false"/> >+ <operations public="false" package="false" protected="false" private="false"/> >+ </display> >+ </class> >+ <class id="7" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.RoundedRectangle" >+ project="org.eclipse.gef4.geometry" >+ file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/RoundedRectangle.java" binary="false"> >+ <position height="-1" width="-1" x="68" y="163"/> >+ <display autosize="true" package="false" initial-value="false" signature="true" visibility="true"> >+ <attributes public="false" package="false" protected="false" private="false"/> >+ <operations public="false" package="false" protected="false" private="false"/> >+ </display> >+ </class> >+ <class id="8" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.Rectangle" >+ project="org.eclipse.gef4.geometry" >+ file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Rectangle.java" binary="false"> >+ <position height="-1" width="-1" x="93" y="86"/> >+ <display autosize="true" package="false" initial-value="false" signature="true" visibility="true"> >+ <attributes public="false" package="false" protected="false" private="false"/> >+ <operations public="false" package="false" protected="false" private="false"/> >+ </display> >+ </class> >+ <class id="9" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.Ring" >+ project="org.eclipse.gef4.geometry" file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Ring.java" >+ binary="false"> >+ <position height="-1" width="-1" x="932" y="210"/> >+ <display autosize="true" package="false" initial-value="false" signature="true" visibility="true"> >+ <attributes public="false" package="false" protected="false" private="false"/> >+ <operations public="false" package="false" protected="false" private="false"/> >+ </display> >+ </class> >+ <class id="10" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.AbstractArcBasedGeometry" >+ project="org.eclipse.gef4.geometry" >+ file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractArcBasedGeometry.java" binary="false"> >+ <position height="-1" width="-1" x="269" y="215"/> >+ <display autosize="true" package="false" initial-value="false" signature="true" visibility="true"> >+ <attributes public="false" package="false" protected="false" private="false"/> >+ <operations public="false" package="false" protected="false" private="false"/> >+ </display> >+ </class> >+ <class id="11" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.Arc" >+ project="org.eclipse.gef4.geometry" file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Arc.java" >+ binary="false"> >+ <position height="-1" width="-1" x="315" y="273"/> >+ <display autosize="true" package="false" initial-value="false" signature="true" visibility="true"> >+ <attributes public="false" package="false" protected="false" private="false"/> >+ <operations public="false" package="false" protected="false" private="false"/> >+ </display> >+ </class> >+ <class id="12" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.CubicCurve" >+ project="org.eclipse.gef4.geometry" >+ file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/CubicCurve.java" binary="false"> >+ <position height="32" width="91" x="639" y="189"/> >+ <display autosize="false" package="false" initial-value="false" signature="true" visibility="true"> >+ <attributes public="false" package="false" protected="false" private="false"/> >+ <operations public="false" package="false" protected="false" private="false"/> >+ </display> >+ </class> >+ <class id="13" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.Ellipse" >+ project="org.eclipse.gef4.geometry" >+ file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Ellipse.java" binary="false"> >+ <position height="-1" width="-1" x="93" y="124"/> >+ <display autosize="true" package="false" initial-value="false" signature="true" visibility="true"> >+ <attributes public="false" package="false" protected="false" private="false"/> >+ <operations public="false" package="false" protected="false" private="false"/> >+ </display> >+ </class> >+ <class id="14" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.Path" >+ project="org.eclipse.gef4.geometry" file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Path.java" >+ binary="false"> >+ <position height="-1" width="-1" x="474" y="201"/> >+ <display autosize="true" package="false" initial-value="false" signature="true" visibility="true"> >+ <attributes public="false" package="false" protected="false" private="false"/> >+ <operations public="false" package="false" protected="false" private="false"/> >+ </display> >+ </class> >+ <class id="15" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.Pie" >+ project="org.eclipse.gef4.geometry" file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Pie.java" >+ binary="false"> >+ <position height="-1" width="-1" x="201" y="276"/> >+ <display autosize="true" package="false" initial-value="false" signature="true" visibility="true"> >+ <attributes public="false" package="false" protected="false" private="false"/> >+ <operations public="false" package="false" protected="false" private="false"/> >+ </display> >+ </class> >+ <class id="16" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.BezierSpline" >+ project="org.eclipse.gef4.geometry" >+ file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/BezierSpline.java" binary="false"> >+ <position height="-1" width="-1" x="223" y="54"/> >+ <display autosize="true" package="false" initial-value="false" signature="true" visibility="true"> >+ <attributes public="false" package="false" protected="false" private="false"/> >+ <operations public="false" package="false" protected="false" private="false"/> >+ </display> >+ </class> >+ <class id="17" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.Polyline" >+ project="org.eclipse.gef4.geometry" >+ file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Polyline.java" binary="false"> >+ <position height="-1" width="-1" x="819" y="89"/> >+ <display autosize="true" package="false" initial-value="false" signature="true" visibility="true"> >+ <attributes public="false" package="false" protected="false" private="false"/> >+ <operations public="false" package="false" protected="false" private="false"/> >+ </display> >+ </class> >+ <class id="18" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.Polygon" >+ project="org.eclipse.gef4.geometry" >+ file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Polygon.java" binary="false"> >+ <position height="-1" width="-1" x="905" y="89"/> >+ <display autosize="true" package="false" initial-value="false" signature="true" visibility="true"> >+ <attributes public="false" package="false" protected="false" private="false"/> >+ <operations public="false" package="false" protected="false" private="false"/> >+ </display> >+ </class> >+ <class id="19" corner="BOTTOM_RIGHT" language="java" >+ name="org.eclipse.gef4.geometry.planar.AbstractPointListBasedGeometry" project="org.eclipse.gef4.geometry" >+ file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractPointListBasedGeometry.java" >+ binary="false"> >+ <position height="-1" width="-1" x="849" y="25"/> >+ <display autosize="true" package="false" initial-value="false" signature="true" visibility="true"> >+ <attributes public="false" package="false" protected="false" private="false"/> >+ <operations public="false" package="false" protected="false" private="false"/> >+ </display> >+ </class> >+ <class id="20" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.AbstractPolyShape" >+ project="org.eclipse.gef4.geometry" >+ file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractPolyShape.java" binary="false"> >+ <position height="-1" width="-1" x="889" y="149"/> >+ <display autosize="true" package="false" initial-value="false" signature="true" visibility="true"> >+ <attributes public="false" package="false" protected="false" private="false"/> >+ <operations public="false" package="false" protected="false" private="false"/> >+ </display> >+ </class> >+ <class id="21" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.planar.Line" >+ project="org.eclipse.gef4.geometry" file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/Line.java" >+ binary="false"> >+ <position height="-1" width="-1" x="681" y="123"/> >+ <display autosize="true" package="false" initial-value="false" signature="true" visibility="true"> >+ <attributes public="false" package="false" protected="false" private="false"/> >+ <operations public="false" package="false" protected="false" private="false"/> >+ </display> >+ </class> >+ <generalization id="22"> >+ <end type="SOURCE" refId="14"/> >+ <end type="TARGET" refId="6"/> >+ </generalization> >+ <generalization id="23"> >+ <end type="SOURCE" refId="2"/> >+ <end type="TARGET" refId="6"/> >+ </generalization> >+ <generalization id="24"> >+ <end type="SOURCE" refId="5"/> >+ <end type="TARGET" refId="6"/> >+ </generalization> >+ <generalization id="25"> >+ <bendpoint x="622" y="206"/> >+ <end type="SOURCE" refId="12"/> >+ <end type="TARGET" refId="5"/> >+ </generalization> >+ <generalization id="26"> >+ <bendpoint x="616" y="163"/> >+ <end type="SOURCE" refId="4"/> >+ <end type="TARGET" refId="5"/> >+ </generalization> >+ <generalization id="27"> >+ <end type="SOURCE" refId="11"/> >+ <end type="TARGET" refId="10"/> >+ </generalization> >+ <generalization id="28"> >+ <end type="SOURCE" refId="1"/> >+ <end type="TARGET" refId="20"/> >+ </generalization> >+ <generalization id="29"> >+ <end type="SOURCE" refId="17"/> >+ <end type="TARGET" refId="19"/> >+ </generalization> >+ <generalization id="30"> >+ <end type="SOURCE" refId="9"/> >+ <end type="TARGET" refId="20"/> >+ </generalization> >+ <generalization id="31"> >+ <end type="SOURCE" refId="21"/> >+ <end type="TARGET" refId="5"/> >+ </generalization> >+ <generalization id="32"> >+ <end type="SOURCE" refId="18"/> >+ <end type="TARGET" refId="19"/> >+ </generalization> >+ <generalization id="33"> >+ <end type="SOURCE" refId="3"/> >+ <end type="TARGET" refId="6"/> >+ </generalization> >+ <generalization id="34"> >+ <end type="SOURCE" refId="16"/> >+ <end type="TARGET" refId="6"/> >+ </generalization> >+ <generalization id="35"> >+ <end type="SOURCE" refId="10"/> >+ <end type="TARGET" refId="3"/> >+ </generalization> >+ <generalization id="36"> >+ <end type="SOURCE" refId="13"/> >+ <end type="TARGET" refId="3"/> >+ </generalization> >+ <generalization id="37"> >+ <end type="SOURCE" refId="15"/> >+ <end type="TARGET" refId="10"/> >+ </generalization> >+ <generalization id="38"> >+ <end type="SOURCE" refId="20"/> >+ <end type="TARGET" refId="6"/> >+ </generalization> >+ <generalization id="39"> >+ <bendpoint x="174" y="163"/> >+ <end type="SOURCE" refId="7"/> >+ <end type="TARGET" refId="3"/> >+ </generalization> >+ <generalization id="40"> >+ <end type="SOURCE" refId="19"/> >+ <end type="TARGET" refId="6"/> >+ </generalization> >+ <generalization id="41"> >+ <bendpoint x="180" y="86"/> >+ <end type="SOURCE" refId="8"/> >+ <end type="TARGET" refId="3"/> >+ </generalization> >+ <classifier-display autosize="true" package="false" initial-value="false" signature="true" visibility="true"> >+ <attributes public="false" package="false" protected="false" private="false"/> >+ <operations public="false" package="false" protected="false" private="false"/> >+ </classifier-display> >+ <association-display labels="true" multiplicity="true"/> >+</class-diagram> >\ No newline at end of file >diff --git a/org.eclipse.gef4.geometry.doc/reference/image_src/transform-overview.ucls b/org.eclipse.gef4.geometry.doc/reference/image_src/transform-overview.ucls >new file mode 100644 >index 0000000..dded353 >--- /dev/null >+++ b/org.eclipse.gef4.geometry.doc/reference/image_src/transform-overview.ucls >@@ -0,0 +1,44 @@ >+<class-diagram version="1.0.7" icons="true" always-add-relationships="false" generalizations="true" realizations="true" >+ associations="true" dependencies="false" nesting-relationships="true"> >+ <class id="1" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.transform.AffineTransform" >+ project="org.eclipse.gef4.geometry" >+ file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/transform/AffineTransform.java" binary="false"> >+ <position height="-1" width="-1" x="169" y="127"/> >+ <display autosize="true" package="true" initial-value="false" signature="true" visibility="true"> >+ <attributes public="true" package="false" protected="false" private="false"/> >+ <operations public="true" package="false" protected="false" private="false"/> >+ </display> >+ </class> >+ <interface id="2" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.transform.IRotatable" >+ project="org.eclipse.gef4.geometry" >+ file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/transform/IRotatable.java" binary="false"> >+ <position height="-1" width="-1" x="448" y="-207"/> >+ <display autosize="true" package="true" initial-value="false" signature="true" visibility="true"> >+ <attributes public="true" package="false" protected="false" private="false"/> >+ <operations public="true" package="false" protected="false" private="false"/> >+ </display> >+ </interface> >+ <interface id="3" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.transform.IScalable" >+ project="org.eclipse.gef4.geometry" >+ file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/transform/IScalable.java" binary="false"> >+ <position height="-1" width="-1" x="671" y="-160"/> >+ <display autosize="true" package="true" initial-value="false" signature="true" visibility="true"> >+ <attributes public="true" package="false" protected="false" private="false"/> >+ <operations public="true" package="false" protected="false" private="false"/> >+ </display> >+ </interface> >+ <interface id="4" corner="BOTTOM_RIGHT" language="java" name="org.eclipse.gef4.geometry.transform.ITranslatable" >+ project="org.eclipse.gef4.geometry" >+ file="/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/transform/ITranslatable.java" binary="false"> >+ <position height="-1" width="-1" x="874" y="-225"/> >+ <display autosize="true" package="true" initial-value="false" signature="true" visibility="true"> >+ <attributes public="true" package="false" protected="false" private="false"/> >+ <operations public="true" package="false" protected="false" private="false"/> >+ </display> >+ </interface> >+ <classifier-display autosize="true" package="true" initial-value="false" signature="true" visibility="true"> >+ <attributes public="true" package="false" protected="false" private="false"/> >+ <operations public="true" package="false" protected="false" private="false"/> >+ </classifier-display> >+ <association-display labels="true" multiplicity="true"/> >+</class-diagram> >\ No newline at end of file >diff --git a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/demos/BezierApproximationExample.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/demos/BezierApproximationExample.java >new file mode 100644 >index 0000000..c0beca9 >--- /dev/null >+++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/demos/BezierApproximationExample.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.demos; >+ >+import org.eclipse.gef4.geometry.Point; >+import org.eclipse.gef4.geometry.examples.intersection.AbstractIntersectionExample; >+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, 200)); >+ addControlPoint(new Point(150, 250)); >+ addControlPoint(new Point(200, 150)); >+ addControlPoint(new Point(250, 250)); >+ addControlPoint(new Point(300, 150)); >+ addControlPoint(new Point(350, 250)); >+ addControlPoint(new Point(400, 200)); >+ } >+ >+ @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/demos/ConvexHullExample.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/demos/ConvexHullExample.java >new file mode 100644 >index 0000000..67955f7 >--- /dev/null >+++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/demos/ConvexHullExample.java >@@ -0,0 +1,84 @@ >+/******************************************************************************* >+ * 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.demos; >+ >+import org.eclipse.gef4.geometry.Point; >+import org.eclipse.gef4.geometry.examples.intersection.AbstractIntersectionExample; >+import org.eclipse.gef4.geometry.examples.intersection.AbstractIntersectionExample.AbstractControllableShape; >+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/demos/CubicCurveDeCasteljauExample.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/demos/CubicCurveDeCasteljauExample.java >new file mode 100644 >index 0000000..aa9eda0 >--- /dev/null >+++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/demos/CubicCurveDeCasteljauExample.java >@@ -0,0 +1,136 @@ >+/******************************************************************************* >+ * 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.demos; >+ >+import org.eclipse.gef4.geometry.Point; >+import org.eclipse.gef4.geometry.examples.intersection.AbstractIntersectionExample; >+import org.eclipse.gef4.geometry.examples.intersection.AbstractIntersectionExample.AbstractControllableShape; >+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/demos/RegionExample.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/demos/RegionExample.java >new file mode 100644 >index 0000000..a8a9795 >--- /dev/null >+++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/demos/RegionExample.java >@@ -0,0 +1,100 @@ >+/******************************************************************************* >+ * 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.demos; >+ >+import org.eclipse.gef4.geometry.Point; >+import org.eclipse.gef4.geometry.examples.intersection.AbstractIntersectionExample; >+import org.eclipse.gef4.geometry.examples.intersection.AbstractIntersectionExample.AbstractControllableShape; >+import org.eclipse.gef4.geometry.planar.IGeometry; >+import org.eclipse.gef4.geometry.planar.Line; >+import org.eclipse.gef4.geometry.planar.Rectangle; >+import org.eclipse.gef4.geometry.planar.Region; >+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 RegionExample extends AbstractIntersectionExample { >+ public static void main(String[] args) { >+ new RegionExample("Region Example"); >+ } >+ >+ public RegionExample(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(200, 200)); >+ >+ addControlPoint(new Point(150, 150)); >+ addControlPoint(new Point(250, 250)); >+ } >+ >+ @Override >+ public Region createGeometry() { >+ Point[] cp = getControlPoints(); >+ Region region = new Region(new Rectangle(cp[0], cp[1]), >+ new Rectangle(cp[2], cp[3])); >+ return region; >+ } >+ >+ @Override >+ public void drawShape(GC gc) { >+ Region region = createGeometry(); >+ >+ gc.setClipping(region.toSWTRegion()); >+ >+ for (int y = 0; y < 800; y += 20) { >+ gc.drawString( >+ "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", >+ 20, y); >+ } >+ >+ gc.setClipping((org.eclipse.swt.graphics.Region) null); >+ >+ gc.setAlpha(128); >+ gc.setBackground(Display.getCurrent().getSystemColor( >+ SWT.COLOR_BLUE)); >+ for (Rectangle r : region.getShapes()) { >+ gc.fillRectangle(r.toSWTRectangle()); >+ } >+ gc.setAlpha(255); >+ } >+ }; >+ } >+ >+ 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/demos/RegionOutlineExample.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/demos/RegionOutlineExample.java >new file mode 100644 >index 0000000..2cc7466 >--- /dev/null >+++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/demos/RegionOutlineExample.java >@@ -0,0 +1,113 @@ >+/******************************************************************************* >+ * 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.demos; >+ >+import org.eclipse.gef4.geometry.Point; >+import org.eclipse.gef4.geometry.examples.intersection.AbstractIntersectionExample; >+import org.eclipse.gef4.geometry.examples.intersection.AbstractIntersectionExample.AbstractControllableShape; >+import org.eclipse.gef4.geometry.planar.IGeometry; >+import org.eclipse.gef4.geometry.planar.Line; >+import org.eclipse.gef4.geometry.planar.Rectangle; >+import org.eclipse.gef4.geometry.planar.Region; >+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 RegionOutlineExample extends AbstractIntersectionExample { >+ public static void main(String[] args) { >+ new RegionOutlineExample("Region Example"); >+ } >+ >+ public RegionOutlineExample(String title) { >+ super(title); >+ } >+ >+ protected AbstractControllableShape createControllableShape1(Canvas canvas) { >+ return new AbstractControllableShape(canvas) { >+ @Override >+ public void createControlPoints() { >+ addControlPoint(new Point(100, 0)); >+ addControlPoint(new Point(300, 100)); >+ >+ addControlPoint(new Point(250, 200)); >+ addControlPoint(new Point(350, 330)); >+ >+ addControlPoint(new Point(100, 200)); >+ addControlPoint(new Point(190, 325)); >+ >+ addControlPoint(new Point(150, 300)); >+ addControlPoint(new Point(280, 380)); >+ } >+ >+ @Override >+ public Region createGeometry() { >+ Point[] cp = getControlPoints(); >+ Rectangle[] rectangles = new Rectangle[cp.length / 2]; >+ for (int i = 0; i < rectangles.length; i++) { >+ rectangles[i] = new Rectangle(cp[2 * i], cp[2 * i + 1]); >+ // System.out.println("R" + i + " " + cp[2 * i] + "\\" >+ // + cp[2 * i + 1]); >+ } >+ Region region = new Region(rectangles); >+ return region; >+ } >+ >+ @Override >+ public void drawShape(GC gc) { >+ Region region = createGeometry(); >+ >+ gc.setAlpha(128); >+ gc.setBackground(Display.getCurrent().getSystemColor( >+ SWT.COLOR_BLUE)); >+ for (Rectangle r : region.getShapes()) { >+ gc.fillRectangle(r.toSWTRectangle()); >+ } >+ >+ gc.setAlpha(255); >+ gc.setForeground(Display.getCurrent().getSystemColor( >+ SWT.COLOR_RED)); >+ for (Rectangle r : region.getShapes()) { >+ gc.drawRectangle(r.toSWTRectangle()); >+ } >+ gc.setForeground(Display.getCurrent().getSystemColor( >+ SWT.COLOR_BLACK)); >+ for (Line l : region.getOutlineSegments()) { >+ gc.drawLine((int) (l.getX1()), (int) (l.getY1()), >+ (int) (l.getX2()), (int) (l.getY2())); >+ } >+ } >+ }; >+ } >+ >+ 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/demos/RingExample.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/demos/RingExample.java >new file mode 100644 >index 0000000..fcd180a >--- /dev/null >+++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/demos/RingExample.java >@@ -0,0 +1,128 @@ >+/******************************************************************************* >+ * Copyright (c) 2012 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.demos; >+ >+import org.eclipse.gef4.geometry.Point; >+import org.eclipse.gef4.geometry.examples.intersection.AbstractIntersectionExample; >+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.Ring; >+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 RingExample extends AbstractIntersectionExample { >+ public static void main(String[] args) { >+ new RingExample("Ring Example"); >+ } >+ >+ public RingExample(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(400, 100)); >+ addControlPoint(new Point(400, 200)); >+ >+ addControlPoint(new Point(400, 100)); >+ addControlPoint(new Point(400, 400)); >+ addControlPoint(new Point(300, 400)); >+ >+ addControlPoint(new Point(400, 400)); >+ addControlPoint(new Point(100, 400)); >+ addControlPoint(new Point(100, 300)); >+ >+ addControlPoint(new Point(100, 400)); >+ addControlPoint(new Point(100, 100)); >+ addControlPoint(new Point(200, 100)); >+ } >+ >+ @Override >+ public Ring createGeometry() { >+ Point[] cp = getControlPoints(); >+ Polygon[] polygons = new Polygon[cp.length / 3]; >+ for (int i = 0; i < polygons.length; i++) { >+ polygons[i] = new Polygon(cp[3 * i], cp[3 * i + 1], >+ cp[3 * i + 2]); >+ // System.out.println("R" + i + " " + cp[2 * i] + "\\" >+ // + cp[2 * i + 1]); >+ } >+ Ring ring = new Ring(polygons); >+ return ring; >+ } >+ >+ @Override >+ public void drawShape(GC gc) { >+ Ring ring = createGeometry(); >+ >+ gc.setAlpha(64); >+ gc.setBackground(Display.getCurrent().getSystemColor( >+ SWT.COLOR_BLUE)); >+ for (Polygon p : ring.getShapes()) { >+ gc.fillPolygon(p.toSWTPointArray()); >+ } >+ >+ gc.setAlpha(255); >+ gc.setForeground(Display.getCurrent().getSystemColor( >+ SWT.COLOR_RED)); >+ for (Polygon p : ring.getShapes()) { >+ gc.drawPolygon(p.toSWTPointArray()); >+ } >+ >+ gc.setForeground(Display.getCurrent().getSystemColor( >+ SWT.COLOR_BLACK)); >+ int lineWidth = gc.getLineWidth(); >+ gc.setLineWidth(lineWidth + 2); >+ for (Line l : ring.getOutlineSegments()) { >+ gc.drawLine((int) (l.getX1()), (int) (l.getY1()), >+ (int) (l.getX2()), (int) (l.getY2())); >+ } >+ gc.setLineWidth(lineWidth); >+ >+ // gc.setForeground(Display.getCurrent().getSystemColor( >+ // SWT.COLOR_BLACK)); >+ // for (Line l : region.getOutlineSegments()) { >+ // gc.drawLine((int) (l.getX1()), (int) (l.getY1()), >+ // (int) (l.getX2()), (int) (l.getY2())); >+ // } >+ } >+ }; >+ } >+ >+ 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/demos/TriangulationExample.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/demos/TriangulationExample.java >new file mode 100644 >index 0000000..fb052a1 >--- /dev/null >+++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/demos/TriangulationExample.java >@@ -0,0 +1,109 @@ >+/******************************************************************************* >+ * Copyright (c) 2012 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.demos; >+ >+import org.eclipse.gef4.geometry.Point; >+import org.eclipse.gef4.geometry.examples.intersection.AbstractIntersectionExample; >+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.Polygon.NonSimplePolygonException; >+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 TriangulationExample extends AbstractIntersectionExample { >+ public static void main(String[] args) { >+ new TriangulationExample("Triangulation Example"); >+ } >+ >+ public TriangulationExample(String title) { >+ super(title); >+ } >+ >+ protected AbstractControllableShape createControllableShape1(Canvas canvas) { >+ return new AbstractControllableShape(canvas) { >+ @Override >+ public void createControlPoints() { >+ addControlPoint(new Point(300 / 2, 100 / 2)); >+ addControlPoint(new Point(100 / 2, 200 / 2)); >+ addControlPoint(new Point(200 / 2, 300 / 2)); >+ addControlPoint(new Point(100 / 2, 500 / 2)); >+ addControlPoint(new Point(300 / 2, 400 / 2)); >+ addControlPoint(new Point(500 / 2, 600 / 2)); >+ addControlPoint(new Point(600 / 2, 300 / 2)); >+ addControlPoint(new Point(500 / 2, 400 / 2)); >+ addControlPoint(new Point(500 / 2, 200 / 2)); >+ addControlPoint(new Point(300 / 2, 200 / 2)); >+ } >+ >+ @Override >+ public Polygon createGeometry() { >+ Point[] cp = getControlPoints(); >+ Polygon p = new Polygon(getControlPoints()); >+ return p; >+ } >+ >+ @Override >+ public void drawShape(GC gc) { >+ Polygon p = createGeometry(); >+ >+ // System.out.println("p = " + p); >+ >+ gc.setForeground(Display.getCurrent().getSystemColor( >+ SWT.COLOR_RED)); >+ >+ Polygon[] triangulation; >+ try { >+ triangulation = p.getTriangulation(); >+ } catch (NonSimplePolygonException x) { >+ triangulation = new Polygon[] { p }; >+ } >+ for (Polygon triangle : triangulation) { >+ gc.drawPolygon(triangle.toSWTPointArray()); >+ } >+ >+ int lineWidth = gc.getLineWidth(); >+ gc.setLineWidth(lineWidth + 2); >+ gc.setForeground(Display.getCurrent().getSystemColor( >+ SWT.COLOR_BLACK)); >+ >+ gc.drawPolygon(p.toSWTPointArray()); >+ >+ gc.setLineWidth(lineWidth); >+ } >+ }; >+ } >+ >+ 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/AbstractIntersectionExample.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/AbstractIntersectionExample.java >index 5137201..777fda7 100644 >--- 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 >@@ -333,11 +333,14 @@ public abstract class AbstractIntersectionExample implements PaintListener { > } > infoLabel.setText(infoText); > >+ // open the shell before creating the controllable shapes so that their >+ // default coordinates are not changed due to the resize of their canvas >+ shell.open(); >+ > controllableShape1 = createControllableShape1(shell); > controllableShape2 = createControllableShape2(shell); > > shell.addPaintListener(this); >- shell.open(); > > while (!shell.isDisposed()) { > if (!display.readAndDispatch()) { >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 >deleted file mode 100644 >index 2bf2bb9..0000000 >--- a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/BezierApproximationExample.java >+++ /dev/null >@@ -1,80 +0,0 @@ >-/******************************************************************************* >- * 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 >deleted file mode 100644 >index 4e317f6..0000000 >--- a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/ConvexHullExample.java >+++ /dev/null >@@ -1,82 +0,0 @@ >-/******************************************************************************* >- * 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 >deleted file mode 100644 >index 380f1b2..0000000 >--- a/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/intersection/CubicCurveDeCasteljauExample.java >+++ /dev/null >@@ -1,134 +0,0 @@ >-/******************************************************************************* >- * 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/scalerotate/CubicCurveScaleRotate.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/scalerotate/CubicCurveScaleRotate.java >new file mode 100644 >index 0000000..0fa634a >--- /dev/null >+++ b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/scalerotate/CubicCurveScaleRotate.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.scalerotate; >+ >+import org.eclipse.gef4.geometry.Point; >+import org.eclipse.gef4.geometry.planar.CubicCurve; >+import org.eclipse.swt.graphics.GC; >+import org.eclipse.swt.widgets.Canvas; >+import org.eclipse.swt.widgets.Display; >+ >+public class CubicCurveScaleRotate extends AbstractScaleRotateExample { >+ >+ public static void main(String[] args) { >+ new CubicCurveScaleRotate(); >+ } >+ >+ public CubicCurveScaleRotate() { >+ super("Scale/Rotate - CubicCurve"); >+ } >+ >+ @Override >+ protected AbstractScaleRotateShape createShape(Canvas canvas) { >+ return new AbstractScaleRotateShape(canvas) { >+ @Override >+ public boolean contains(Point p) { >+ return createGeometry().getBounds().contains(p); >+ } >+ >+ @Override >+ public CubicCurve createGeometry() { >+ double w = getCanvas().getClientArea().width; >+ double h = getCanvas().getClientArea().height; >+ double padx = w / 10; >+ double pady = h / 10; >+ >+ CubicCurve me = new CubicCurve(padx, pady, w + w, h, -w, h, w >+ - padx, pady); >+ me.rotateCW(getRotationAngle(), getCenter()); >+ me.scale(getZoomFactor(), getCenter()); >+ >+ return me; >+ } >+ >+ @Override >+ public void draw(GC gc) { >+ CubicCurve me = createGeometry(); >+ gc.fillRectangle(me.getBounds().toSWTRectangle()); >+ gc.drawPath(new org.eclipse.swt.graphics.Path(Display >+ .getCurrent(), me.toPath().toSWTPathData())); >+ } >+ }; >+ } >+} >diff --git a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/AllTests.java b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/AllTests.java >index b7abe26..cf7c4de 100644 >--- a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/AllTests.java >+++ b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/AllTests.java >@@ -17,12 +17,14 @@ import org.junit.runners.Suite; > import org.junit.runners.Suite.SuiteClasses; > > @RunWith(Suite.class) >-@SuiteClasses({ AngleTests.class, CubicCurveTests.class, CurveUtilsTests.class, >- DimensionTests.class, EllipseTests.class, LineTests.class, >- PointListUtilsTests.class, PointTests.class, PolygonTests.class, >- PolylineTests.class, PolynomCalculationUtilsTests.class, >- PrecisionUtilsTests.class, QuadraticCurveTests.class, >- RectangleTests.class, StraightTests.class, VectorTests.class }) >+@SuiteClasses({ AngleTests.class, BezierCurveTests.class, >+ CubicCurveTests.class, CurveUtilsTests.class, DimensionTests.class, >+ EllipseTests.class, LineTests.class, PointListUtilsTests.class, >+ PointTests.class, PolygonTests.class, PolylineTests.class, >+ PolynomCalculationUtilsTests.class, PrecisionUtilsTests.class, >+ QuadraticCurveTests.class, RectangleTests.class, RegionTests.class, >+ RingTests.class, RoundedRectangleTests.class, StraightTests.class, >+ VectorTests.class, Vector3DTests.class }) > public class AllTests { > > } >diff --git a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/AngleTests.java b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/AngleTests.java >index 02c0278..ead4299 100644 >--- a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/AngleTests.java >+++ b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/AngleTests.java >@@ -74,6 +74,7 @@ public class AngleTests { > assertFalse(alpha.equals(delta)); > assertTrue(gamma.equals(delta)); > assertTrue(delta.equals(gamma)); >+ assertFalse(alpha.equals(null)); > assertFalse(alpha.equals(new Point())); > > alpha = new Angle(UNRECOGNIZABLE_FRACTION / 2, AngleUnit.RAD); >@@ -81,6 +82,7 @@ public class AngleTests { > AngleUnit.RAD); > assertTrue(alpha.equals(beta)); > assertTrue(beta.equals(alpha)); >+ > } > > @Test >diff --git a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/BezierCurveTests.java b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/BezierCurveTests.java >new file mode 100644 >index 0000000..88d3679 >--- /dev/null >+++ b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/BezierCurveTests.java >@@ -0,0 +1,345 @@ >+/******************************************************************************* >+ * Copyright (c) 2012 itemis AG and others. >+ * All rights reserved. This program and the accompanying materials >+ * are made available under the terms of the Eclipse Public License v1.0 >+ * which accompanies this distribution, and is available at >+ * http://www.eclipse.org/legal/epl-v10.html >+ * >+ * Contributors: >+ * Matthias Wienand (itemis AG) - initial API and implementation >+ * >+ *******************************************************************************/ >+package org.eclipse.gef4.geometry.tests; >+ >+import static org.junit.Assert.assertEquals; >+import static org.junit.Assert.assertFalse; >+import static org.junit.Assert.assertNotSame; >+import static org.junit.Assert.assertNull; >+import static org.junit.Assert.assertTrue; >+ >+import org.eclipse.gef4.geometry.Point; >+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.PointListUtils; >+import org.eclipse.gef4.geometry.utils.PrecisionUtils; >+import org.junit.Test; >+ >+public class BezierCurveTests { >+ >+ @Test >+ public void test_equals() { >+ BezierCurve c = new BezierCurve(1, 1, 1, 10, 10, 1, 10, 10); >+ assertFalse(c.equals(null)); >+ assertFalse(c.equals(new Rectangle(1, 2, 3, 4))); >+ assertEquals(c, c); >+ >+ BezierCurve cr = new BezierCurve(10, 10, 10, 1, 1, 10, 1, 1); >+ assertEquals(cr, cr); >+ assertEquals(c, cr); >+ assertEquals(cr, c); >+ >+ BezierCurve ce = c.getElevated(); >+ BezierCurve cre = cr.getElevated(); >+ assertEquals(c, ce); >+ assertEquals(ce, c); >+ assertEquals(cr, cre); >+ assertEquals(cre, cr); >+ assertEquals(c, cre); >+ assertEquals(cre, c); >+ assertEquals(cr, ce); >+ assertEquals(ce, cr); >+ >+ BezierCurve c2 = new BezierCurve(1, 2, 3, 4); >+ assertFalse(c.equals(c2)); >+ assertFalse(c2.equals(c)); >+ c2 = new BezierCurve(1, 1, 1, 10, 2, 3); >+ assertFalse(c.equals(c2)); >+ assertFalse(c2.equals(c)); >+ c2 = new BezierCurve(1, 1, 1, 10, 10, 1, 2, 3); >+ assertFalse(c.equals(c2)); >+ assertFalse(c2.equals(c)); >+ c2 = new BezierCurve(1, 1, 2, 9, 9, 2, 10, 10); >+ assertFalse(c.equals(c2)); >+ assertFalse(c2.equals(c)); >+ } >+ >+ @Test >+ public void test_constructors() { >+ BezierCurve c0 = new BezierCurve(1, 1, 1, 10, 10, 1, 10, 10); >+ assertEquals(c0, new BezierCurve(new Point(1, 1), new Point(1, 10), >+ new Point(10, 1), new Point(10, 10))); >+ assertEquals(c0, new BezierCurve(new CubicCurve(1, 1, 1, 10, 10, 1, 10, >+ 10))); >+ BezierCurve c1 = new BezierCurve(1, 1, 10, 1, 10, 10); >+ assertEquals(c1, new BezierCurve(new Point(1, 1), new Point(10, 1), >+ new Point(10, 10))); >+ assertEquals(c1, new BezierCurve( >+ new QuadraticCurve(1, 1, 10, 1, 10, 10))); >+ >+ // getCopy() >+ BezierCurve c0copy = c0.getCopy(); >+ assertEquals(c0, c0copy); >+ assertNotSame(c0, c0copy); >+ >+ c0copy.setP1(new Point(100, 100)); >+ assertFalse(c0.equals(c0copy)); >+ } >+ >+ @Test >+ public void test_contains_Point() { >+ BezierCurve c0 = new BezierCurve(1, 1, 1, 10, 10, 1, 10, 10); >+ assertFalse(c0.contains(new Point(0, 0))); >+ assertFalse(c0.contains(new Point(3, 3))); >+ assertFalse(c0.contains(new Point(3, 8))); >+ assertFalse(c0.contains(new Point(7, 3))); >+ assertFalse(c0.contains(new Point(7, 8))); >+ assertFalse(c0.contains(new Point(11, 11))); >+ assertTrue(c0.contains(new Point(1, 1))); >+ assertTrue(c0.contains(new Point(10, 10))); >+ >+ // evaluate curve at some parameter values and check that the returned >+ // points are contained by the curve >+ for (double t = 0; t <= 1; t += 0.02) >+ assertTrue(c0.contains(c0.get(t))); >+ } >+ >+ @Test >+ public void test_get() { >+ BezierCurve c0 = new BezierCurve(1, 1, 1, 10, 10, 1, 10, 10); >+ assertEquals(new Point(1, 1), c0.get(0)); >+ assertEquals(new Point(10, 10), c0.get(1)); >+ assertEquals(new Point(5.5, 5.5), c0.get(0.5)); >+ } >+ >+ @Test >+ public void test_getBounds() { >+ BezierCurve c0 = new BezierCurve(1, 1, 1, 10, 10, 1, 10, 10); >+ assertEquals(new Rectangle(1, 1, 9, 9), c0.getBounds()); >+ } >+ >+ @Test >+ public void test_getClipped() { >+ BezierCurve c0 = new BezierCurve(1, 1, 1, 10, 10, 1, 10, 10); >+ assertEquals(new BezierCurve(1, 1), c0.getClipped(0, 0)); >+ assertEquals(new BezierCurve(10, 10), c0.getClipped(1, 1)); >+ >+ BezierCurve c1 = c0.getClipped(0, 0.5); >+ BezierCurve c2 = c0.getClipped(0.5, 1); >+ assertEquals(new Point(1, 1), c1.get(0)); >+ assertEquals(new Point(5.5, 5.5), c1.get(1)); >+ assertEquals(new Point(5.5, 5.5), c2.get(0)); >+ assertEquals(new Point(10, 10), c2.get(1)); >+ } >+ >+ @Test >+ public void test_getControlBounds() { >+ BezierCurve c0 = new BezierCurve(1, 1, 1, 10, 10, 1, 10, 10); >+ assertEquals(new Rectangle(1, 1, 9, 9), c0.getControlBounds()); >+ >+ BezierCurve c1 = new BezierCurve(1, 5, 5, 8, 10, 1); >+ assertEquals(new Rectangle(1, 1, 9, 7), c1.getControlBounds()); >+ } >+ >+ @Test >+ public void test_getDerivative() { >+ BezierCurve c0 = new BezierCurve(1, 1, 1, 10, 10, 1, 10, 10); >+ BezierCurve d0 = c0.getDerivative(); >+ assertEquals(3, d0.getPoints().length); >+ // TODO: check the derivative for some points on the curve >+ } >+ >+ @Test >+ public void test_getOverlap() { >+ BezierCurve c0 = new BezierCurve(1, 1, 1, 10, 10, 1, 10, 10); >+ BezierCurve c1 = c0.getClipped(0, 0.5); >+ BezierCurve c2 = c0.getClipped(0.5, 1); >+ >+ BezierCurve o01 = c0.getOverlap(c1); >+ assertTrue(o01 != null); >+ assertTrue(c1.contains(o01)); >+ // assertEquals(c1, o01); >+ >+ /* >+ * TODO: The equality check may not return true for the computed overlap >+ * and the real overlap. This is because the overlap is only >+ * approximated. >+ */ >+ >+ BezierCurve o02 = c0.getOverlap(c2); >+ assertTrue(o02 != null); >+ assertTrue(c2.contains(o02)); >+ // assertEquals(c2, o02); >+ >+ assertNull(c1.getOverlap(c2)); >+ } >+ >+ private void check_values_with_getters(BezierCurve c, Point... points) { >+ Point p1 = points[0]; >+ assertEquals(p1, c.getP1()); >+ assertTrue(PrecisionUtils.equal(p1.x, c.getX1())); >+ assertTrue(PrecisionUtils.equal(p1.y, c.getY1())); >+ >+ Point p2 = points[points.length - 1]; >+ assertEquals(p2, c.getP2()); >+ assertTrue(PrecisionUtils.equal(p2.x, c.getX2())); >+ assertTrue(PrecisionUtils.equal(p2.y, c.getY2())); >+ >+ assertTrue(PointListUtils.equals(c.getPoints(), points)); >+ >+ for (int i = 0; i < points.length; i++) >+ assertEquals(points[i], c.getPoint(i)); >+ } >+ >+ @Test >+ public void test_point_getters() { >+ BezierCurve c0 = new BezierCurve(1, 1, 1, 10, 10, 1, 10, 10); >+ check_values_with_getters(c0, new Point[] { new Point(1, 1), >+ new Point(1, 10), new Point(10, 1), new Point(10, 10) }); >+ } >+ >+ @Test >+ public void test_point_setters() { >+ BezierCurve c0 = new BezierCurve(1, 1, 1, 10, 10, 1, 10, 10); >+ check_values_with_getters(c0, new Point[] { new Point(1, 1), >+ new Point(1, 10), new Point(10, 1), new Point(10, 10) }); >+ >+ c0.setP1(new Point(-30, 5)); >+ check_values_with_getters(c0, new Point[] { new Point(-30, 5), >+ new Point(1, 10), new Point(10, 1), new Point(10, 10) }); >+ >+ c0.setP2(new Point(31, 11)); >+ check_values_with_getters(c0, new Point[] { new Point(-30, 5), >+ new Point(1, 10), new Point(10, 1), new Point(31, 11) }); >+ >+ c0.setPoint(1, new Point(3, -3)); >+ check_values_with_getters(c0, new Point[] { new Point(-30, 5), >+ new Point(3, -3), new Point(10, 1), new Point(31, 11) }); >+ >+ c0.setPoint(2, new Point(-3, 3)); >+ check_values_with_getters(c0, new Point[] { new Point(-30, 5), >+ new Point(3, -3), new Point(-3, 3), new Point(31, 11) }); >+ } >+ >+ @Test >+ public void test_getParameterAt() { >+ BezierCurve c0 = new BezierCurve(1, 1, 1, 10, 10, 1, 10, 10); >+ assertTrue(PrecisionUtils.equal(0, c0.getParameterAt(new Point(1, 1)))); >+ assertTrue(PrecisionUtils >+ .equal(1, c0.getParameterAt(new Point(10, 10)))); >+ assertTrue(PrecisionUtils.equal(0.5, >+ c0.getParameterAt(new Point(5.5, 5.5)))); >+ >+ boolean thrown = false; >+ try { >+ c0.getParameterAt(null); >+ } catch (NullPointerException x) { >+ thrown = true; >+ } >+ assertTrue(thrown); >+ >+ thrown = false; >+ try { >+ c0.getParameterAt(new Point(3, 3)); >+ } catch (IllegalArgumentException x) { >+ thrown = true; >+ } >+ assertTrue(thrown); >+ } >+ >+ @Test >+ public void test_getTranslated() { >+ BezierCurve c0 = new BezierCurve(1, 1, 1, 10, 10, 1, 10, 10); >+ BezierCurve t0 = c0.getTranslated(new Point(-1, 4)); >+ assertEquals(new BezierCurve(0, 5, 0, 14, 9, 5, 9, 14), t0); >+ } >+ >+ @Test >+ public void test_overlaps() { >+ BezierCurve c0 = new BezierCurve(1, 1, 1, 10, 10, 1, 10, 10); >+ BezierCurve c1 = c0.getClipped(0, 0.5); >+ BezierCurve c2 = c0.getClipped(0.5, 1); >+ assertTrue(c0.overlaps(c1)); >+ assertTrue(c1.overlaps(c0)); >+ assertTrue(c0.overlaps(c2)); >+ assertTrue(c2.overlaps(c0)); >+ assertFalse(c1.overlaps(c2)); >+ assertFalse(c2.overlaps(c1)); >+ } >+ >+ @Test >+ public void test_split() { >+ BezierCurve c0 = new BezierCurve(1, 1, 1, 10, 10, 1, 10, 10); >+ BezierCurve c1 = c0.getClipped(0, 0.5); >+ BezierCurve c2 = c0.getClipped(0.5, 1); >+ BezierCurve[] split = c0.split(0.5); >+ assertEquals(c1, split[0]); >+ assertEquals(c2, split[1]); >+ } >+ >+ @Test >+ public void test_toBezier() { >+ BezierCurve c0 = new BezierCurve(1, 1, 1, 10, 10, 1, 10, 10); >+ BezierCurve[] beziers = c0.toBezier(); >+ assertEquals(1, beziers.length); >+ assertEquals(c0, beziers[0]); >+ } >+ >+ @Test >+ public void test_toCubic() { >+ BezierCurve c0 = new BezierCurve(1, 1); >+ assertNull(c0.toCubic()); >+ c0 = new BezierCurve(1, 1, 1, 10); >+ assertNull(c0.toCubic()); >+ c0 = new BezierCurve(1, 1, 1, 10, 10, 1); >+ assertNull(c0.toCubic()); >+ c0 = new BezierCurve(1, 1, 1, 10, 10, 1, 10, 10); >+ assertEquals(new CubicCurve(1, 1, 1, 10, 10, 1, 10, 10), c0.toCubic()); >+ c0 = new BezierCurve(1, 1, 1, 10, 10, 1, 67, 89, 10, 10); >+ assertEquals(new CubicCurve(1, 1, 1, 10, 10, 1, 10, 10), c0.toCubic()); >+ c0 = new BezierCurve(1, 1, 1, 10, 10, 1, 10, 10, 98, 76); >+ assertEquals(new CubicCurve(1, 1, 1, 10, 10, 1, 98, 76), c0.toCubic()); >+ } >+ >+ @Test >+ public void test_toQuadratic() { >+ BezierCurve c0 = new BezierCurve(1, 1); >+ assertNull(c0.toQuadratic()); >+ c0 = new BezierCurve(1, 1, 1, 10); >+ assertNull(c0.toQuadratic()); >+ c0 = new BezierCurve(1, 1, 1, 10, 10, 1); >+ assertEquals(new QuadraticCurve(1, 1, 1, 10, 10, 1), c0.toQuadratic()); >+ c0 = new BezierCurve(1, 1, 1, 10, 67, 89, 10, 1); >+ assertEquals(new QuadraticCurve(1, 1, 1, 10, 10, 1), c0.toQuadratic()); >+ c0 = new BezierCurve(1, 1, 1, 10, 10, 1, 98, 76); >+ assertEquals(new QuadraticCurve(1, 1, 1, 10, 98, 76), c0.toQuadratic()); >+ } >+ >+ @Test >+ public void test_toLine() { >+ BezierCurve c0 = new BezierCurve(1, 1); >+ assertNull(c0.toCubic()); >+ c0 = new BezierCurve(1, 1, 1, 10); >+ assertEquals(new Line(1, 1, 1, 10), c0.toLine()); >+ c0 = new BezierCurve(1, 1, 1, 10, 10, 1); >+ assertEquals(new Line(1, 1, 10, 1), c0.toLine()); >+ c0 = new BezierCurve(1, 1, 1, 10, 10, 1, 10, 10); >+ assertEquals(new Line(1, 1, 10, 10), c0.toLine()); >+ c0 = new BezierCurve(1, 1, 1, 10, 10, 1, 10, 10, 98, 76); >+ assertEquals(new Line(1, 1, 98, 76), c0.toLine()); >+ } >+ >+ @Test >+ public void test_toLineStrip() { >+ BezierCurve linear = new BezierCurve(0, 0, 1, 1); >+ Line[] lines = linear.toLineStrip(1); >+ assertEquals(1, lines.length); >+ assertEquals(new Line(0, 0, 1, 1), lines[0]); >+ assertEquals(linear.toLine(), lines[0]); >+ >+ // TODO: check complicated curves, too >+ } >+ >+} >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 b81d8d8..186cfdb 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 >@@ -154,7 +154,7 @@ public class CurveUtilsTests { > } > > @Test >- public void test_clip_with_QuadraticCurve() { >+ public void test_getClipped_with_QuadraticCurve() { > final int numPoints = 4; > final double step = 0.123456789; > >@@ -169,7 +169,7 @@ public class CurveUtilsTests { > QuadraticCurve c = new QuadraticCurve(points); > for (double t1 = 0; t1 <= 1; t1 += step) { > for (double t2 = 0; t2 <= 1; t2 += step) { >- QuadraticCurve cc = c.clip(t1, t2); >+ QuadraticCurve cc = c.getClipped(t1, t2); > assertEquals(c.get(t1), cc.get(0)); > assertEquals(c.get(t2), cc.get(1)); > } >diff --git a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/DimensionTests.java b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/DimensionTests.java >index 634af06..1837d62 100644 >--- a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/DimensionTests.java >+++ b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/DimensionTests.java >@@ -45,9 +45,12 @@ public class DimensionTests { > public void test_contains() { > Dimension d1 = new Dimension(0.1, 0.1); > Dimension d2 = new Dimension(0.2, 0.2); >+ Dimension d3 = new Dimension(0.1, 0.3); > > assertTrue(d2.contains(d1)); > assertFalse(d1.contains(d2)); >+ assertFalse(d2.contains(d3)); >+ assertTrue(d3.contains(d1)); > } > > @Test >@@ -64,16 +67,20 @@ public class DimensionTests { > */ > @Test > public void test_equals() { >- Dimension p1 = new Dimension(0.1, 0.1); >- Dimension p2 = new Dimension(0.2, 0.2); >- assertFalse(p1.equals(p2)); >+ Dimension d1 = new Dimension(0.1, 0.1); >+ Dimension d2 = new Dimension(0.2, 0.2); >+ Dimension d3 = new Dimension(0.1, 0.2); >+ Dimension d4 = new Dimension(0.2, 0.1); >+ assertFalse(d1.equals(d2)); >+ assertFalse(d1.equals(d3)); >+ assertFalse(d1.equals(d4)); > >- p1 = new Dimension(0.2, 0.2); >- assertTrue(p1.equals(p2)); >+ d1 = new Dimension(0.2, 0.2); >+ assertTrue(d1.equals(d2)); > > // wrong type >- p1 = new Dimension(1, 1); >- assertFalse(p1.equals(new org.eclipse.swt.graphics.Point(1, 1))); >+ d1 = new Dimension(1, 1); >+ assertFalse(d1.equals(new org.eclipse.swt.graphics.Point(1, 1))); > } > > @Test >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 83d7dcf..94039e1 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 >@@ -11,17 +11,19 @@ > * > *******************************************************************************/ > package org.eclipse.gef4.geometry.tests; >+ > import static org.junit.Assert.assertEquals; > import static org.junit.Assert.assertFalse; > import static org.junit.Assert.assertTrue; > > 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.Rectangle; > import org.junit.Test; > >- > /** > * Unit tests for {@link Ellipse}. > * >@@ -34,35 +36,32 @@ public class EllipseTests { > .getPrecisionFraction(); > > @Test >- public void test_contains_with_Point() { >+ public void test_equals() { >+ Ellipse e = new Ellipse(0, 0, 100, 50); >+ assertFalse(e.equals(null)); >+ assertFalse(e.equals(new Point())); >+ assertEquals(e, e); >+ assertEquals(e, new Ellipse(0, 0, 100, 50)); >+ assertEquals(e, new Ellipse(new Rectangle(0, 0, 100, 50))); >+ assertEquals(e, e.getCopy()); >+ assertFalse(e.equals(new Ellipse(0, 0, 100, 10))); >+ assertFalse(e.equals(new Ellipse(0, 0, 10, 50))); >+ assertFalse(e.equals(new Ellipse(10, 0, 100, 50))); >+ assertFalse(e.equals(new Ellipse(0, 10, 100, 50))); >+ } >+ >+ @Test >+ public void test_contains_Point() { > Rectangle r = new Rectangle(34.3435, 56.458945, 123.3098, 146.578); > Ellipse e = new Ellipse(r); > >- assertTrue(e.contains(r.getCentroid())); >- >- assertTrue(e.contains(r.getLeft())); >- assertTrue(e.contains(r.getLeft().getTranslated(PRECISION_FRACTION * 1, >- 0))); >- assertFalse(e.contains(r.getLeft().getTranslated( >- -PRECISION_FRACTION * 1000, 0))); >+ checkPointContainment(r, e); > >+ // these things could not be tested in the general case, because of >+ // AWT's behavior > assertTrue(e.contains(r.getTop())); >- assertTrue(e.contains(r.getTop().getTranslated(0, >- PRECISION_FRACTION * 100))); >- assertFalse(e.contains(r.getTop().getTranslated(0, >- -PRECISION_FRACTION * 100))); >- > assertTrue(e.contains(r.getRight())); >- assertTrue(e.contains(r.getRight().getTranslated( >- -PRECISION_FRACTION * 100, 0))); >- assertFalse(e.contains(r.getRight().getTranslated( >- PRECISION_FRACTION * 100, 0))); >- > assertTrue(e.contains(r.getBottom())); >- assertTrue(e.contains(r.getBottom().getTranslated(0, >- -PRECISION_FRACTION * 100))); >- assertFalse(e.contains(r.getBottom().getTranslated(0, >- PRECISION_FRACTION * 100))); > > for (Point p : e.getIntersections(new Line(r.getTopLeft(), r > .getBottomRight()))) { >@@ -72,10 +71,75 @@ public class EllipseTests { > .getBottomLeft()))) { > assertTrue(e.contains(p)); > } >+ >+ for (CubicCurve c : e.getOutlineSegments()) { >+ assertTrue(e.contains(c.get(0.5))); >+ } >+ } >+ >+ private void checkPointContainment(Rectangle r, IGeometry g) { >+ assertFalse(g.contains(r.getTopLeft())); >+ assertFalse(g.contains(r.getTopRight())); >+ assertFalse(g.contains(r.getBottomLeft())); >+ assertFalse(g.contains(r.getBottomRight())); >+ >+ assertTrue(g.contains(r.getCentroid())); >+ >+ assertTrue(g.contains(r.getLeft())); >+ assertTrue(g.contains(r.getLeft().getTranslated(PRECISION_FRACTION * 1, >+ 0))); >+ assertFalse(g.contains(r.getLeft().getTranslated( >+ -PRECISION_FRACTION * 1000, 0))); >+ >+ // due to AWT's behavior, we won't check getTop() but a point very near >+ // to it, so that the Path() will survive these tests, too >+ assertTrue(g.contains(r.getTop().getTranslated(0, 1))); >+ assertTrue(g.contains(r.getTop().getTranslated(0, >+ PRECISION_FRACTION * 100))); >+ assertFalse(g.contains(r.getTop().getTranslated(0, >+ -PRECISION_FRACTION * 100))); >+ >+ // due to AWT's behavior, we won't check getRight() but a point very >+ // near to it, so that the Path() will survive these tests, too >+ assertTrue(g.contains(r.getRight().getTranslated(-1, 0))); >+ assertTrue(g.contains(r.getRight().getTranslated( >+ -PRECISION_FRACTION * 100, 0))); >+ assertFalse(g.contains(r.getRight().getTranslated( >+ PRECISION_FRACTION * 100, 0))); >+ >+ // due to AWT's behavior, we won't check getBottom() but a point very >+ // near to it, so that the Path() will survive these tests, too >+ assertTrue(g.contains(r.getBottom().getTranslated(0, -1))); >+ assertTrue(g.contains(r.getBottom().getTranslated(0, >+ -PRECISION_FRACTION * 100))); >+ assertFalse(g.contains(r.getBottom().getTranslated(0, >+ PRECISION_FRACTION * 100))); >+ } >+ >+ @Test >+ public void test_contains_Line() { >+ Ellipse e = new Ellipse(0, 0, 100, 50); >+ assertFalse(e.contains(new Line(-10, -10, 10, -10))); >+ assertFalse(e.contains(new Line(-10, -10, 50, 50))); >+ assertTrue(e.contains(new Line(1, 25, 99, 25))); >+ assertTrue(e.contains(new Line(0, 25, 100, 25))); > } > > @Test >- public void test_intersects_with_Line() { >+ public void test_getCenter() { >+ Ellipse e = new Ellipse(0, 0, 100, 50); >+ assertEquals(new Point(50, 25), e.getCenter()); >+ e.scale(2); >+ assertEquals(new Point(50, 25), e.getCenter()); >+ e.scale(0.5); >+ e.scale(2, new Point()); >+ assertEquals(new Point(100, 50), e.getCenter()); >+ e.translate(-100, -50); >+ assertEquals(new Point(), e.getCenter()); >+ } >+ >+ @Test >+ public void test_intersects_Line() { > Rectangle r = new Rectangle(34.3435, 56.458945, 123.3098, 146.578); > Ellipse e = new Ellipse(r); > for (Line l : r.getOutlineSegments()) { >@@ -83,8 +147,45 @@ public class EllipseTests { > } > } > >+ private void checkPoints(Point[] expected, Point[] obtained) { >+ assertEquals(expected.length, obtained.length); >+ for (Point e : expected) { >+ boolean found = false; >+ for (Point o : obtained) { >+ if (e.equals(o)) { >+ found = true; >+ break; >+ } >+ } >+ assertTrue(found); >+ } >+ } >+ > @Test >- public void test_get_intersections_with_Ellipse_strict() { >+ public void test_getIntersections_Line() { >+ Ellipse e = new Ellipse(0, 0, 100, 50); >+ Line lh = new Line(0, 25, 100, 25); >+ Point[] is = e.getIntersections(lh); >+ checkPoints(new Point[] { new Point(0, 25), new Point(100, 25) }, is); >+ Line lv = new Line(50, 0, 50, 50); >+ is = e.getIntersections(lv); >+ checkPoints(new Point[] { new Point(50, 0), new Point(50, 50) }, is); >+ >+ lh = lh.getTranslated(new Point(0, -25)).toLine(); >+ is = e.getIntersections(lh); >+ checkPoints(new Point[] { new Point(50, 0) }, is); >+ >+ lv = lv.getTranslated(new Point(-50, 0)).toLine(); >+ is = e.getIntersections(lv); >+ checkPoints(new Point[] { new Point(0, 25) }, is); >+ >+ Line li = new Line(-100, 100, 0, 50); >+ is = e.getIntersections(li); >+ assertEquals(0, is.length); >+ } >+ >+ @Test >+ public void test_get_intersections_Ellipse_strict() { > Rectangle r = new Rectangle(34.3435, 56.458945, 123.3098, 146.578); > Ellipse e1 = new Ellipse(r); > Ellipse e2 = new Ellipse(r); >@@ -203,7 +304,7 @@ public class EllipseTests { > } > > @Test >- public void test_getIntersections_with_Ellipse_tolerance() { >+ public void test_getIntersections_Ellipse_tolerance() { > Rectangle r = new Rectangle(34.3435, 56.458945, 123.3098, 146.578); > Ellipse e1 = new Ellipse(r); > Ellipse e2 = new Ellipse(r); >@@ -245,7 +346,7 @@ public class EllipseTests { > > // @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() { >+ public void test_getIntersections_Ellipse_Bezier_special() { > // 3 nearly tangential intersections > Ellipse e1 = new Ellipse(126, 90, 378, 270); > Ellipse e2 = new Ellipse(222, 77, 200, 200); >@@ -263,7 +364,7 @@ public class EllipseTests { > } > > @Test >- public void test_getIntersections_with_Ellipse_Bezier_special_tolerance() { >+ public void test_getIntersections_Ellipse_Bezier_special_tolerance() { > // 3 nearly tangential intersections > Ellipse e1 = new Ellipse(126, 90, 378, 270); > Ellipse e2 = new Ellipse(222, 77, 200, 200); >@@ -279,4 +380,17 @@ public class EllipseTests { > intersectionsTolerance(e1, e2); // TODO: find out the 3 expected points > } > >+ @Test >+ public void test_toPath() { >+ Rectangle r = new Rectangle(0, 0, 100, 50); >+ Ellipse e = new Ellipse(r); >+ checkPointContainment(r, e.toPath()); >+ } >+ >+ @Test >+ public void test_toString() { >+ Ellipse e = new Ellipse(0, 0, 100, 50); >+ assertEquals("Ellipse (0.0, 0.0, 100.0, 50.0)", e.toString()); >+ } >+ > } >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 c6a4b53..f6ab42c 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 >@@ -14,6 +14,7 @@ package org.eclipse.gef4.geometry.tests; > > import static org.junit.Assert.assertEquals; > import static org.junit.Assert.assertFalse; >+import static org.junit.Assert.assertNotNull; > import static org.junit.Assert.assertNull; > import static org.junit.Assert.assertTrue; > >@@ -224,6 +225,7 @@ public class LineTests { > // 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)); >@@ -254,6 +256,12 @@ public class LineTests { > Line elsewhere = new Line(new Point(-5, 1), new Point(5, 10)); > assertNull(normal.getIntersection(elsewhere)); > assertNull(elsewhere.getIntersection(normal)); >+ >+ // single end point intersection with parallel lines: >+ // X-------X-------X >+ Line l1 = new Line(400.0, 102.48618784530387, 399.99999999999994, 100.0); >+ Line l2 = new Line(400.0, 51.10497237569061, 399.99999999999994, 100.0); >+ assertNotNull(l1.getIntersection(l2)); > } > > @Test >diff --git a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/PointTests.java b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/PointTests.java >index 6d81c3e..945cf34 100644 >--- a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/PointTests.java >+++ b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/PointTests.java >@@ -126,6 +126,10 @@ public class PointTests { > q = new Point(3, 6); > assertTrue(p.getScaled(3, 6).equals(q)); > assertTrue(q.getScaled(1f / 3f, 1f / 6f).equals(p)); >+ >+ // scale around some other point >+ Point c = new Point(10, 10); >+ assertEquals(new Point(9, 8), q.getScaled(1d / 7d, 1d / 2d, c)); > } > > @Test >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 49e40bd..e4e9ecd 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 >@@ -90,6 +90,11 @@ public class PolygonTests { > assertFalse(new Polygon(new Point(), new Point(0, 5), new Point(5, 5), > new Point(5, 0), new Point(2.5, 2.5)).contains(new Line(1, 2, > 4, 2))); >+ >+ Polygon mouth = new Polygon(new Point(0, 5), new Point(2, 1), >+ new Point(4, 1), new Point(6, 5), new Point(4, 6), new Point(6, >+ 7), new Point(4, 10), new Point(2, 10)); >+ assertFalse(mouth.contains(new Line(6, 5, 6, 7))); > } > > /** >@@ -496,6 +501,24 @@ public class PolygonTests { > } > > @Test >+ public void test_getTriangulation() { >+ Polygon p = new Polygon(150.0, 50.0, 50.0, 100.0, 23.0, 165.0, 50.0, >+ 250.0, 135.0, 294.0, 250.0, 300.0, 137.0, 260.0, 63.0, 168.0, >+ 113.0, 105.0, 136.0, 206.0, 150.0, 50.0); >+ >+ // test that it does not throw a NullPointerException >+ p.getTriangulation(); >+ assertTrue(true); >+ // TODO: test that the triangulation is correct >+ >+ p = new Polygon(150.0, 50.0, 50.0, 100.0, 32.0, 168.0, 50.0, 250.0, >+ 136.0, 298.0, 250.0, 300.0, 122.0, 252.0, 67.0, 180.0, 114.0, >+ 95.0, 136.0, 194.0, 150.0, 50.0); >+ p.getTriangulation(); >+ assertTrue(true); >+ } >+ >+ @Test > public void test_intersects_Ellipse() { > assertTrue(RHOMB.touches(new Ellipse(0, 0, 4, 4))); > } >diff --git a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/PolylineTests.java b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/PolylineTests.java >index cf74fe0..0a6e498 100644 >--- a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/PolylineTests.java >+++ b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/PolylineTests.java >@@ -12,17 +12,35 @@ > *******************************************************************************/ > package org.eclipse.gef4.geometry.tests; > >+import static org.junit.Assert.assertEquals; > import static org.junit.Assert.assertFalse; > import static org.junit.Assert.assertTrue; > >+import java.util.Arrays; >+import java.util.Collections; >+import java.util.List; >+ > import org.eclipse.gef4.geometry.Point; >+import org.eclipse.gef4.geometry.planar.Line; > import org.eclipse.gef4.geometry.planar.Polyline; > import org.junit.Test; > > public class PolylineTests { > >- private static final Polyline POLYLINE = new Polyline(new Point[] { >- new Point(0, 0), new Point(1, 0), new Point(6, 5) }); >+ private static final Point[] POINTS = new Point[] { new Point(0, 0), >+ new Point(1, 0), new Point(6, 5) }; >+ private static final Polyline POLYLINE = new Polyline(POINTS); >+ >+ @Test >+ public void test_equals() { >+ assertEquals(POLYLINE, POLYLINE); >+ assertFalse(POLYLINE.equals((Object) null)); >+ assertFalse(POLYLINE.equals(new Line(1, 2, 3, 4))); >+ >+ List<Point> points = Arrays.asList(POINTS); >+ Collections.reverse(points); >+ assertEquals(POLYLINE, new Polyline(points.toArray(new Point[] {}))); >+ } > > @Test > public void test_contains_with_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 4ac9488..5dc75a5 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 >@@ -40,10 +40,6 @@ public class RectangleTests { > void action(Rectangle rect, Point tl, Point br); > } > >- private interface IPairAction { >- void action(Rectangle r1, Rectangle r2); >- } >- > private static final double PRECISION_FRACTION = TestUtils > .getPrecisionFraction(); > >@@ -53,32 +49,6 @@ public class RectangleTests { > private static final double UNRECOGNIZABLE_FRACTION = PRECISION_FRACTION > - PRECISION_FRACTION / 10; > >- private void forRectanglePairs(IPairAction action) { >- for (double x11 = -2; x11 <= 2.4; x11 += 1.1) { >- for (double y11 = -2; y11 <= 2.4; y11 += 1.1) { >- Point p11 = new Point(x11, y11); >- for (double x12 = -2; x12 <= 2.4; x12 += 1.1) { >- for (double y12 = -2; y12 <= 2.4; y12 += 1.1) { >- Point p12 = new Point(x12, y12); >- Rectangle r1 = new Rectangle(p11, p12); >- for (double x21 = -2; x21 <= 2.4; x21 += 1.1) { >- for (double y21 = -2; y21 <= 2.4; y21 += 1.1) { >- Point p21 = new Point(x21, y21); >- for (double x22 = -2; x22 <= 2.4; x22 += 1.1) { >- for (double y22 = -2; y22 <= 2.4; y22 += 1.1) { >- Point p22 = new Point(x22, y22); >- Rectangle r2 = new Rectangle(p21, p22); >- action.action(r1, r2); >- } >- } >- } >- } >- } >- } >- } >- } >- } >- > private void forRectangles(IAction action) { > for (double x1 = -2; x1 <= 2; x1 += 0.4) { > for (double y1 = -2; y1 <= 2; y1 += 0.4) { >@@ -198,6 +168,12 @@ public class RectangleTests { > assertTrue(recognizableExpanded.contains(preciseRect)); > assertFalse(recognizableShrinked.contains(preciseRect)); > assertFalse(recognizableShrinked.contains(recognizableExpanded)); >+ >+ // Regression test for a contains() bug that caused false positives for >+ // a "containing" Rectangle with smaller x and y coordinates and greater >+ // width and height as the "contained" one. >+ assertFalse(new Rectangle(0, 0, 100, 100).contains(new Rectangle(200, >+ 200, 1, 1))); > } > > @Test >diff --git a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/RegionTests.java b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/RegionTests.java >new file mode 100644 >index 0000000..e042f32 >--- /dev/null >+++ b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/RegionTests.java >@@ -0,0 +1,103 @@ >+/******************************************************************************* >+ * Copyright (c) 2012 itemis AG and others. >+ * All rights reserved. This program and the accompanying materials >+ * are made available under the terms of the Eclipse Public License v1.0 >+ * which accompanies this distribution, and is available at >+ * http://www.eclipse.org/legal/epl-v10.html >+ * >+ * Contributors: >+ * Matthias Wienand (itemis AG) - initial API and implementation >+ * >+ *******************************************************************************/ >+package org.eclipse.gef4.geometry.tests; >+ >+import static org.junit.Assert.assertEquals; >+import static org.junit.Assert.assertFalse; >+import static org.junit.Assert.assertTrue; >+ >+import org.eclipse.gef4.geometry.planar.Rectangle; >+import org.eclipse.gef4.geometry.planar.Region; >+import org.eclipse.gef4.geometry.utils.PrecisionUtils; >+import org.junit.Test; >+ >+public class RegionTests { >+ >+ @Test >+ public void test_constructor() { >+ Region region = new Region(); >+ assertEquals(0, region.getShapes().length); >+ >+ region = new Region(new Rectangle(0, 0, 100, 100)); >+ assertEquals(1, region.getShapes().length); >+ >+ region = new Region(region); >+ assertEquals(1, region.getShapes().length); >+ } >+ >+ @Test >+ public void test_copy_semantics() { >+ Rectangle r1 = new Rectangle(0, 0, 100, 100); >+ Region a1 = new Region(r1); >+ r1.setWidth(50); >+ >+ // constructor copies Rectangles: >+ // changing r1 does not change a1 >+ assertTrue(PrecisionUtils.equal(100, a1.getShapes()[0].getWidth())); >+ >+ Region a2 = a1.getCopy(); >+ a2.getShapes()[0].setWidth(50); >+ >+ // getCopy() copies Rectangles: >+ // changing a2 does not change a1 >+ assertTrue(PrecisionUtils.equal(100, a1.getShapes()[0].getWidth())); >+ >+ a2 = new Region(a1); >+ a2.getShapes()[0].setWidth(50); >+ >+ // constructor copies Rectangles: >+ // changing a2 does not change a1 >+ assertTrue(PrecisionUtils.equal(100, a1.getShapes()[0].getWidth())); >+ } >+ >+ @Test >+ public void test_cover_single_rectangle() { >+ Rectangle r1 = new Rectangle(100, 100, 100, 100); >+ Region region = new Region(r1); >+ >+ assertTrue(region.contains(r1)); >+ assertTrue( >+ "A Region of just a single Rectangle should use this Rectangle as its only internal shape.", >+ region.getShapes()[0].equals(r1)); >+ >+ assertFalse(region.contains(new Rectangle(0, 0, 50, 50))); >+ assertFalse(region.contains(new Rectangle(50, 50, 100, 100))); >+ } >+ >+ @Test >+ public void test_cover_two_distinct_rectangles() { >+ Rectangle r1 = new Rectangle(100, 100, 100, 100); >+ Rectangle r2 = new Rectangle(500, 100, 100, 100); >+ Region region = new Region(r1, r2); >+ >+ assertTrue(region.contains(r1)); >+ assertTrue(region.contains(r2)); >+ >+ assertFalse(region.contains(new Rectangle(0, 0, 50, 50))); >+ assertFalse(region.contains(new Rectangle(50, 50, 100, 100))); >+ } >+ >+ @Test >+ public void test_cover_two_intersecting_rectangles() { >+ Rectangle r1 = new Rectangle(50, 50, 50, 200); >+ Rectangle r2 = new Rectangle(50, 200, 200, 50); >+ Region region = new Region(r1, r2); >+ >+ assertTrue(region.contains(r1)); >+ assertTrue(region.contains(r2)); >+ assertTrue(region.contains(r1.getIntersected(r2))); >+ >+ assertFalse(region.contains(new Rectangle(0, 0, 10, 10))); >+ assertFalse(region.contains(new Rectangle(25, 25, 50, 50))); >+ } >+ >+} >diff --git a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/RingTests.java b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/RingTests.java >new file mode 100644 >index 0000000..1a72166 >--- /dev/null >+++ b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/RingTests.java >@@ -0,0 +1,1120 @@ >+/******************************************************************************* >+ * Copyright (c) 2012 itemis AG and others. >+ * All rights reserved. This program and the accompanying materials >+ * are made available under the terms of the Eclipse Public License v1.0 >+ * which accompanies this distribution, and is available at >+ * http://www.eclipse.org/legal/epl-v10.html >+ * >+ * Contributors: >+ * Matthias Wienand (itemis AG) - initial API and implementation >+ * >+ *******************************************************************************/ >+package org.eclipse.gef4.geometry.tests; >+ >+import static org.junit.Assert.assertEquals; >+import static org.junit.Assert.assertFalse; >+import static org.junit.Assert.assertNotSame; >+import static org.junit.Assert.assertTrue; >+ >+import java.lang.reflect.Method; >+ >+import org.eclipse.gef4.geometry.Point; >+import org.eclipse.gef4.geometry.planar.Line; >+import org.eclipse.gef4.geometry.planar.Polygon; >+import org.eclipse.gef4.geometry.planar.Ring; >+import org.junit.Before; >+import org.junit.Test; >+import org.junit.experimental.runners.Enclosed; >+import org.junit.runner.RunWith; >+ >+@RunWith(Enclosed.class) >+public class RingTests { >+ >+ public static class TriangulateTriangleWithOutlinePoints { >+ >+ private Polygon[] triangulate(Polygon p, Point p1, Point p2) { >+ try { >+ Class<?> parameterTypes[] = new Class<?>[] { Polygon.class, >+ Point.class, Point.class }; >+ Method triangulate = Ring.class.getDeclaredMethod( >+ "triangulate", parameterTypes); >+ triangulate.setAccessible(true); >+ return (Polygon[]) triangulate.invoke(null, p, p1, p2); >+ } catch (Exception x) { >+ throw new IllegalStateException(x); >+ } >+ } >+ >+ Polygon p; >+ >+ @Before >+ public void setUp() { >+ p = new Polygon(new Point(100, 100), new Point(100, 300), >+ new Point(300, 200)); >+ } >+ >+ @Test >+ public void no_polygon() { >+ try { >+ triangulate(null, new Point(0, 0), new Point(1, 1)); >+ } catch (IllegalStateException x) { >+ Throwable cause = x; >+ while (cause.getCause() != null) >+ cause = cause.getCause(); >+ >+ assertTrue(cause.getClass().equals( >+ IllegalArgumentException.class)); >+ } >+ } >+ >+ @Test >+ public void no_triangle() { >+ try { >+ triangulate(new Polygon(0, 0, 1, 0, 1, 1, 0, 1), >+ new Point(0, 0), new Point(1, 1)); >+ } catch (IllegalStateException x) { >+ Throwable cause = x; >+ while (cause.getCause() != null) >+ cause = cause.getCause(); >+ >+ assertTrue(cause.getClass().equals( >+ IllegalArgumentException.class)); >+ } >+ } >+ >+ @Test >+ public void p1_not_on_polygon() { >+ try { >+ triangulate(new Polygon(0, 0, 1, 1, 0, 1), new Point(1, 0), >+ new Point(0, 1)); >+ } catch (IllegalStateException x) { >+ Throwable cause = x; >+ while (cause.getCause() != null) >+ cause = cause.getCause(); >+ >+ assertTrue(cause.getClass().equals( >+ IllegalArgumentException.class)); >+ } >+ } >+ >+ @Test >+ public void p2_not_on_polygon() { >+ try { >+ triangulate(new Polygon(0, 0, 1, 1, 0, 1), new Point(0, 1), >+ new Point(1, 0)); >+ } catch (IllegalStateException x) { >+ Throwable cause = x; >+ while (cause.getCause() != null) >+ cause = cause.getCause(); >+ >+ assertTrue(cause.getClass().equals( >+ IllegalArgumentException.class)); >+ } >+ } >+ >+ @Test >+ public void both_points_on_first_edge() { >+ // inner >+ Polygon[] r = triangulate(p, new Point(100, 150), new Point(100, >+ 250)); >+ assertEquals("1 resulting polygon", 1, r.length); >+ assertEquals("result equal to original", r[0], p); >+ assertNotSame("a copy is returned", r[0], p); >+ >+ r = triangulate(p, new Point(100, 250), new Point(100, 150)); >+ assertEquals("1 resulting polygon", 1, r.length); >+ assertEquals("result equal to original", r[0], p); >+ assertNotSame("a copy is returned", r[0], p); >+ >+ // start - inner >+ r = triangulate(p, new Point(100, 100), new Point(100, 200)); >+ assertEquals("1 resulting polygon", 1, r.length); >+ assertEquals("result equal to original", r[0], p); >+ assertNotSame("a copy is returned", r[0], p); >+ >+ r = triangulate(p, new Point(100, 200), new Point(100, 100)); >+ assertEquals("1 resulting polygon", 1, r.length); >+ assertEquals("result equal to original", r[0], p); >+ assertNotSame("a copy is returned", r[0], p); >+ >+ // end - inner >+ r = triangulate(p, new Point(100, 300), new Point(100, 200)); >+ assertEquals("1 resulting polygon", 1, r.length); >+ assertEquals("result equal to original", r[0], p); >+ assertNotSame("a copy is returned", r[0], p); >+ >+ r = triangulate(p, new Point(100, 200), new Point(100, 300)); >+ assertEquals("1 resulting polygon", 1, r.length); >+ assertEquals("result equal to original", r[0], p); >+ assertNotSame("a copy is returned", r[0], p); >+ >+ // start - end >+ r = triangulate(p, new Point(100, 100), new Point(100, 300)); >+ assertEquals("1 resulting polygon", 1, r.length); >+ assertEquals("result equal to original", r[0], p); >+ assertNotSame("a copy is returned", r[0], p); >+ >+ r = triangulate(p, new Point(100, 300), new Point(100, 100)); >+ assertEquals("1 resulting polygon", 1, r.length); >+ assertEquals("result equal to original", r[0], p); >+ assertNotSame("a copy is returned", r[0], p); >+ } >+ >+ @Test >+ public void both_points_on_second_edge() { >+ // inner >+ Polygon[] r = triangulate(p, new Point(250, 225), new Point(150, >+ 275)); >+ assertEquals("1 resulting polygon", 1, r.length); >+ assertEquals("result equal to original", r[0], p); >+ assertNotSame("a copy is returned", r[0], p); >+ >+ r = triangulate(p, new Point(150, 275), new Point(250, 225)); >+ assertEquals("1 resulting polygon", 1, r.length); >+ assertEquals("result equal to original", r[0], p); >+ assertNotSame("a copy is returned", r[0], p); >+ >+ // start - inner >+ r = triangulate(p, new Point(300, 200), new Point(200, 250)); >+ assertEquals("1 resulting polygon", 1, r.length); >+ assertEquals("result equal to original", r[0], p); >+ assertNotSame("a copy is returned", r[0], p); >+ >+ r = triangulate(p, new Point(200, 250), new Point(300, 200)); >+ assertEquals("1 resulting polygon", 1, r.length); >+ assertEquals("result equal to original", r[0], p); >+ assertNotSame("a copy is returned", r[0], p); >+ >+ // end - inner >+ r = triangulate(p, new Point(100, 300), new Point(200, 250)); >+ assertEquals("1 resulting polygon", 1, r.length); >+ assertEquals("result equal to original", r[0], p); >+ assertNotSame("a copy is returned", r[0], p); >+ >+ r = triangulate(p, new Point(200, 250), new Point(100, 300)); >+ assertEquals("1 resulting polygon", 1, r.length); >+ assertEquals("result equal to original", r[0], p); >+ assertNotSame("a copy is returned", r[0], p); >+ >+ // start - end >+ r = triangulate(p, new Point(100, 300), new Point(300, 200)); >+ assertEquals("1 resulting polygon", 1, r.length); >+ assertEquals("result equal to original", r[0], p); >+ assertNotSame("a copy is returned", r[0], p); >+ >+ r = triangulate(p, new Point(300, 200), new Point(100, 300)); >+ assertEquals("1 resulting polygon", 1, r.length); >+ assertEquals("result equal to original", r[0], p); >+ assertNotSame("a copy is returned", r[0], p); >+ } >+ >+ @Test >+ public void both_points_on_third_edge() { >+ // inner >+ Polygon[] r = triangulate(p, new Point(150, 125), new Point(250, >+ 175)); >+ assertEquals("1 resulting polygon", 1, r.length); >+ assertEquals("result equal to original", r[0], p); >+ assertNotSame("a copy is returned", r[0], p); >+ >+ r = triangulate(p, new Point(250, 175), new Point(150, 125)); >+ assertEquals("1 resulting polygon", 1, r.length); >+ assertEquals("result equal to original", r[0], p); >+ assertNotSame("a copy is returned", r[0], p); >+ >+ // start - inner >+ r = triangulate(p, new Point(100, 100), new Point(250, 175)); >+ assertEquals("1 resulting polygon", 1, r.length); >+ assertEquals("result equal to original", r[0], p); >+ assertNotSame("a copy is returned", r[0], p); >+ >+ r = triangulate(p, new Point(250, 175), new Point(100, 100)); >+ assertEquals("1 resulting polygon", 1, r.length); >+ assertEquals("result equal to original", r[0], p); >+ assertNotSame("a copy is returned", r[0], p); >+ >+ // end - inner >+ r = triangulate(p, new Point(150, 125), new Point(300, 200)); >+ assertEquals("1 resulting polygon", 1, r.length); >+ assertEquals("result equal to original", r[0], p); >+ assertNotSame("a copy is returned", r[0], p); >+ >+ r = triangulate(p, new Point(300, 200), new Point(150, 125)); >+ assertEquals("1 resulting polygon", 1, r.length); >+ assertEquals("result equal to original", r[0], p); >+ assertNotSame("a copy is returned", r[0], p); >+ >+ // start - end >+ r = triangulate(p, new Point(100, 100), new Point(300, 200)); >+ assertEquals("1 resulting polygon", 1, r.length); >+ assertEquals("result equal to original", r[0], p); >+ assertNotSame("a copy is returned", r[0], p); >+ >+ r = triangulate(p, new Point(300, 200), new Point(100, 100)); >+ assertEquals("1 resulting polygon", 1, r.length); >+ assertEquals("result equal to original", r[0], p); >+ assertNotSame("a copy is returned", r[0], p); >+ } >+ >+ private static boolean exists(Polygon[] list, Polygon item) { >+ for (Polygon p : list) >+ if (p.equals(item)) >+ return true; >+ return false; >+ } >+ >+ @Test >+ public void edge1_and_edge2() { >+ Polygon[] r = triangulate(p, new Point(100, 200), new Point(200, >+ 250)); >+ assertEquals("3 resulting polygons", 3, r.length); >+ assertEquals( >+ "isolated-vertex polygon exists", >+ true, >+ exists(r, new Polygon(new Point(100, 300), new Point(100, >+ 200), new Point(200, 250)))); >+ // TODO: test for existence of the other two pieces >+ >+ r = triangulate(p, new Point(200, 250), new Point(100, 200)); >+ assertEquals("3 resulting polygons", 3, r.length); >+ assertEquals( >+ "isolated-vertex polygon exists", >+ true, >+ exists(r, new Polygon(new Point(100, 300), new Point(100, >+ 200), new Point(200, 250)))); >+ // TODO: test for existence of the other two pieces >+ } >+ >+ @Test >+ public void edge1_and_edge3() { >+ Polygon[] r = triangulate(p, new Point(100, 200), new Point(200, >+ 150)); >+ assertEquals("3 resulting polygons", 3, r.length); >+ assertEquals( >+ "isolated-vertex polygon exists", >+ true, >+ exists(r, new Polygon(new Point(100, 100), new Point(100, >+ 200), new Point(200, 150)))); >+ // TODO: test for existence of the other two pieces >+ >+ r = triangulate(p, new Point(200, 150), new Point(100, 200)); >+ assertEquals("3 resulting polygons", 3, r.length); >+ assertEquals( >+ "isolated-vertex polygon exists", >+ true, >+ exists(r, new Polygon(new Point(100, 100), new Point(100, >+ 200), new Point(200, 150)))); >+ // TODO: test for existence of the other two pieces >+ } >+ >+ @Test >+ public void edge2_and_edge3() { >+ Polygon[] r = triangulate(p, new Point(200, 250), new Point(200, >+ 150)); >+ assertEquals("3 resulting polygons", 3, r.length); >+ assertEquals( >+ "isolated-vertex polygon exists", >+ true, >+ exists(r, new Polygon(new Point(200, 250), new Point(200, >+ 150), new Point(300, 200)))); >+ // TODO: test for existence of the other two pieces >+ >+ r = triangulate(p, new Point(200, 150), new Point(200, 250)); >+ assertEquals("3 resulting polygons", 3, r.length); >+ assertEquals( >+ "isolated-vertex polygon exists", >+ true, >+ exists(r, new Polygon(new Point(200, 250), new Point(200, >+ 150), new Point(300, 200)))); >+ // TODO: test for existence of the other two pieces >+ } >+ >+ @Test >+ public void edge1_vertex3() { >+ Polygon[] r = triangulate(p, new Point(100, 200), new Point(300, >+ 200)); >+ assertEquals("2 resulting polygons", 2, r.length); >+ assertEquals("left-side polygon exists", true, >+ exists(r, new Polygon(100, 100, 100, 200, 300, 200))); >+ assertEquals("right-side polygon exists", true, >+ exists(r, new Polygon(100, 300, 100, 200, 300, 200))); >+ >+ r = triangulate(p, new Point(300, 200), new Point(100, 200)); >+ assertEquals("2 resulting polygons", 2, r.length); >+ assertEquals("left-side polygon exists", true, >+ exists(r, new Polygon(100, 100, 100, 200, 300, 200))); >+ assertEquals("right-side polygon exists", true, >+ exists(r, new Polygon(100, 300, 100, 200, 300, 200))); >+ } >+ >+ @Test >+ public void edge2_vertex1() { >+ Polygon[] r = triangulate(p, new Point(200, 250), new Point(100, >+ 100)); >+ assertEquals("2 resulting polygons", 2, r.length); >+ assertEquals("left-side polygon exists", true, >+ exists(r, new Polygon(100, 100, 200, 250, 100, 300))); >+ assertEquals("right-side polygon exists", true, >+ exists(r, new Polygon(100, 100, 200, 250, 300, 200))); >+ >+ r = triangulate(p, new Point(100, 100), new Point(200, 250)); >+ assertEquals("2 resulting polygons", 2, r.length); >+ assertEquals("left-side polygon exists", true, >+ exists(r, new Polygon(100, 100, 200, 250, 100, 300))); >+ assertEquals("right-side polygon exists", true, >+ exists(r, new Polygon(100, 100, 200, 250, 300, 200))); >+ } >+ >+ @Test >+ public void edge3_vertex2() { >+ Polygon[] r = triangulate(p, new Point(200, 150), new Point(100, >+ 300)); >+ assertEquals("2 resulting polygons", 2, r.length); >+ assertEquals("left-side polygon exists", true, >+ exists(r, new Polygon(200, 150, 100, 300, 100, 100))); >+ assertEquals("right-side polygon exists", true, >+ exists(r, new Polygon(200, 150, 100, 300, 300, 200))); >+ >+ r = triangulate(p, new Point(100, 300), new Point(200, 150)); >+ assertEquals("2 resulting polygons", 2, r.length); >+ assertEquals("left-side polygon exists", true, >+ exists(r, new Polygon(200, 150, 100, 300, 100, 100))); >+ assertEquals("right-side polygon exists", true, >+ exists(r, new Polygon(200, 150, 100, 300, 300, 200))); >+ } >+ >+ } >+ >+ /** >+ * <p> >+ * The {@link Ring#triangulate(Polygon, Line)} method is tested here. >+ * </p> >+ * >+ * <p> >+ * The test names indicate the various situations that are tested. Each >+ * situation comprises the location of the start and end point of the >+ * {@link Line} and the real and imaginary intersection {@link Point}s of >+ * the {@link Line} and the {@link Polygon}. Real {@link Point}s of >+ * intersection are the intersection {@link Point}s of the {@link Line} and >+ * the {@link Polygon}. Imaginary {@link Point}s of intersection do not lie >+ * on the {@link Line} but on its expansion to infinity in both directions. >+ * </p> >+ * >+ * <p> >+ * The first two characters indicate the location of the start and the end >+ * {@link Point} of the {@link Line} relative to the {@link Polygon}. 'o' >+ * means 'outside the polygon'. 'i' means 'inside the polygon'. 'e' means >+ * 'on an edge of the polygon'. 'v' means 'on a vertex of the polygon'. >+ * </p> >+ * >+ * <p> >+ * After that, number and type of expected intersections are stated. Real >+ * intersection {@link Point}s ('r' for 'real') are named before the >+ * imaginary ('i' for 'imaginary') intersections. The characters after 'r' >+ * or 'i' define the type of intersection. 'v' means 'the intersection point >+ * is a vertex of the polygon'. 'e' means 'the intersection point is on an >+ * edge of the polygon'. >+ * </p> >+ * >+ * <p> >+ * The postfix indicates the expected number of resulting {@link Polygon}s. >+ * 'ntd' means 'nothing to do' and therefore, it appears when a copy of the >+ * original {@link Polygon} is expected as the result. Otherwise, 's' is >+ * followed by the number of results. 'overlaps_edge' means that a >+ * {@link Polygon}s edge is overlapped by the {@link Line}. >+ * </p> >+ */ >+ public static class TriangulateTriangleWithLine { >+ >+ private Polygon[] triangulate(Polygon p, Line s) { >+ try { >+ Class<?> parameterTypes[] = new Class<?>[] { Polygon.class, >+ Line.class }; >+ Method triangulate = Ring.class.getDeclaredMethod( >+ "triangulate", parameterTypes); >+ triangulate.setAccessible(true); >+ return (Polygon[]) triangulate.invoke(null, p, s); >+ } catch (Exception x) { >+ throw new IllegalStateException(x); >+ } >+ } >+ >+ Polygon p; >+ >+ @Before >+ public void setUp() { >+ p = new Polygon(new Point(100, 100), new Point(100, 300), >+ new Point(300, 200)); >+ } >+ >+ @Test >+ public void no_polygon() { >+ try { >+ triangulate(null, new Line(1, 2, 3, 4)); >+ } catch (IllegalStateException x) { >+ Throwable cause = x; >+ while (cause.getCause() != null) >+ cause = cause.getCause(); >+ >+ assertTrue(cause.getClass().equals( >+ IllegalArgumentException.class)); >+ } >+ } >+ >+ @Test >+ public void no_line() { >+ try { >+ triangulate(p, null); >+ } catch (IllegalStateException x) { >+ Throwable cause = x; >+ while (cause.getCause() != null) >+ cause = cause.getCause(); >+ >+ assertTrue(cause.getClass().equals( >+ IllegalArgumentException.class)); >+ } >+ } >+ >+ @Test >+ public void triangulate_oo_ntd() { >+ // p1 outside, p2 outside, nothing to do >+ Polygon[] r = triangulate(p, new Line(new Point(0, 0), new Point( >+ 400, 0))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ } >+ >+ @Test >+ public void triangulate_oo1rv_ntd() { >+ // p1 outside, p2 outside, 1 real intersection (vertex), nothing to >+ // do >+ Polygon[] r = triangulate(p, new Line(new Point(0, 100), new Point( >+ 200, 100))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(0, 300), new Point(200, 300))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(300, 100), >+ new Point(300, 300))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ } >+ >+ @Test >+ public void triangulate_oo_overlaps_edge_ntd() { >+ // p1 outside, p2 outside, overlaps edge, nothing to do >+ Polygon[] r = triangulate(p, new Line(new Point(0, 50), new Point( >+ 400, 250))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(0, 350), new Point(400, 150))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, >+ new Line(new Point(100, 50), new Point(100, 350))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ } >+ >+ @Test >+ public void triangulate_vv_overlaps_edge_ntd() { >+ // p1 on vertex, p2 on vertex, overlaps edge, nothing to do >+ Polygon[] r = triangulate(p, new Line(new Point(100, 100), >+ new Point(100, 300))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(100, 100), >+ new Point(300, 200))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(300, 200), >+ new Point(100, 300))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ } >+ >+ @Test >+ public void triangulate_ee_overlaps_edge_ntd() { >+ // p1 on edge, p2 on edge, overlaps edge, nothing to do >+ Polygon[] r = triangulate(p, new Line(new Point(100, 150), >+ new Point(100, 250))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(150, 125), >+ new Point(250, 175))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(150, 275), >+ new Point(250, 225))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ } >+ >+ @Test >+ public void triangulate_ev_overlaps_edge_ntd() { >+ // p1 on edge, p2 on vertex, overlaps edge, nothing to do >+ Polygon[] r = triangulate(p, new Line(new Point(100, 200), >+ new Point(100, 100))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(150, 125), >+ new Point(100, 100))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(150, 125), >+ new Point(300, 200))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(150, 275), >+ new Point(300, 200))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(150, 275), >+ new Point(100, 300))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(100, 200), >+ new Point(100, 300))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ } >+ >+ @Test >+ public void triangulate_ve_overlaps_edge_ntd() { >+ // p1 on vertex, p2 on edge, overlaps edge, nothing to do >+ Polygon[] r = triangulate(p, new Line(new Point(100, 100), >+ new Point(100, 200))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(100, 100), >+ new Point(150, 125))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(300, 200), >+ new Point(150, 125))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(300, 200), >+ new Point(150, 275))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(100, 300), >+ new Point(150, 275))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(100, 300), >+ new Point(100, 200))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ } >+ >+ @Test >+ public void triangulate_vo1rv_ntd() { >+ // p1 on vertex, p2 outside, 1 real intersection (vertex), nothing >+ // to do >+ Polygon[] r = triangulate(p, new Line(new Point(100, 100), >+ new Point(200, 100))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(100, 300), >+ new Point(200, 300))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(300, 200), >+ new Point(300, 300))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ } >+ >+ @Test >+ public void triangulate_eo1re_ntd() { >+ // p1 on edge, p2 outside, 1 real intersection (edge), nothing to do >+ Polygon[] r = triangulate(p, new Line(new Point(100, 200), >+ new Point(100, 0))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(200, 150), new Point(200, 0))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(200, 250), >+ new Point(200, 300))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ } >+ >+ @Test >+ public void triangulate_ov1rv_ntd() { >+ // p1 outside, p2 on vertex, 1 real intersection (vertex), nothing >+ // to do >+ Polygon[] r = triangulate(p, new Line(new Point(200, 100), >+ new Point(100, 100))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(200, 300), >+ new Point(100, 300))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(300, 300), >+ new Point(300, 200))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ } >+ >+ @Test >+ public void triangulate_oe1re_ntd() { >+ // p1 outside, p2 on edge, 1 real intersection (edge), nothing to do >+ Polygon[] r = triangulate(p, new Line(new Point(100, 0), new Point( >+ 100, 200))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(200, 0), new Point(200, 150))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ >+ r = triangulate(p, new Line(new Point(200, 300), >+ new Point(200, 250))); >+ assertEquals("nothing to do", 1, r.length); >+ assertEquals("polygon remains the same", p, r[0]); >+ } >+ >+ @Test >+ public void triangulate_oo2ree_s3() { >+ // p1 outside, p2 outside, 2 real intersections (edge, edge), split >+ // into >+ // 3 pieces >+ Polygon[] r = triangulate(p, new Line(new Point(200, 100), >+ new Point(200, 300))); >+ assertEquals("split into three", 3, r.length); >+ // TODO: verify that the created three polygons are those you wanted >+ // to >+ // get back >+ >+ r = triangulate(p, >+ new Line(new Point(50, 150), new Point(250, 300))); >+ assertEquals("split into three", 3, r.length); >+ >+ r = triangulate(p, >+ new Line(new Point(50, 250), new Point(250, 100))); >+ assertEquals("split into three", 3, r.length); >+ } >+ >+ @Test >+ public void triangulate_oo2rve_s2() { >+ // p1 outside, p2 outside, 2 real intersections (vertex, edge), >+ // split into 2 pieces >+ Polygon[] r = triangulate(p, new Line(new Point(50, 200), >+ new Point(350, 200))); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(new Point(50, 50), new Point(300, 300))); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(new Point(50, 350), new Point(350, 50))); >+ assertEquals("split into two", 2, r.length); >+ } >+ >+ @Test >+ public void triangulate_io1re1ie_s3() { >+ // p1 inside, p2 outside, 1 real intersection (edge), 1 imaginary >+ // intersection (edge), split into 3 pieces >+ Polygon[] r = triangulate(p, new Line(new Point(150, 200), >+ new Point(150, 50))); >+ assertEquals("split into three", 3, r.length); >+ >+ r = triangulate(p, new Line(new Point(150, 200), >+ new Point(250, 100))); >+ assertEquals("split into three", 3, r.length); >+ >+ r = triangulate(p, new Line(new Point(150, 200), >+ new Point(150, 350))); >+ assertEquals("split into three", 3, r.length); >+ >+ r = triangulate(p, new Line(new Point(150, 200), >+ new Point(250, 300))); >+ assertEquals("split into three", 3, r.length); >+ >+ r = triangulate(p, >+ new Line(new Point(150, 200), new Point(50, 300))); >+ assertEquals("split into three", 3, r.length); >+ >+ r = triangulate(p, >+ new Line(new Point(150, 200), new Point(50, 100))); >+ assertEquals("split into three", 3, r.length); >+ } >+ >+ @Test >+ public void triangulate_io1re1iv_s2() { >+ // p1 inside, p2 outside, 1 real intersection (edge), 1 imaginary >+ // intersection (vertex), split into 2 pieces >+ Polygon[] r = triangulate(p, new Line(new Point(150, 200), >+ new Point(200, 100))); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(new Point(150, 200), >+ new Point(200, 300))); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, >+ new Line(new Point(150, 200), new Point(50, 200))); >+ assertEquals("split into two", 2, r.length); >+ } >+ >+ @Test >+ public void triangulate_io1rv1ie_s2() { >+ // p1 inside, p2 outside, 1 real intersection (vertex), 1 imaginary >+ // intersection (edge), split into 2 pieces >+ Polygon[] r = triangulate(p, new Line(new Point(150, 200), >+ new Point(50, 400))); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(new Point(150, 200), new Point(50, 0))); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(new Point(150, 200), >+ new Point(400, 200))); >+ assertEquals("split into two", 2, r.length); >+ } >+ >+ @Test >+ public void triangulate_oi1re1ie_s3() { >+ // p1 outside, p2 inside, 1 real intersection (edge), 1 imaginary >+ // intersection (edge), split into 3 pieces >+ Polygon[] r = triangulate(p, new Line(new Point(150, 50), >+ new Point(150, 200))); >+ assertEquals("split into three", 3, r.length); >+ >+ r = triangulate(p, new Line(new Point(250, 100), >+ new Point(150, 200))); >+ assertEquals("split into three", 3, r.length); >+ >+ r = triangulate(p, new Line(new Point(150, 350), >+ new Point(150, 200))); >+ assertEquals("split into three", 3, r.length); >+ >+ r = triangulate(p, new Line(new Point(250, 300), >+ new Point(150, 200))); >+ assertEquals("split into three", 3, r.length); >+ >+ r = triangulate(p, >+ new Line(new Point(50, 300), new Point(150, 200))); >+ assertEquals("split into three", 3, r.length); >+ >+ r = triangulate(p, >+ new Line(new Point(50, 100), new Point(150, 200))); >+ assertEquals("split into three", 3, r.length); >+ } >+ >+ @Test >+ public void triangulate_oi1re1iv_s2() { >+ // p1 outside, p2 inside, 1 real intersection (edge), 1 imaginary >+ // intersection (vertex), split into 2 pieces >+ Polygon[] r = triangulate(p, new Line(new Point(200, 100), >+ new Point(150, 200))); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(new Point(200, 300), >+ new Point(150, 200))); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, >+ new Line(new Point(50, 200), new Point(150, 200))); >+ assertEquals("split into two", 2, r.length); >+ } >+ >+ @Test >+ public void triangulate_oi1rv1ie_s2() { >+ // p1 outside, p2 inside, 1 real intersection (vertex), 1 imaginary >+ // intersection (edge), split into 2 pieces >+ Polygon[] r = triangulate(p, new Line(new Point(50, 400), >+ new Point(150, 200))); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(new Point(50, 0), new Point(150, 200))); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(new Point(400, 200), >+ new Point(150, 200))); >+ assertEquals("split into two", 2, r.length); >+ } >+ >+ @Test >+ public void triangulate_ee2ree_s3() { >+ // p1 on edge, p2 on edge, 2 real intersections (edge, edge), split >+ // into >+ // 3 pieces >+ Polygon[] r = triangulate(p, new Line(100, 200, 200, 150)); >+ assertEquals("split into three", 3, r.length); >+ >+ r = triangulate(p, new Line(100, 200, 200, 250)); >+ assertEquals("split into three", 3, r.length); >+ >+ r = triangulate(p, new Line(200, 250, 200, 150)); >+ assertEquals("split into three", 3, r.length); >+ >+ // swap start and end point >+ r = triangulate(p, new Line(200, 150, 100, 200)); >+ assertEquals("split into three", 3, r.length); >+ >+ r = triangulate(p, new Line(200, 250, 100, 200)); >+ assertEquals("split into three", 3, r.length); >+ >+ r = triangulate(p, new Line(200, 150, 200, 250)); >+ assertEquals("split into three", 3, r.length); >+ } >+ >+ @Test >+ public void triangulate_ve2rve_s2() { >+ // p1 on vertex, p2 on edge, 2 real intersections (vertex, edge), >+ // split >+ // into 2 pieces >+ Polygon[] r = triangulate(p, new Line(100, 100, 200, 250)); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(300, 200, 100, 200)); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(100, 300, 200, 150)); >+ assertEquals("split into two", 2, r.length); >+ } >+ >+ @Test >+ public void triangulate_ev2rve_s2() { >+ // p1 on edge, p2 on vertex, 2 real intersections (vertex, edge), >+ // split >+ // into 2 pieces >+ Polygon[] r = triangulate(p, new Line(200, 250, 100, 100)); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(100, 200, 300, 200)); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(200, 150, 100, 300)); >+ assertEquals("split into two", 2, r.length); >+ } >+ >+ @Test >+ public void triangulate_ei1re1ie_s3() { >+ // p1 on edge, p2 inside, 1 real intersection (edge), 1 imaginary >+ // intersection (edge), split into 3 pieces >+ Polygon[] r = triangulate(p, new Line(100, 200, 150, 175)); >+ assertEquals("split into three", 3, r.length); >+ >+ r = triangulate(p, new Line(100, 200, 150, 225)); >+ assertEquals("split into three", 3, r.length); >+ >+ r = triangulate(p, new Line(200, 250, 200, 200)); >+ assertEquals("split into three", 3, r.length); >+ } >+ >+ @Test >+ public void triangulate_ie1re1ie_s3() { >+ // p1 inside, p2 on edge, 1 real intersection (edge), 1 imaginary >+ // intersection (edge), split into 3 pieces >+ Polygon[] r = triangulate(p, new Line(150, 175, 100, 200)); >+ assertEquals("split into three", 3, r.length); >+ >+ r = triangulate(p, new Line(150, 225, 100, 200)); >+ assertEquals("split into three", 3, r.length); >+ >+ r = triangulate(p, new Line(200, 200, 200, 250)); >+ assertEquals("split into three", 3, r.length); >+ } >+ >+ @Test >+ public void triangulate_vi1rv1ie_s2() { >+ // p1 on vertex, p2 inside, 1 real intersection (vertex), 1 >+ // imaginary >+ // intersection (edge), split into 2 pieces >+ Polygon[] r = triangulate(p, new Line(100, 100, 200, 250)); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(300, 200, 100, 200)); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(100, 300, 200, 150)); >+ assertEquals("split into two", 2, r.length); >+ } >+ >+ @Test >+ public void triangulate_iv1rv1ie_s2() { >+ // p1 inside, p2 on vertex, 1 real intersection (vertex), 1 >+ // imaginary >+ // intersection (edge), split into 2 pieces >+ Polygon[] r = triangulate(p, new Line(200, 250, 100, 100)); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(100, 200, 300, 200)); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(200, 150, 100, 300)); >+ assertEquals("split into two", 2, r.length); >+ } >+ >+ @Test >+ public void triangulate_ei1re1iv_s2() { >+ // p1 on edge, p2 inside, 1 real intersection (edge), 1 imaginary >+ // intersection (vertex), split into 2 pieces >+ Polygon[] r = triangulate(p, new Line(100, 200, 200, 200)); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(200, 150, 150, 225)); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(200, 250, 150, 175)); >+ assertEquals("split into two", 2, r.length); >+ } >+ >+ @Test >+ public void triangulate_ie1re1iv_s2() { >+ // p1 inside, p2 on edge, 1 real intersection (edge), 1 imaginary >+ // intersection (vertex), split into 2 pieces >+ Polygon[] r = triangulate(p, new Line(200, 200, 100, 200)); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(150, 225, 200, 150)); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(150, 175, 200, 250)); >+ assertEquals("split into two", 2, r.length); >+ } >+ >+ @Test >+ public void triangulate_ii2iee_s3() { >+ // p1 inside, p2 inside, 2 imaginary intersections (edge, edge), >+ // split >+ // into 3 pieces >+ Polygon[] r = triangulate(p, new Line(125, 200, 150, 175)); >+ assertEquals("split into three", 3, r.length); >+ >+ r = triangulate(p, new Line(200, 175, 200, 225)); >+ assertEquals("split into three", 3, r.length); >+ >+ r = triangulate(p, new Line(125, 200, 150, 225)); >+ assertEquals("split into three", 3, r.length); >+ >+ r = triangulate(p, new Line(150, 175, 125, 200)); >+ assertEquals("split into three", 3, r.length); >+ >+ r = triangulate(p, new Line(200, 225, 200, 175)); >+ assertEquals("split into three", 3, r.length); >+ >+ r = triangulate(p, new Line(150, 225, 125, 200)); >+ assertEquals("split into three", 3, r.length); >+ } >+ >+ @Test >+ public void triangulate_ii2iev_s2() { >+ // p1 inside, p2 inside, 2 imaginary intersections (edge, vertex), >+ // split >+ // into 2 pieces >+ Polygon[] r = triangulate(p, new Line(150, 200, 125, 150)); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(150, 200, 200, 200)); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(150, 200, 125, 250)); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(125, 150, 150, 200)); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(200, 200, 150, 200)); >+ assertEquals("split into two", 2, r.length); >+ >+ r = triangulate(p, new Line(125, 250, 150, 200)); >+ assertEquals("split into two", 2, r.length); >+ } >+ >+ @Test >+ public void triangulate_precision_error() { >+ Polygon t = new Polygon(100.0, 100.0, 371.1146624051138, >+ 197.80263683579705, 370.0, 189.99999999999997); >+ Line l = new Line(370.0, 190.0, 400.0, 400.0); >+ >+ // throws an exception if it fails >+ triangulate(t, l); >+ } >+ >+ } >+ >+ public static class ContainmentTests { >+ >+ @Test >+ public void cover_single_polygon() { >+ Polygon p1 = new Polygon(1, 2, 1, 3, 2, 4, 3, 4, 4, 3, 4, 2, 3, 1, >+ 2, 1); >+ Ring ring = new Ring(p1); >+ >+ assertFalse(ring.contains(new Polygon(0, 0, 1, 0, 1, 1, 0, 1))); >+ assertFalse(ring.contains(new Polygon(1, 1, 3, 1, 2, 2))); >+ assertTrue(ring.contains(new Polygon(2, 2, 2, 3, 3, 3, 3, 2))); >+ assertTrue(ring.contains(p1)); >+ } >+ >+ @Test >+ public void cover_two_distinct_polygons() { >+ Polygon p1 = new Polygon(1, 2, 1, 3, 2, 4, 3, 4, 4, 3, 4, 2, 3, 1, >+ 2, 1); >+ Polygon p2 = new Polygon(4, 4, 4, 5, 5, 5, 5, 4); >+ Ring ring = new Ring(p1, p2); >+ >+ assertFalse(ring.contains(new Polygon(0, 0, 1, 0, 1, 1, 0, 1))); >+ assertFalse(ring.contains(new Polygon(1, 1, 3, 1, 2, 2))); >+ assertFalse(ring.contains(new Polygon(4.5, 4.5, 4.5, 5.5, 5.5, 5.5, >+ 5.5, 4.5))); >+ assertFalse(ring.contains(new Polygon(3, 3, 5, 3, 5, 5))); >+ assertTrue(ring.contains(new Polygon(2, 2, 2, 3, 3, 3, 3, 2))); >+ assertTrue(ring.contains(new Polygon(4.1, 4.1, 4.9, 4.1, 4.9, 4.9, >+ 4.1, 4.9))); >+ assertTrue(ring.contains(p1)); >+ assertTrue(ring.contains(p2)); >+ } >+ >+ @Test >+ public void cover_two_intersecting_polygons() { >+ Polygon p1 = new Polygon(1, 2, 1, 3, 2, 4, 3, 4, 4, 3, 4, 2, 3, 1, >+ 2, 1); >+ Polygon p2 = new Polygon(2.5, 2.5, 2.5, 5, 5, 5, 5, 2.5); >+ Ring ring = new Ring(p1, p2); >+ >+ assertFalse(ring.contains(new Polygon(0, 0, 1, 0, 1, 1, 0, 1))); >+ assertFalse(ring.contains(new Polygon(1, 1, 3, 1, 2, 2))); >+ assertFalse(ring.contains(new Polygon(4.5, 4.5, 4.5, 5.5, 5.5, 5.5, >+ 5.5, 4.5))); >+ assertTrue(ring.contains(new Polygon(2, 2, 2, 3, 3, 3, 3, 2))); >+ assertTrue(ring.contains(new Polygon(3, 3, 5, 3, 5, 5))); >+ assertTrue(ring.contains(p1)); >+ assertTrue(ring.contains(p2)); >+ assertTrue(ring.contains(new Polygon(2, 2, 2, 3, 3, 3, 3, 2))); >+ } >+ >+ } >+ >+} >diff --git a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/RoundedRectangleTests.java b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/RoundedRectangleTests.java >new file mode 100644 >index 0000000..5586fc5 >--- /dev/null >+++ b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/RoundedRectangleTests.java >@@ -0,0 +1,214 @@ >+/******************************************************************************* >+ * Copyright (c) 2012 itemis AG and others. >+ * All rights reserved. This program and the accompanying materials >+ * are made available under the terms of the Eclipse Public License v1.0 >+ * which accompanies this distribution, and is available at >+ * http://www.eclipse.org/legal/epl-v10.html >+ * >+ * Contributors: >+ * Matthias Wienand (itemis AG) - initial API and implementation >+ * >+ *******************************************************************************/ >+package org.eclipse.gef4.geometry.tests; >+ >+import static org.junit.Assert.assertEquals; >+import static org.junit.Assert.assertFalse; >+import static org.junit.Assert.assertTrue; >+ >+import org.eclipse.gef4.geometry.Angle; >+import org.eclipse.gef4.geometry.Point; >+import org.eclipse.gef4.geometry.planar.Arc; >+import org.eclipse.gef4.geometry.planar.ICurve; >+import org.eclipse.gef4.geometry.planar.IGeometry; >+import org.eclipse.gef4.geometry.planar.Line; >+import org.eclipse.gef4.geometry.planar.Rectangle; >+import org.eclipse.gef4.geometry.planar.RoundedRectangle; >+import org.eclipse.gef4.geometry.utils.PrecisionUtils; >+import org.junit.Before; >+import org.junit.Test; >+ >+public class RoundedRectangleTests { >+ >+ double x = 1, y = 2, w = 5, h = 6, aw = 1, ah = 2; >+ RoundedRectangle rr; >+ >+ @Before >+ public void setUp() { >+ rr = new RoundedRectangle(x, y, w, h, aw, ah); >+ } >+ >+ @Test >+ public void test_equals() { >+ assertEquals(rr, >+ new RoundedRectangle(new Rectangle(x, y, w, h), aw, ah)); >+ assertEquals(rr, rr.getCopy()); >+ assertFalse(rr.equals(null)); >+ assertFalse(rr.equals(new Point())); >+ >+ assertFalse(rr.equals(new RoundedRectangle(x + 10, y, w, h, aw, ah))); >+ assertFalse(rr.equals(new RoundedRectangle(x, y + 10, w, h, aw, ah))); >+ assertFalse(rr.equals(new RoundedRectangle(x, y, w + 10, h, aw, ah))); >+ assertFalse(rr.equals(new RoundedRectangle(x, y, w, h + 10, aw, ah))); >+ assertFalse(rr.equals(new RoundedRectangle(x, y, w, h, aw + 10, ah))); >+ assertFalse(rr.equals(new RoundedRectangle(x, y, w, h, aw, ah + 10))); >+ } >+ >+ @Test >+ public void test_getters() { >+ check_values_with_getters(rr, x, y, w, h, aw, ah); >+ } >+ >+ @Test >+ public void test_setters() { >+ // TODO: change values and test if the changes are applied correctly >+ RoundedRectangle rrCopy = rr.getCopy(); >+ >+ double nx = 9, ny = 8, nw = 7, nh = 6, naw = 5, nah = 4; >+ >+ rrCopy.setX(nx); >+ rrCopy.setY(ny); >+ rrCopy.setWidth(nw); >+ rrCopy.setHeight(nh); >+ rrCopy.setArcWidth(naw); >+ rrCopy.setArcHeight(nah); >+ >+ check_values_with_getters(rrCopy, nx, ny, nw, nh, naw, nah); >+ check_values_with_getters(rr, x, y, w, h, aw, ah); >+ } >+ >+ @Test >+ public void test_contains_Point() { >+ check_Point_containment(rr); >+ } >+ >+ @Test >+ public void test_toPath() { >+ check_Point_containment(rr.toPath()); >+ } >+ >+ @Test >+ public void test_getOutlineSegments() { >+ ICurve[] outlineSegments = rr.getOutlineSegments(); >+ assertEquals(8, outlineSegments.length); >+ >+ // consecutive >+ for (int i = 0; i < 7; i++) >+ assertEquals(outlineSegments[i].getP2(), >+ outlineSegments[i + 1].getP1()); >+ assertEquals(outlineSegments[7].getP2(), outlineSegments[0].getP1()); >+ >+ // position >+ assertEquals(new Point(x + w, y + ah), outlineSegments[0].getP1()); >+ assertEquals(new Point(x + w - aw, y), outlineSegments[1].getP1()); >+ assertEquals(new Point(x + aw, y), outlineSegments[2].getP1()); >+ assertEquals(new Point(x, y + ah), outlineSegments[3].getP1()); >+ assertEquals(new Point(x, y + h - ah), outlineSegments[4].getP1()); >+ assertEquals(new Point(x + aw, y + h), outlineSegments[5].getP1()); >+ assertEquals(new Point(x + w - aw, y + h), outlineSegments[6].getP1()); >+ assertEquals(new Point(x + w, y + h - ah), outlineSegments[7].getP1()); >+ } >+ >+ @Test >+ public void test_toString() { >+ assertEquals("RoundedRectangle(" + x + ", " + y + ", " + w + ", " + h >+ + ", " + aw + ", " + ah + ")", rr.toString()); >+ } >+ >+ @Test >+ public void test_getOutline() { >+ // coherence with getOutlineSegments >+ ICurve[] outlineSegments = rr.getOutlineSegments(); >+ ICurve[] outlineCurves = rr.getOutline().getCurves(); >+ assertEquals(outlineSegments.length, outlineCurves.length); >+ for (int i = 0; i < 8; i++) >+ assertEquals(outlineSegments[i], outlineCurves[i]); >+ } >+ >+ @Test >+ public void test_contains_shape() { >+ // translate it by some values and test that the translated versions are >+ // not contained >+ for (double tx : new double[] { -1, 1 }) >+ for (double ty : new double[] { -1, 1 }) >+ assertFalse(rr.contains(rr.getTranslated(tx, ty))); >+ >+ // scale it down by some values and test that the smaller versions are >+ // contained >+ for (double s = 1; s > 0; s -= 0.1) >+ assertTrue(rr.contains(rr.getScaled(s))); >+ >+ // scale it up by some values and test that the greater versions are not >+ // contained >+ for (double s = 1.1; s < 2; s += 0.1) >+ assertFalse(rr.contains(rr.getScaled(s))); >+ } >+ >+ private void check_Point_containment(IGeometry g) { >+ assertTrue(g.contains(new Point(3.5, 5))); >+ assertTrue(g.contains(new Point(1.5, 5))); >+ assertTrue(g.contains(new Point(1, 5))); >+ assertTrue(g.contains(new Point(5.5, 5))); >+ // TODO: next test is commented out because the AWT Path does not >+ // recognize points on the right and bottom sides >+ // assertTrue(g.contains(new Point(6, 5))); >+ assertTrue(g.contains(new Point(3.5, 2.5))); >+ assertTrue(g.contains(new Point(3.5, 2))); >+ assertTrue(g.contains(new Point(3.5, 7.5))); >+ // TODO: next test is commented out because the AWT Path does not >+ // recognize points on the right and bottom sides >+ // assertTrue(g.contains(new Point(3.5, 8))); >+ assertTrue(g.contains(new Point(1.5, 3.5))); >+ assertTrue(g.contains(new Point(5.5, 3.5))); >+ assertTrue(g.contains(new Point(1.5, 6.5))); >+ assertTrue(g.contains(new Point(5.5, 6.5))); >+ assertFalse(g.contains(new Point(0, 0))); >+ assertFalse(g.contains(new Point(4, 0))); >+ assertFalse(g.contains(new Point(7, 0))); >+ assertFalse(g.contains(new Point(0, 5))); >+ assertFalse(g.contains(new Point(7, 5))); >+ assertFalse(g.contains(new Point(0, 9))); >+ assertFalse(g.contains(new Point(4, 9))); >+ assertFalse(g.contains(new Point(7, 9))); >+ assertFalse(g.contains(new Point(1, 2))); >+ assertFalse(g.contains(new Point(6, 2))); >+ assertFalse(g.contains(new Point(1, 8))); >+ assertFalse(g.contains(new Point(6, 8))); >+ } >+ >+ private void check_values_with_getters(RoundedRectangle r, double px, >+ double py, double pw, double ph, double paw, double pah) { >+ assertTrue(PrecisionUtils.equal(px, r.getX())); >+ assertTrue(PrecisionUtils.equal(py, r.getY())); >+ assertTrue(PrecisionUtils.equal(pw, r.getWidth())); >+ assertTrue(PrecisionUtils.equal(ph, r.getHeight())); >+ assertTrue(PrecisionUtils.equal(paw, r.getArcWidth())); >+ assertTrue(PrecisionUtils.equal(pah, r.getArcHeight())); >+ assertEquals(new Point(px, py), r.getLocation()); >+ assertEquals(new Rectangle(px, py, pw, ph), r.getBounds()); >+ >+ // generated arcs have double width and height as specified so that the >+ // underlying ellipse fits into the respective rectangle >+ assertEquals( >+ new Arc(px + pw - 2 * paw, py, 2 * paw, 2 * pah, >+ Angle.fromDeg(0), Angle.fromDeg(90)), >+ r.getTopRightArc()); >+ assertEquals( >+ new Arc(px, py, 2 * paw, 2 * pah, Angle.fromDeg(90), >+ Angle.fromDeg(90)), r.getTopLeftArc()); >+ assertEquals( >+ new Arc(px, py + ph - 2 * pah, 2 * paw, 2 * pah, >+ Angle.fromDeg(180), Angle.fromDeg(90)), >+ r.getBottomLeftArc()); >+ assertEquals(new Arc(px + pw - 2 * paw, py + ph - 2 * pah, 2 * paw, >+ 2 * pah, Angle.fromDeg(270), Angle.fromDeg(90)), >+ r.getBottomRightArc()); >+ >+ assertEquals(new Line(px + paw, py, px + pw - paw, py), r.getTop()); >+ assertEquals(new Line(px + paw, py + ph, px + pw - paw, py + ph), >+ r.getBottom()); >+ assertEquals(new Line(px, py + pah, px, py + ph - pah), r.getLeft()); >+ assertEquals(new Line(px + pw, py + pah, px + pw, py + ph - pah), >+ r.getRight()); >+ } >+ >+} >diff --git a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/StraightTests.java b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/StraightTests.java >index c861033..4420f5a 100644 >--- a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/StraightTests.java >+++ b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/StraightTests.java >@@ -328,6 +328,10 @@ public class StraightTests { > assertTrue(s1.getPointAt(0).equals(new Point())); > assertTrue(s1.getPointAt(1).equals(new Point(1, 0))); > assertTrue(s1.getPointAt(-1).equals(new Point(-1, 0))); >+ >+ // test 0/0 straight (not a straight anymore) >+ s1 = new Straight(new Point(), new Point()); >+ assertTrue(PrecisionUtils.equal(0, s1.getParameterAt(new Point()))); > } > > @Test >diff --git a/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/Vector3DTests.java b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/Vector3DTests.java >new file mode 100644 >index 0000000..10031da >--- /dev/null >+++ b/org.eclipse.gef4.geometry.tests/src/org/eclipse/gef4/geometry/tests/Vector3DTests.java >@@ -0,0 +1,115 @@ >+/******************************************************************************* >+ * Copyright (c) 2012 itemis AG and others. >+ * All rights reserved. This program and the accompanying materials >+ * are made available under the terms of the Eclipse Public License v1.0 >+ * which accompanies this distribution, and is available at >+ * http://www.eclipse.org/legal/epl-v10.html >+ * >+ * Contributors: >+ * Matthias Wienand (itemis AG) - initial API and implementation >+ * >+ *******************************************************************************/ >+package org.eclipse.gef4.geometry.tests; >+ >+import static org.junit.Assert.assertEquals; >+import static org.junit.Assert.assertFalse; >+import static org.junit.Assert.assertNotSame; >+import static org.junit.Assert.assertTrue; >+ >+import org.eclipse.gef4.geometry.Point; >+import org.eclipse.gef4.geometry.projective.Vector3D; >+import org.eclipse.gef4.geometry.utils.PrecisionUtils; >+import org.junit.Test; >+ >+public class Vector3DTests { >+ >+ @Test >+ public void test_equals() { >+ Vector3D v0 = new Vector3D(1, 1, 1); >+ assertFalse(v0.equals(null)); >+ assertFalse(v0.equals(new Point())); >+ assertEquals(v0, v0); >+ assertEquals(v0, new Vector3D(1, 1, 1)); >+ assertEquals(v0, new Vector3D(new Point(1, 1))); >+ assertEquals(v0, new Vector3D(2, 2, 2)); >+ } >+ >+ @Test >+ public void test_getCopy() { >+ Vector3D v0 = new Vector3D(1, 2, 3); >+ Vector3D v1 = v0.getCopy(); >+ assertEquals(v0, v1); >+ assertNotSame(v0, v1); >+ v0.x++; >+ v0.y--; >+ assertFalse(v0.equals(v1)); >+ } >+ >+ @Test >+ public void test_toString() { >+ Vector3D v0 = new Vector3D(1, 2, 3); >+ assertEquals("Vector3D(1.0, 2.0, 3.0)", v0.toString()); >+ } >+ >+ @Test >+ public void test_getAdded() { >+ Vector3D v0 = new Vector3D(1, 0, 5); >+ Vector3D v1 = new Vector3D(0, 1, 5); >+ assertEquals(new Vector3D(1, 0, 5), v0.getAdded(v0)); >+ assertEquals(new Vector3D(0, 1, 5), v1.getAdded(v1)); >+ assertEquals(new Vector3D(1, 1, 10), v0.getAdded(v1)); >+ assertEquals(new Vector3D(1, 1, 10), v1.getAdded(v0)); >+ assertEquals(new Vector3D(2, 2, 20), v0.getAdded(v1)); >+ assertEquals(new Vector3D(2, 2, 20), v1.getAdded(v0)); >+ } >+ >+ @Test >+ public void test_getSubtracted() { >+ Vector3D v0 = new Vector3D(10, 5, 1); >+ Vector3D v1 = new Vector3D(5, 10, 1); >+ assertEquals(new Vector3D(0, 0, 0), v0.getSubtracted(v0)); >+ assertEquals(new Vector3D(0, 0, 0), v1.getSubtracted(v1)); >+ assertFalse(v0.getSubtracted(v1).equals(new Vector3D(0, 0, 1))); >+ assertEquals(new Vector3D(5, -5, 0), v0.getSubtracted(v1)); >+ assertEquals(new Vector3D(5, -5, 0), v1.getSubtracted(v0)); >+ assertEquals(new Vector3D(1, -1, 1 / 5), v0.getSubtracted(v1)); >+ assertEquals(new Vector3D(1, -1, 1 / 5), v1.getSubtracted(v0)); >+ } >+ >+ @Test >+ public void test_getScaled() { >+ Vector3D v0 = new Vector3D(1, 2, 3); >+ for (double s = -1.1; s <= 1.1; s += 0.2) >+ assertEquals(new Vector3D(1, 2, 3), v0.getScaled(s)); >+ } >+ >+ @Test >+ public void test_getDot() { >+ Vector3D v0 = new Vector3D(1, 0, 1); >+ Vector3D v1 = new Vector3D(0, 1, 1); >+ assertTrue(PrecisionUtils.equal(1, v0.getDot(v1))); >+ assertTrue(PrecisionUtils.equal(1, v1.getDot(v0))); >+ >+ v0 = new Vector3D(1, 2, 3); >+ v1 = new Vector3D(3, 2, 1); >+ assertTrue(PrecisionUtils.equal(10, v0.getDot(v1))); >+ assertTrue(PrecisionUtils.equal(10, v1.getDot(v0))); >+ } >+ >+ @Test >+ public void test_getCrossed() { >+ Vector3D v0 = new Vector3D(1, 0, 1); >+ Vector3D v1 = new Vector3D(0, 1, 1); >+ assertEquals(new Vector3D(-1, -1, 1), v0.getCrossed(v1)); >+ assertEquals(new Vector3D(1, 1, -1), v1.getCrossed(v0)); >+ } >+ >+ @Test >+ public void test_getRatio() { >+ Vector3D v0 = new Vector3D(0, 0, 1); >+ Vector3D v1 = new Vector3D(10, 10, 1); >+ assertEquals(new Vector3D(5, 5, 1), v0.getRatio(v1, 0.5)); >+ assertEquals(new Vector3D(5, 5, 1), v1.getRatio(v0, 0.5)); >+ } >+ >+} >diff --git a/org.eclipse.gef4.geometry/META-INF/MANIFEST.MF b/org.eclipse.gef4.geometry/META-INF/MANIFEST.MF >index 7f37060..cd045d8 100644 >--- a/org.eclipse.gef4.geometry/META-INF/MANIFEST.MF >+++ b/org.eclipse.gef4.geometry/META-INF/MANIFEST.MF >@@ -7,7 +7,9 @@ Bundle-Vendor: Eclipse.org > Bundle-RequiredExecutionEnvironment: J2SE-1.5 > Require-Bundle: org.eclipse.swt;bundle-version="[3.2.0,4.0.0)" > Export-Package: org.eclipse.gef4.geometry, >+ org.eclipse.gef4.geometry.convert, > org.eclipse.gef4.geometry.euclidean, > org.eclipse.gef4.geometry.planar, >+ org.eclipse.gef4.geometry.projective, > org.eclipse.gef4.geometry.transform, > org.eclipse.gef4.geometry.utils;x-friends:="org.eclipse.gef4.geometry.tests" >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 362daaa..9f3e3ab 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 >@@ -216,8 +216,7 @@ public class Point implements Cloneable, Serializable { > * @return The new, scaled {@link Point} > */ > public Point getScaled(double factorX, double factorY, Point center) { >- return getTranslated(center.getNegated()).scale(factorX, factorY) >- .translate(center); >+ return getCopy().scale(factorX, factorY, center); > } > > /** >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 bdacc4e..d55ee0b 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 >@@ -297,7 +297,7 @@ public class Straight implements Cloneable, Serializable { > * {@link Point} p > */ > public double getParameterAt(Point p) { >- if (direction.x != 0) { >+ if (Math.abs(direction.x) > Math.abs(direction.y)) { > return (p.x - position.x) / direction.x; > } > if (direction.y != 0) { >@@ -473,4 +473,4 @@ public class Straight implements Cloneable, Serializable { > return -line.getSignedDistanceCW(new Vector3D(r)); > } > >-} >\ No newline at end of file >+} >diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractArcBasedGeometry.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractArcBasedGeometry.java >new file mode 100644 >index 0000000..10e7e1a >--- /dev/null >+++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractArcBasedGeometry.java >@@ -0,0 +1,300 @@ >+/******************************************************************************* >+ * Copyright (c) 2012 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) - contribution for Bugzilla #355997 >+ * >+ *******************************************************************************/ >+package org.eclipse.gef4.geometry.planar; >+ >+import java.util.ArrayList; >+import java.util.List; >+ >+import org.eclipse.gef4.geometry.Angle; >+import org.eclipse.gef4.geometry.Point; >+import org.eclipse.gef4.geometry.utils.CurveUtils; >+ >+/** >+ * An {@link AbstractArcBasedGeometry} describes the arc of an {@link Ellipse}. >+ * It provides functionality to modify and query attributes of the arc and to >+ * compute a Bezier approximation of the arc (the outline). >+ * >+ * @param <T> >+ * type of the inheriting class >+ */ >+public abstract class AbstractArcBasedGeometry<T extends AbstractArcBasedGeometry<?>> >+ extends AbstractRectangleBasedGeometry<T, IGeometry> { >+ >+ private static final long serialVersionUID = 1L; >+ >+ /** >+ * The CCW (counter-clock-wise) {@link Angle} to the x-axis at which this >+ * {@link AbstractArcBasedGeometry} begins. >+ */ >+ protected Angle startAngle; >+ >+ /** >+ * The CCW (counter-clock-wise) {@link Angle} that spans this >+ * {@link AbstractArcBasedGeometry}. >+ */ >+ protected Angle angularExtent; >+ >+ /** >+ * Constructs a new {@link AbstractArcBasedGeometry} so that it is fully >+ * contained within the framing rectangle defined by (x, y, width, height), >+ * spanning the given extend (in CCW direction) from the given start angle >+ * (relative to the x-axis). >+ * >+ * @param x >+ * the x-coordinate of the framing rectangle >+ * @param y >+ * the y-coordinate of the framing rectangle >+ * @param width >+ * @param height >+ * @param startAngle >+ * @param angularExtent >+ */ >+ public AbstractArcBasedGeometry(double x, double y, double width, >+ double height, Angle startAngle, Angle angularExtent) { >+ this.x = x; >+ this.y = y; >+ this.width = width; >+ this.height = height; >+ this.startAngle = startAngle; >+ this.angularExtent = angularExtent; >+ } >+ >+ /** >+ * Returns the extension {@link Angle} of this >+ * {@link AbstractArcBasedGeometry}, i.e. the {@link Angle} defining the >+ * span of this {@link AbstractArcBasedGeometry}. >+ * >+ * @return the extension {@link Angle} of this >+ * {@link AbstractArcBasedGeometry} >+ */ >+ public Angle getAngularExtent() { >+ return angularExtent; >+ } >+ >+ /** >+ * Returns a {@link Point} representing the start {@link Point} of this >+ * {@link AbstractArcBasedGeometry}. >+ * >+ * @return the start {@link Point} of this {@link AbstractArcBasedGeometry} >+ */ >+ public Point getP1() { >+ return getPoint(Angle.fromRad(0)); >+ } >+ >+ /** >+ * Returns a {@link Point} representing the end {@link Point} of this >+ * {@link AbstractArcBasedGeometry}. >+ * >+ * @return the end {@link Point} of this {@link AbstractArcBasedGeometry} >+ */ >+ public Point getP2() { >+ return getPoint(angularExtent); >+ } >+ >+ /** >+ * Computes a {@link Point} on this {@link AbstractArcBasedGeometry}. The >+ * {@link Point}'s coordinates are calculated by moving the given >+ * {@link Angle} on this {@link AbstractArcBasedGeometry} starting at the >+ * {@link AbstractArcBasedGeometry}'s start {@link Point}. >+ * >+ * @param angularExtent >+ * @return the {@link Point} at the given {@link Angle} >+ */ >+ 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 this {@link AbstractArcBasedGeometry}'s start {@link Angle}. >+ * >+ * @return this {@link AbstractArcBasedGeometry}'s start {@link Angle} >+ */ >+ public Angle getStartAngle() { >+ return startAngle; >+ } >+ >+ /** >+ * Returns the x-coordinate of the start {@link Point} of this >+ * {@link AbstractArcBasedGeometry}. >+ * >+ * @return the x-coordinate of the start {@link Point} of this >+ * {@link AbstractArcBasedGeometry} >+ */ >+ public double getX1() { >+ return getP1().x; >+ } >+ >+ /** >+ * Returns the x-coordinate of the end {@link Point} of this >+ * {@link AbstractArcBasedGeometry}. >+ * >+ * @return the x-coordinate of the end {@link Point} of this >+ * {@link AbstractArcBasedGeometry} >+ */ >+ public double getX2() { >+ return getP2().x; >+ } >+ >+ /** >+ * Returns the y-coordinate of the start {@link Point} of this >+ * {@link AbstractArcBasedGeometry}. >+ * >+ * @return the y-coordinate of the start {@link Point} of this >+ * {@link AbstractArcBasedGeometry} >+ */ >+ public double getY1() { >+ return getP1().y; >+ } >+ >+ /** >+ * Returns the y-coordinate of the end {@link Point} of this >+ * {@link AbstractArcBasedGeometry}. >+ * >+ * @return the y-coordinate of the end {@link Point} of this >+ * {@link AbstractArcBasedGeometry} >+ */ >+ public double getY2() { >+ return getP2().y; >+ } >+ >+ /** >+ * Sets the extension {@link Angle} of this {@link AbstractArcBasedGeometry} >+ * . >+ * >+ * @param angularExtent >+ * the new extension {@link Angle} for this >+ * {@link AbstractArcBasedGeometry} >+ */ >+ public void setAngularExtent(Angle angularExtent) { >+ this.angularExtent = angularExtent; >+ } >+ >+ /** >+ * Sets the start {@link Angle} of this {@link AbstractArcBasedGeometry}. >+ * >+ * @param startAngle >+ * the new start {@link Angle} for this >+ * {@link AbstractArcBasedGeometry} >+ */ >+ public void setStartAngle(Angle startAngle) { >+ this.startAngle = startAngle; >+ } >+ >+ /** >+ * Computes a Bezier approximation for this {@link AbstractArcBasedGeometry} >+ * . It is approximated by at most four {@link CubicCurve}s which span at >+ * most 90 degrees. >+ * >+ * @return a Bezier approximation for this {@link AbstractArcBasedGeometry} >+ */ >+ protected CubicCurve[] computeBezierApproximation() { >+ double start = getStartAngle().rad(); >+ double end = getStartAngle().rad() + getAngularExtent().rad(); >+ >+ // approximation is for arcs with angle < 90 degrees, so we may have to >+ // split the arc into up to 4 cubic curves >+ List<CubicCurve> segments = new ArrayList<CubicCurve>(); >+ if (angularExtent.deg() <= 90.0) { >+ segments.add(CurveUtils.computeEllipticalArcApproximation(x, y, >+ width, height, Angle.fromRad(start), Angle.fromRad(end))); >+ } else { >+ // two or more segments, the first will be an ellipse segment >+ // approximation >+ segments.add(CurveUtils.computeEllipticalArcApproximation(x, y, >+ width, height, Angle.fromRad(start), >+ Angle.fromRad(start + Math.PI / 2))); >+ if (angularExtent.deg() <= 180.0) { >+ // two segments, calculate the second (which is below 90 >+ // degrees) >+ segments.add(CurveUtils.computeEllipticalArcApproximation(x, y, >+ width, height, Angle.fromRad(start + Math.PI / 2), >+ Angle.fromRad(end))); >+ } else { >+ // three or more segments, so calculate the second one >+ segments.add(CurveUtils.computeEllipticalArcApproximation(x, y, >+ width, height, Angle.fromRad(start + Math.PI / 2), >+ Angle.fromRad(start + Math.PI))); >+ if (angularExtent.deg() <= 270.0) { >+ // three segments, calculate the third (which is below 90 >+ // degrees) >+ segments.add(CurveUtils.computeEllipticalArcApproximation( >+ x, y, width, height, >+ Angle.fromRad(start + Math.PI), Angle.fromRad(end))); >+ } else { >+ // four segments (fourth below 90 degrees), so calculate the >+ // third and fourth >+ segments.add(CurveUtils.computeEllipticalArcApproximation( >+ x, y, width, height, >+ Angle.fromRad(start + Math.PI), >+ Angle.fromRad(start + 3 * Math.PI / 2))); >+ segments.add(CurveUtils.computeEllipticalArcApproximation( >+ x, y, width, height, >+ Angle.fromRad(start + 3 * Math.PI / 2), >+ Angle.fromRad(end))); >+ } >+ } >+ } >+ return segments.toArray(new CubicCurve[] {}); >+ } >+ >+ /** >+ * @see IGeometry#toPath() >+ */ >+ public Path toPath() { >+ return CurveUtils.toPath(computeBezierApproximation()); >+ } >+ >+ @SuppressWarnings("unchecked") >+ public T getRotatedCCW(Angle angle) { >+ return (T) ((T) getCopy()).rotateCCW(angle); >+ } >+ >+ /** >+ * Rotates this {@link AbstractArcBasedGeometry} counter-clock-wise (CCW) by >+ * the given {@link Angle} around its center {@link Point}. >+ * >+ * @param angle >+ * the rotation {@link Angle} >+ * @return <code>this</code> for convenience >+ */ >+ @SuppressWarnings("unchecked") >+ public T rotateCCW(Angle angle) { >+ startAngle.setRad(startAngle.getAdded(angle).rad()); >+ return (T) this; >+ } >+ >+ @SuppressWarnings("unchecked") >+ public T getRotatedCW(Angle angle) { >+ return (T) ((T) getCopy()).rotateCW(angle); >+ } >+ >+ /** >+ * Rotates this {@link AbstractArcBasedGeometry} clock-wise (CW) by the >+ * given {@link Angle} around its center {@link Point}. >+ * >+ * @param angle >+ * the rotation {@link Angle} >+ * @return <code>this</code> for convenience >+ */ >+ @SuppressWarnings("unchecked") >+ public T rotateCW(Angle angle) { >+ startAngle.setRad(startAngle.getAdded(angle.getOppositeFull()).rad()); >+ return (T) this; >+ } >+ >+} >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 5ecb1fc..b8f8309 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 >@@ -15,10 +15,14 @@ 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.transform.IRotatable; >+import org.eclipse.gef4.geometry.transform.IScalable; >+import org.eclipse.gef4.geometry.transform.ITranslatable; > import org.eclipse.gef4.geometry.utils.PointListUtils; > > abstract class AbstractPointListBasedGeometry<T extends AbstractPointListBasedGeometry<?>> >- extends AbstractGeometry { >+ extends AbstractGeometry implements ITranslatable<T>, IScalable<T>, >+ IRotatable<T> { > > private static final long serialVersionUID = 1L; > >@@ -54,28 +58,7 @@ abstract class AbstractPointListBasedGeometry<T extends AbstractPointListBasedGe > * {@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)); >+ return PointListUtils.computeCentroid(points); > } > > /** >@@ -116,6 +99,10 @@ abstract class AbstractPointListBasedGeometry<T extends AbstractPointListBasedGe > return getRotatedCCW(alpha, getCentroid()); > } > >+ public T getRotatedCCW(Angle angle, double cx, double cy) { >+ return getRotatedCCW(angle, new Point(cx, cy)); >+ } >+ > /** > * Returns a new {@link AbstractPointListBasedGeometry} which is rotated > * counter-clock-wise by the given {@link Angle} around the given >@@ -149,6 +136,10 @@ abstract class AbstractPointListBasedGeometry<T extends AbstractPointListBasedGe > return getRotatedCW(alpha, getCentroid()); > } > >+ public T getRotatedCW(Angle angle, double cx, double cy) { >+ return getRotatedCW(angle, new Point(cx, cy)); >+ } >+ > /** > * Returns a new {@link AbstractPointListBasedGeometry} which is rotated > * clock-wise by the given {@link Angle} around the given {@link Point}. >@@ -187,6 +178,14 @@ abstract class AbstractPointListBasedGeometry<T extends AbstractPointListBasedGe > return (T) ((T) getCopy()).scale(factorX, factorY); > } > >+ public T getScaled(double factor, double cx, double cy) { >+ return getScaled(factor, factor, new Point(cx, cy)); >+ } >+ >+ public T getScaled(double fx, double fy, double cx, double cy) { >+ return getScaled(fx, fy, new Point(cx, cy)); >+ } >+ > @SuppressWarnings("unchecked") > public T getScaled(double factorX, double factorY, Point center) { > return (T) ((T) getCopy()).scale(factorX, factorY, center); >@@ -238,6 +237,10 @@ abstract class AbstractPointListBasedGeometry<T extends AbstractPointListBasedGe > return rotateCCW(alpha, getCentroid()); > } > >+ public T rotateCCW(Angle angle, double cx, double cy) { >+ return rotateCCW(angle, new Point(cx, cy)); >+ } >+ > /** > * Rotates this {@link AbstractPointListBasedGeometry} counter-clock-wise by > * the given {@link Angle} around the given {@link Point}. >@@ -281,7 +284,11 @@ abstract class AbstractPointListBasedGeometry<T extends AbstractPointListBasedGe > * @see #rotateCW(Angle, Point) > */ > public T rotateCW(Angle alpha) { >- return rotateCW(alpha, getCentroid()); >+ return (T) rotateCW(alpha, getCentroid()); >+ } >+ >+ public T rotateCW(Angle angle, double cx, double cy) { >+ return rotateCW(angle, new Point(cx, cy)); > } > > /** >@@ -331,14 +338,22 @@ abstract class AbstractPointListBasedGeometry<T extends AbstractPointListBasedGe > return scale(factor, factor); > } > >- public T scale(double factorX, double factorY) { >- return scale(factorX, factorY, getCentroid()); >+ public T scale(double fx, double fy) { >+ return scale(fx, fy, getCentroid()); >+ } >+ >+ public T scale(double factor, double cx, double cy) { >+ return scale(factor, factor, new Point(cx, cy)); >+ } >+ >+ public T scale(double fx, double fy, double cx, double cy) { >+ return scale(fx, fy, new Point(cx, cy)); > } > > @SuppressWarnings("unchecked") >- public T scale(double factorX, double factorY, Point center) { >+ public T scale(double fx, double fy, Point center) { > for (Point p : points) { >- Point np = p.getScaled(factorX, factorY, center); >+ Point np = p.getScaled(fx, fy, center); > p.x = np.x; > p.y = np.y; > } >@@ -392,4 +407,4 @@ abstract class AbstractPointListBasedGeometry<T extends AbstractPointListBasedGe > return translate(p.x, p.y); > } > >-} >\ No newline at end of file >+} >diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractPolyShape.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractPolyShape.java >new file mode 100644 >index 0000000..d0414d8 >--- /dev/null >+++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/planar/AbstractPolyShape.java >@@ -0,0 +1,181 @@ >+/******************************************************************************* >+ * Copyright (c) 2012 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 java.util.Arrays; >+import java.util.Comparator; >+import java.util.HashMap; >+import java.util.HashSet; >+import java.util.Stack; >+ >+import org.eclipse.gef4.geometry.Point; >+import org.eclipse.gef4.geometry.utils.PrecisionUtils; >+ >+/** >+ * The {@link AbstractPolyShape} class contains an algorithm to find the outline >+ * segments of an object of an inheriting class. >+ * >+ */ >+public abstract class AbstractPolyShape extends AbstractGeometry implements >+ IPolyShape { >+ >+ private static final long serialVersionUID = 1L; >+ >+ private void assignRemainingSegment(HashMap<Line, Integer> seen, >+ Stack<Line> addends, Line toAdd, Point start, Point end) { >+ if (!start.equals(end)) { >+ Line rest = new Line(start, end); >+ if (start.equals(toAdd.getP1()) || start.equals(toAdd.getP2())) { >+ // System.out >+ // .println(" pushing rest (" + rest + ") to addends"); >+ addends.push(rest); >+ } else { >+ // System.out.println(" marking rest (" + rest + >+ // ") as seen"); >+ seen.put(rest, >+ seen.containsKey(rest) && seen.get(rest) == 2 ? 2 : 1); >+ } >+ } >+ } >+ >+ public boolean contains(Point p) { >+ for (IShape s : getShapes()) { >+ if (s.contains(p)) { >+ return true; >+ } >+ } >+ return false; >+ } >+ >+ /** >+ * Inner segments are identified by a segment count of exactly 2. >+ * >+ * @param seen >+ */ >+ private void filterOutInnerSegments(HashMap<Line, Integer> seen) { >+ for (Line seg : new HashSet<Line>(seen.keySet())) { >+ if (seen.get(seg) == 2) { >+ seen.remove(seg); >+ } >+ } >+ } >+ >+ /** >+ * Collects all edges of the internal {@link IShape}s. For a {@link Region} >+ * the internal {@link IShape}s are {@link Rectangle}s. For a {@link Ring} >+ * the internal {@link IShape}s are {@link Polygon}s (triangles). >+ * >+ * The internal edges are needed to determine inner and outer segments of >+ * the {@link IPolyShape}. Based on the outline of the {@link IPolyShape}, >+ * the outline intersections can be computed. These outline intersections >+ * are required to test if an {@link ICurve} is fully-contained by the >+ * {@link IPolyShape}. >+ * >+ * @return the edges of all internal {@link IShape}s >+ */ >+ abstract protected Line[] getAllEdges(); >+ >+ /** >+ * Computes the outline of this {@link AbstractPolyShape}. >+ * >+ * @return the outline of this {@link AbstractPolyShape} >+ * @see #getOutlineSegments() >+ */ >+ public Polyline getOutline() { >+ return new Polyline(getOutlineSegments()); >+ } >+ >+ /** >+ * Computes the outline segments of this {@link AbstractPolyShape}. >+ * >+ * The outline segments are those outline segments of the internal >+ * {@link Rectangle}s that only exist once. >+ * >+ * @return the outline segments of this {@link AbstractPolyShape} >+ */ >+ public Line[] getOutlineSegments() { >+ // System.out.println("collecting all edges..."); >+ HashMap<Line, Integer> seen = new HashMap<Line, Integer>(); >+ Stack<Line> elementsToAdd = new Stack<Line>(); >+ for (Line e : getAllEdges()) >+ elementsToAdd.push(e); >+ >+ int c = 0; >+ addingElements: while (c++ < 1000 && !elementsToAdd.empty()) { >+ Line toAdd = elementsToAdd.pop(); >+ // System.out.println("adding " + toAdd + "..."); >+ for (Line seg : new HashSet<Line>(seen.keySet())) { >+ if (seg.overlaps(toAdd)) { >+ // System.out.println(" overlaps with " + seg); >+ Point[] p = getSortedEndpoints(toAdd, seg); >+ seen.remove(seg); >+ assignRemainingSegment(seen, elementsToAdd, toAdd, p[0], >+ p[1]); >+ assignRemainingSegment(seen, elementsToAdd, toAdd, p[3], >+ p[2]); >+ markOverlap(seen, p[1], p[2]); >+ continue addingElements; >+ } >+ } >+ // System.out.println(" did not overlap"); >+ seen.put(toAdd, 1); >+ } >+ >+ // System.out.println("filter out inner segments..."); >+ filterOutInnerSegments(seen); >+ >+ return seen.keySet().toArray(new Line[] {}); >+ } >+ >+ /** >+ * Sorts the end {@link Point}s of two {@link Line}s that do overlap by >+ * their coordinate values. >+ * >+ * @param toAdd >+ * @param seg >+ * @return the sorted {@link Point}s >+ */ >+ private Point[] getSortedEndpoints(Line toAdd, Line seg) { >+ final Point[] p = new Point[] { seg.getP1(), seg.getP2(), >+ toAdd.getP1(), toAdd.getP2() }; >+ Arrays.sort(p, new Comparator<Point>() { >+ public int compare(Point p1, Point p2) { >+ if (PrecisionUtils.equal(p1.x, p2.x)) { >+ return p1.y < p2.y ? 1 : -1; >+ } >+ return p1.x < p2.x ? 1 : -1; >+ } >+ }); >+ return p; >+ } >+ >+ /** >+ * Marks a given segment from start to end {@link Point} as an overlap in >+ * the seen {@link HashMap} if the segment is not degenerated, i.e. it is >+ * not just a single {@link Point}. >+ * >+ * @param seen >+ * @param start >+ * @param end >+ */ >+ private void markOverlap(HashMap<Line, Integer> seen, Point start, Point end) { >+ if (!start.equals(end)) { >+ // Count an overlapping segment twice to assure that it is going to >+ // get deleted afterwards. >+ Line overlap = new Line(start, end); >+ seen.put(overlap, 2); >+ // System.out.println(" mark segment " + overlap + >+ // " as overlap"); >+ } >+ } >+ >+} >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 b1760d8..94d8318 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 >@@ -14,16 +14,32 @@ package org.eclipse.gef4.geometry.planar; > > import org.eclipse.gef4.geometry.Dimension; > import org.eclipse.gef4.geometry.Point; >+import org.eclipse.gef4.geometry.transform.IRotatable; >+import org.eclipse.gef4.geometry.transform.IScalable; >+import org.eclipse.gef4.geometry.transform.ITranslatable; > > /** >+ * <p> > * Abstract superclass of geometries that are defined by means of their upper > * left coordinate (x,y) and a given width and height. >+ * </p> > * >- * @author anyssen >+ * <p> >+ * The type parameter <code>T</code> specifies the type of the inheriting class. >+ * This is to be able to return the correct type, so that a type cast is >+ * unnecessary. >+ * </p> >+ * >+ * <p> >+ * The type parameter <code>S</code> specifies the result type of all rotation >+ * short-cut methods. See {@link IRotatable} for more information. >+ * </p> > * >+ * @author anyssen > */ >-abstract class AbstractRectangleBasedGeometry<T extends AbstractRectangleBasedGeometry<?>> >- extends AbstractGeometry { >+abstract class AbstractRectangleBasedGeometry<T extends AbstractRectangleBasedGeometry<?, ?>, S extends IGeometry> >+ extends AbstractGeometry implements ITranslatable<T>, IScalable<T>, >+ IRotatable<S> { > > private static final long serialVersionUID = 1L; > >@@ -53,29 +69,28 @@ abstract class AbstractRectangleBasedGeometry<T extends AbstractRectangleBasedGe > * which is the location of its bounds. > * > * @return a {@link Point} representing the location of this >- * {@link AbstractRectangleBasedGeometry} 's bounds >+ * {@link AbstractRectangleBasedGeometry}'s bounds > */ > public Point getLocation() { > return new Point(x, y); > } > >- /** >- * 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 factor, Point center) { >+ return (T) ((T) getCopy()).scale(factor, center); >+ } >+ >+ @SuppressWarnings("unchecked") >+ public T getScaled(double factor, double centerX, double centerY) { >+ return (T) ((T) getCopy()).scale(factor, centerX, centerY); >+ } >+ >+ @SuppressWarnings("unchecked") > public T getScaled(double factorX, double factorY) { > return (T) ((T) getCopy()).scale(factorX, factorY); > } >@@ -86,8 +101,9 @@ abstract class AbstractRectangleBasedGeometry<T extends AbstractRectangleBasedGe > } > > @SuppressWarnings("unchecked") >- public T getScaled(double factor, Point center) { >- return (T) ((T) getCopy()).scale(factor, center); >+ public T getScaled(double factorX, double factorY, double centerX, >+ double centerY) { >+ return (T) ((T) getCopy()).scale(factorX, factorY, centerX, centerY); > } > > /** >@@ -139,37 +155,33 @@ abstract class AbstractRectangleBasedGeometry<T extends AbstractRectangleBasedGe > return y; > } > >- /** >- * 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 <code>this</code> for convenience >- */ >- public T scale(double factor) { >- return scale(factor, factor); >+ @SuppressWarnings("unchecked") >+ public T scale(double fx, double fy, double cx, double cy) { >+ x = (x - cx) * fx + cx; >+ y = (y - cy) * fy + cy; >+ width *= fx; >+ height *= fy; >+ return (T) this; > } > >- public T scale(double factorX, double factorY) { >- return scale(factorX, factorY, getCentroid()); >+ public T scale(double fx, double fy, Point center) { >+ return scale(fx, fy, center.x, center.y); > } > >- @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 fx, double fy) { >+ return scale(fx, fy, getCentroid()); >+ } >+ >+ public T scale(double factor) { >+ return scale(factor, factor); > } > > public T scale(double factor, Point center) { >- return scale(factor, factor, center); >+ return scale(factor, center.x, center.y); >+ } >+ >+ public T scale(double factor, double cx, double cy) { >+ return scale(factor, factor, cx, cy); > } > > /** >@@ -355,4 +367,4 @@ abstract class AbstractRectangleBasedGeometry<T extends AbstractRectangleBasedGe > return 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 de25471..f45d1f0 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 >@@ -7,16 +7,15 @@ > * > * 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.List; >- > 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; > > /** > * Represents the geometric shape of an arc, which is defined by its enclosing >@@ -26,38 +25,21 @@ import org.eclipse.gef4.geometry.utils.CurveUtils; > * @author anyssen > * > */ >-public final class Arc extends AbstractRectangleBasedGeometry<Arc> implements >- ICurve { >+public final class Arc extends AbstractArcBasedGeometry<Arc> implements ICurve { > > private static final long serialVersionUID = 1L; > >- // TODO: move to utilities >- private static final Path toPath(CubicCurve... curves) { >- Path p = new Path(); >- for (int i = 0; i < curves.length; i++) { >- if (i == 0) { >- p.moveTo(curves[i].getX1(), curves[i].getY1()); >- } >- p.curveTo(curves[i].getCtrlX1(), curves[i].getCtrlY1(), >- curves[i].getCtrlX2(), curves[i].getCtrlY2(), >- curves[i].getX2(), curves[i].getY2()); >- } >- return p; >- } >- >- private Angle startAngle; >- private Angle angularExtent; >- > /** >- * Constructs a new {@link Arc} so that it is fully contained within the >- * framing rectangle defined by (x, y, width, height), spanning the given >- * extend (in CCW direction) from the given start angle (relative to the >- * x-axis). >+ * Constructs a new {@link Arc} of the given values. A {@link Rectangle} is >+ * used to define the {@link Ellipse} from which the {@link Arc} is cut out. >+ * The start {@link Angle} is the CCW (counter-clock-wise) {@link Angle} to >+ * the x-axis at which the {@link Arc} begins. The angular extent is the CCW >+ * {@link Angle} that spans the {@link Arc}, i.e. the resulting end >+ * {@link Angle} of the {@link Arc} is the sum of the start {@link Angle} >+ * and the angular extent. > * > * @param x >- * the x-coordinate of the framing rectangle > * @param y >- * the y-coordinate of the framing rectangle > * @param width > * @param height > * @param startAngle >@@ -65,56 +47,22 @@ public final class Arc extends AbstractRectangleBasedGeometry<Arc> implements > */ > public Arc(double x, double y, double width, double height, > Angle startAngle, Angle angularExtent) { >- this.x = x; >- this.y = y; >- this.width = width; >- this.height = height; >- this.startAngle = startAngle; >- this.angularExtent = angularExtent; >+ super(x, y, width, height, startAngle, angularExtent); > } > >- private CubicCurve computeApproximation(double start, double end) { >- // compute major and minor axis length >- double a = width / 2; >- double b = height / 2; >- >- // // calculate start and end points of the arc from start to end >- Point startPoint = new Point(x + a + a * Math.cos(start), y + b - b >- * Math.sin(start)); >- Point endPoint = new Point(x + a + a * Math.cos(end), y + b - b >- * Math.sin(end)); >- >- // approximation by cubic Bezier according to approximation provided in: >- // http://www.spaceroots.org/documents/ellipse/elliptical-arc.pdf >- double t = Math.tan((end - start) / 2); >- double alpha = Math.sin(end - start) >- * (Math.sqrt(4.0d + 3.0d * t * t) - 1) / 3; >- Point controlPoint1 = new Point(startPoint.x + alpha * -a >- * Math.sin(start), startPoint.y - alpha * b * Math.cos(start)); >- Point controlPoint2 = new Point( >- endPoint.x - alpha * -a * Math.sin(end), endPoint.y + alpha * b >- * Math.cos(end)); >- >- Point[] points = new Point[] { startPoint, controlPoint1, >- controlPoint2, endPoint }; >- return new CubicCurve(points); >- } >- >- /** >- * @see IGeometry#contains(Point) >- */ >- public boolean contains(Point p) { >- return false; >- } >- >- /** >- * 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 Angle getAngularExtent() { >- return angularExtent; >+ @Override >+ public boolean equals(Object obj) { >+ if (obj == null || !(obj instanceof Arc)) { >+ return false; >+ } >+ Arc o = (Arc) obj; >+ return PrecisionUtils.equal(x, o.x) >+ && PrecisionUtils.equal(y, o.y) >+ && PrecisionUtils.equal(width, o.width) >+ && PrecisionUtils.equal(height, o.height) >+ && PrecisionUtils.equal(angularExtent.rad(), >+ o.angularExtent.rad()) >+ && PrecisionUtils.equal(startAngle.rad(), o.startAngle.rad()); > } > > /** >@@ -124,64 +72,27 @@ public final class Arc extends AbstractRectangleBasedGeometry<Arc> implements > return new Arc(x, y, width, height, startAngle, angularExtent); > } > >- public Point[] getIntersections(ICurve g) { >- return CurveUtils.getIntersections(this, g); >- } >- >- /** >- * Returns a {@link Point} representing the start point of this {@link Arc}. >- * >- * @return the start {@link Point} of this {@link Arc} >- */ >- public Point getP1() { >- return getPoint(Angle.fromRad(0)); >- } >- >- public Point getP2() { >- return getPoint(angularExtent); >- } >- > /** >- * 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 angularExtent >- * @return the {@link Point} at the given {@link Angle} >+ * @see IGeometry#contains(Point) > */ >- 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())); >+ public boolean contains(Point p) { >+ for (CubicCurve c : computeBezierApproximation()) { >+ if (c.contains(p)) { >+ return true; >+ } >+ } >+ return false; > } > > /** >- * Returns this {@link Arc}'s start {@link Angle}. >+ * Computes the {@link Point}s of intersection of this {@link Arc} and the >+ * given {@link ICurve}. > * >- * @return this {@link Arc}'s start {@link Angle} >+ * @param c >+ * @return the intersection {@link Point}s > */ >- public Angle getStartAngle() { >- return startAngle; >- } >- >- public double getX1() { >- return getP1().x; >- } >- >- public double getX2() { >- return getP2().x; >- } >- >- public double getY1() { >- return getP1().y; >- } >- >- public double getY2() { >- return getP2().y; >+ public Point[] getIntersections(ICurve c) { >+ return CurveUtils.getIntersections(this, c); > } > > public boolean intersects(ICurve c) { >@@ -189,7 +100,7 @@ public final class Arc extends AbstractRectangleBasedGeometry<Arc> implements > } > > public boolean overlaps(ICurve c) { >- for (BezierCurve seg1 : toBezier()) { >+ for (BezierCurve seg1 : computeBezierApproximation()) { > if (seg1.overlaps(c)) { > return true; > } >@@ -197,68 +108,35 @@ public final class Arc extends AbstractRectangleBasedGeometry<Arc> implements > return false; > } > >- /** >- * Sets the extension {@link Angle} of this {@link Arc}. >- * >- * @param angularExtent >- * the new extension {@link Angle} for this {@link Arc} >- */ >- public void setAngularExtent(Angle angularExtent) { >- this.angularExtent = angularExtent; >+ public CubicCurve[] toBezier() { >+ return computeBezierApproximation(); > } > >- /** >- * Sets the start {@link Angle} of this {@link Arc}. >- * >- * @param startAngle >- * the new start {@link Angle} for this {@link Arc} >- */ >- public void setStartAngle(Angle startAngle) { >- this.startAngle = startAngle; >+ @Override >+ public String toString() { >+ return "Arc(" + "x = " + x + ", y = " + y + ", width = " + width >+ + ", height = " + height + ", startAngle = " + startAngle.deg() >+ + ", angularExtend = " + angularExtent.deg() + ")"; > } > >- public CubicCurve[] toBezier() { >- double start = getStartAngle().rad(); >- double end = getStartAngle().rad() + getAngularExtent().rad(); >+ public PolyBezier getRotatedCCW(Angle angle, double cx, double cy) { >+ return new PolyBezier(computeBezierApproximation()).rotateCCW(angle, >+ cx, cy); >+ } > >- // approximation is for arcs with angle < 90 degrees, so we may have to >- // split the arc into up to 4 cubic curves >- List<CubicCurve> segments = new ArrayList<CubicCurve>(); >- if (angularExtent.deg() <= 90.0) { >- segments.add(computeApproximation(start, end)); >- } else { >- // two or more segments, the first will be an ellipse segment >- // approximation >- segments.add(computeApproximation(start, start + Math.PI / 2)); >- if (angularExtent.deg() <= 180.0) { >- // two segments, calculate the second (which is below 90 >- // degrees) >- segments.add(computeApproximation(start + Math.PI / 2, end)); >- } else { >- // three or more segments, so calculate the second one >- segments.add(computeApproximation(start + Math.PI / 2, start >- + Math.PI)); >- if (angularExtent.deg() <= 270.0) { >- // three segments, calculate the third (which is below 90 >- // degrees) >- segments.add(computeApproximation(start + Math.PI, end)); >- } else { >- // four segments (fourth below 90 degrees), so calculate the >- // third and fourth >- segments.add(computeApproximation(start + Math.PI, start >- + 3 * Math.PI / 2)); >- segments.add(computeApproximation(start + 3 * Math.PI / 2, >- end)); >- } >- } >- } >- return segments.toArray(new CubicCurve[] {}); >+ public PolyBezier getRotatedCCW(Angle angle, Point center) { >+ return new PolyBezier(computeBezierApproximation()).rotateCCW(angle, >+ center); > } > >- /** >- * @see IGeometry#toPath() >- */ >- public Path toPath() { >- return toPath(toBezier()); >+ public PolyBezier getRotatedCW(Angle angle, double cx, double cy) { >+ return new PolyBezier(computeBezierApproximation()).rotateCW(angle, cx, >+ cy); >+ } >+ >+ public PolyBezier getRotatedCW(Angle angle, Point center) { >+ return new PolyBezier(computeBezierApproximation()).rotateCW(angle, >+ center); > } >+ > } >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 8b2bae2..1f1bdfd 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 >@@ -14,8 +14,10 @@ package org.eclipse.gef4.geometry.planar; > > import java.util.ArrayList; > import java.util.Arrays; >+import java.util.Collections; >+import java.util.Comparator; > import java.util.HashSet; >-import java.util.List; >+import java.util.Iterator; > import java.util.Set; > import java.util.Stack; > >@@ -24,21 +26,20 @@ 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.transform.IRotatable; >+import org.eclipse.gef4.geometry.transform.IScalable; >+import org.eclipse.gef4.geometry.transform.ITranslatable; > import org.eclipse.gef4.geometry.utils.PointListUtils; > import org.eclipse.gef4.geometry.utils.PrecisionUtils; > > /** > * Abstract base class of Bezier Curves. > * >- * TODO: make concrete -> leaf specializations in place but delegate >- * functionality to here. >- * > * @author anyssen >- * > */ >-public class BezierCurve extends AbstractGeometry implements ICurve { >- >- private static final long serialVersionUID = 1L; >+public class BezierCurve extends AbstractGeometry implements ICurve, >+ ITranslatable<BezierCurve>, IScalable<BezierCurve>, >+ IRotatable<BezierCurve> { > > private static class FatLine { > public static FatLine from(BezierCurve c, boolean ortho) { >@@ -186,6 +187,19 @@ public class BezierCurve extends AbstractGeometry implements ICurve { > } > > /** >+ * Expands this {@link Interval} to include the given other >+ * {@link Interval}. >+ * >+ * @param i >+ */ >+ public void expand(Interval i) { >+ if (i.a < a) >+ a = i.a; >+ if (i.b > b) >+ b = i.b; >+ } >+ >+ /** > * Returns a copy of this {@link Interval}. > * > * @return a copy of this {@link Interval} >@@ -305,6 +319,22 @@ public class BezierCurve extends AbstractGeometry implements ICurve { > } > > /** >+ * Expands this {@link IntervalPair} to include the given other >+ * {@link IntervalPair}. >+ * >+ * @param ip >+ */ >+ public void expand(IntervalPair ip) { >+ if (p == ip.p) { >+ pi.expand(ip.pi); >+ qi.expand(ip.qi); >+ } else { >+ pi.expand(ip.qi); >+ qi.expand(ip.pi); >+ } >+ } >+ >+ /** > * 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. >@@ -323,7 +353,7 @@ public class BezierCurve extends AbstractGeometry implements ICurve { > * @return the first sub-curve of this {@link IntervalPair} > */ > public BezierCurve getPClipped() { >- return p.getClipped(pi.a, pi.b); >+ return p.getClipped(Math.max(pi.a, 0), Math.min(pi.b, 1)); > } > > /** >@@ -350,7 +380,7 @@ public class BezierCurve extends AbstractGeometry implements ICurve { > * @return the second sub-curve of this {@link IntervalPair} > */ > public BezierCurve getQClipped() { >- return q.getClipped(qi.a, qi.b); >+ return q.getClipped(Math.max(qi.a, 0), Math.min(qi.b, 1)); > } > > /** >@@ -403,6 +433,8 @@ public class BezierCurve extends AbstractGeometry implements ICurve { > public boolean pIsBetterThanQ(Point p, Point q); > } > >+ private static final long serialVersionUID = 1L; >+ > // TODO: use constants that limit the number of iterations for the > // different iterative/recursive algorithms: > // INTERSECTIONS_MAX_ITERATIONS, APPROXIMATION_MAX_ITERATIONS >@@ -418,32 +450,49 @@ public class BezierCurve extends AbstractGeometry implements ICurve { > > private static IntervalPair[] clusterChunks(IntervalPair[] intervalPairs, > int shift) { >- List<IntervalPair> clusters = new ArrayList<IntervalPair>(); >+ ArrayList<IntervalPair> ips = new ArrayList<IntervalPair>(); > >- // TODO: do something intelligent instead! >- boolean isCompletelyClustered = true; >+ ips.addAll(Arrays.asList(intervalPairs)); > >- for (IntervalPair ip : intervalPairs) { >- boolean isExpansion = false; >- >- for (IntervalPair cluster : clusters) { >- if (isNextTo(cluster, ip, shift)) { >- expand(cluster, ip); >- isExpansion = true; >- break; >+ Collections.sort(ips, new Comparator<IntervalPair>() { >+ public int compare(IntervalPair i, IntervalPair j) { >+ return i.pi.a <= j.pi.a ? -1 : 1; >+ } >+ }); >+ >+ // for (IntervalPair ip : ips) { >+ // System.out.println("P [" + ip.pi.a + ";" + ip.pi.b + "] Q [" >+ // + ip.qi.a + ";" + ip.qi.b + "]"); >+ // } >+ >+ ArrayList<IntervalPair> clusters = new ArrayList<IntervalPair>(); >+ IntervalPair current = null; >+ boolean couldMerge; >+ >+ do { >+ clusters.clear(); >+ couldMerge = false; >+ for (IntervalPair i : ips) { >+ if (current == null) { >+ current = i.getCopy(); >+ } else if (isNextTo(current, i, shift)) { >+ couldMerge = true; >+ current.expand(i); >+ } else { >+ isNextTo(current, i, shift); >+ clusters.add(current); >+ current = i.getCopy(); > } > } >- >- if (!isExpansion) { >- clusters.add(ip); >- } else { >- isCompletelyClustered = false; >+ if (current != null) { >+ clusters.add(current); >+ current = null; > } >- } >+ ips.clear(); >+ ips.addAll(clusters); >+ } while (couldMerge); > >- IntervalPair[] clustersArray = clusters.toArray(new IntervalPair[] {}); >- return isCompletelyClustered ? clustersArray : clusterChunks( >- clustersArray, shift); >+ return clusters.toArray(new IntervalPair[] {}); > } > > private static void copyIntervalPair(IntervalPair a, IntervalPair b) { >@@ -453,19 +502,65 @@ public class BezierCurve extends AbstractGeometry implements ICurve { > 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; >+ private static IntervalPair extractOverlap( >+ 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]; > } >- if (group.pi.b < newcomer.pi.b) { >- group.pi.b = newcomer.pi.b; >+ for (int i = 0; i < endPoints.length; i++) { >+ fineChunks[intersectionCandidates.length + i] = endPoints[i]; > } >- if (group.qi.a > newcomer.qi.a) { >- group.qi.a = newcomer.qi.a; >+ >+ if (fineChunks.length == 0) { >+ return null; > } >- if (group.qi.b < newcomer.qi.b) { >- group.qi.b = newcomer.qi.b; >+ >+ // recluster chunks >+ normalizeIntervalPairs(fineChunks); >+ IntervalPair[] chunks = clusterChunks(fineChunks, CHUNK_SHIFT - 1); >+ >+ /* >+ * if they overlap, the chunk has to start/end in a start-/endpoint of >+ * the curves. >+ */ >+ >+ for (IntervalPair overlap : chunks) { >+ if (PrecisionUtils.smallerEqual(overlap.pi.a, 0) >+ && PrecisionUtils.greaterEqual(overlap.pi.b, 1) >+ || PrecisionUtils.smallerEqual(overlap.qi.a, 0) >+ && PrecisionUtils.greaterEqual(overlap.qi.b, 1) >+ || (PrecisionUtils.smallerEqual(overlap.pi.a, 0) || PrecisionUtils >+ .greaterEqual(overlap.pi.b, 1)) >+ && (PrecisionUtils.smallerEqual(overlap.qi.a, 0) || PrecisionUtils >+ .greaterEqual(overlap.qi.b, 1))) { >+ // it overlaps >+ if (PrecisionUtils.smallerEqual(overlap.pi.a, 0, >+ CHUNK_SHIFT - 1) >+ && PrecisionUtils.smallerEqual(overlap.pi.b, 0, >+ CHUNK_SHIFT - 1) >+ || PrecisionUtils.greaterEqual(overlap.pi.a, 1, >+ CHUNK_SHIFT - 1) >+ && PrecisionUtils.greaterEqual(overlap.pi.b, 1, >+ CHUNK_SHIFT - 1) >+ || PrecisionUtils.smallerEqual(overlap.qi.a, 0, >+ CHUNK_SHIFT - 1) >+ && PrecisionUtils.smallerEqual(overlap.qi.b, 0, >+ CHUNK_SHIFT - 1) >+ || PrecisionUtils.greaterEqual(overlap.qi.a, 1, >+ CHUNK_SHIFT - 1) >+ && PrecisionUtils.greaterEqual(overlap.qi.b, 1, >+ CHUNK_SHIFT - 1)) { >+ // only end-point-intersection >+ return null; >+ } >+ return refineOverlap(overlap); >+ } > } >+ >+ return null; > } > > /** >@@ -515,84 +610,13 @@ public class BezierCurve extends AbstractGeometry implements ICurve { > 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 boolean isNextTo(Interval i, Interval j, int shift) { >+ return PrecisionUtils.smallerEqual(j.a, i.b, shift) >+ && PrecisionUtils.greaterEqual(j.b, i.a, shift); > } > >- 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 boolean isNextTo(IntervalPair a, IntervalPair b, int shift) { >+ return isNextTo(a.pi, b.pi, shift) && isNextTo(a.qi, b.qi, shift); > } > > private static void normalizeIntervalPairs(IntervalPair[] intervalPairs) { >@@ -621,6 +645,79 @@ public class BezierCurve extends AbstractGeometry implements ICurve { > && PrecisionUtils.equal(p1.y, p2.y, shift); > } > >+ /** >+ * Binary search from the intervals' limits to the intervals' inner values. >+ * >+ * @param overlap >+ * {@link IntervalPair} representing the overlap of two >+ * {@link BezierCurve}s >+ * @return refined overlap >+ */ >+ private static IntervalPair refineOverlap(IntervalPair overlap) { >+ Interval piLo = refineOverlapLo(overlap.p, overlap.pi.a, >+ overlap.pi.getMid(), overlap.q); >+ Interval piHi = refineOverlapHi(overlap.p, overlap.pi.getMid(), >+ overlap.pi.b, overlap.q); >+ Interval qiLo = refineOverlapLo(overlap.q, overlap.qi.a, >+ overlap.qi.getMid(), overlap.p); >+ Interval qiHi = refineOverlapHi(overlap.q, overlap.qi.getMid(), >+ overlap.qi.b, overlap.p); >+ overlap.pi.a = piLo.b; >+ overlap.pi.b = piHi.a; >+ overlap.qi.a = qiLo.b; >+ overlap.qi.b = qiHi.a; >+ return overlap; >+ } >+ >+ private static Interval refineOverlapHi(BezierCurve p, double mid, >+ double b, BezierCurve q) { >+ Interval i = new Interval(Math.max(mid, 0), Math.min(b, 1)); >+ double prevLo; >+ Point pLo; >+ int c = 0; >+ >+ while (c++ < 30 && !i.converges()) { >+ prevLo = i.a; >+ i.a = i.getMid(); >+ pLo = p.get(i.a); >+ >+ if (!q.contains(pLo)) { >+ i.b = i.a; >+ i.a = prevLo; >+ } >+ } >+ >+ return i; >+ } >+ >+ /** >+ * @param p >+ * @param a >+ * @param mid >+ * @param q >+ * @return >+ */ >+ private static Interval refineOverlapLo(BezierCurve p, double a, >+ double mid, BezierCurve q) { >+ Interval i = new Interval(Math.max(a, 0), Math.min(mid, 1)); >+ double prevHi; >+ Point pHi; >+ int c = 0; >+ >+ while (c++ < 30 && !i.converges()) { >+ prevHi = i.b; >+ i.b = i.getMid(); >+ pHi = p.get(i.b); >+ >+ if (!q.contains(pHi)) { >+ i.a = i.b; >+ i.b = prevHi; >+ } >+ } >+ >+ return i; >+ } >+ > private Vector3D[] points; > > private static final IPointCmp xminCmp = new IPointCmp() { >@@ -816,6 +913,27 @@ public class BezierCurve extends AbstractGeometry implements ICurve { > } > > /** >+ * <p> >+ * Tests if this {@link BezierCurve} contains the given other >+ * {@link BezierCurve}. >+ * </p> >+ * >+ * <p> >+ * The other {@link BezierCurve} is regarded to be contained if its start >+ * and end {@link Point} lie on this {@link BezierCurve} and an overlapping >+ * segment of the two curves can be detected. >+ * </p> >+ * >+ * @param o >+ * @return <code>true</code> if the given {@link BezierCurve} is contained >+ * by this {@link BezierCurve}, otherwise <code>false</code> >+ */ >+ public boolean contains(BezierCurve o) { >+ return contains(o.getP1()) && contains(o.getP2()) >+ && getOverlap(o) != null; >+ } >+ >+ /** > * Returns true if the given {@link Point} lies on this {@link BezierCurve}. > * Returns false, otherwise. > * >@@ -831,6 +949,23 @@ public class BezierCurve extends AbstractGeometry implements ICurve { > return containmentParameter(this, new double[] { 0, 1 }, p); > } > >+ @Override >+ public boolean equals(Object obj) { >+ if (obj instanceof BezierCurve) { >+ BezierCurve o = (BezierCurve) obj; >+ BezierCurve t = this; >+ while (o.points.length < t.points.length) >+ o = o.getElevated(); >+ while (t.points.length < o.points.length) >+ t = t.getElevated(); >+ Point[] oPoints = o.getPoints(); >+ Point[] tPoints = t.getPoints(); >+ return PointListUtils.equals(oPoints, tPoints) >+ || PointListUtils.equalsReverse(oPoints, tPoints); >+ } >+ return false; >+ } >+ > private void findEndPointIntersections(IntervalPair ip, > Set<IntervalPair> endPointIntervalPairs, Set<Point> intersections) { > final double CHUNK_SHIFT_EPSILON = PrecisionUtils >@@ -954,8 +1089,12 @@ public class BezierCurve extends AbstractGeometry implements ICurve { > if (L1 == null || L2 == null) { > // q is degenerated > Point poi = ip.q.getHC(ip.qi.getMid()).toPoint(); >- if (ip.p.contains(poi)) { >+ double[] interval = new double[] { 0, 1 }; >+ if (poi != null && containmentParameter(ip.p, interval, poi)) { > intersections.add(poi); >+ // intervalPairs.add(new IntervalPair(ip.p, >+ // new Interval(interval), ip.q, new Interval(ip.qi >+ // .getMid(), ip.qi.getMid()))); > } > return; > } >@@ -1195,6 +1334,25 @@ public class BezierCurve extends AbstractGeometry implements ICurve { > } > > /** >+ * Computes a {@link BezierCurve} with a degree of one higher than this >+ * {@link BezierCurve}'s degree but of the same shape. >+ * >+ * @return a {@link BezierCurve} of the same shape as this >+ * {@link BezierCurve} but with one more control {@link Point} >+ */ >+ public BezierCurve getElevated() { >+ Point[] p = getPoints(); >+ Point[] q = new Point[p.length + 1]; >+ q[0] = p[0]; >+ q[p.length] = p[p.length - 1]; >+ for (int i = 1; i < p.length; i++) { >+ double c = (double) i / (double) (p.length); >+ q[i] = p[i - 1].getScaled(c).getTranslated(p[i].getScaled(1 - c)); >+ } >+ return new BezierCurve(q); >+ } >+ >+ /** > * Returns the {@link Point} at the given parameter value t. > * > * @param t >@@ -1259,29 +1417,43 @@ public class BezierCurve extends AbstractGeometry implements ICurve { > IntervalPair[] clusters = clusterChunks( > intervalPairs.toArray(new IntervalPair[] {}), 0); > >- if (isOverlap(clusters, >- endPointIntervalPairs.toArray(new IntervalPair[] {})).p != null) { >- return new HashSet<IntervalPair>(0); >- } >+ IntervalPair overlapIntervalPair = extractOverlap(clusters, >+ endPointIntervalPairs.toArray(new IntervalPair[] {})); >+ BezierCurve overlap = overlapIntervalPair == null ? null >+ : overlapIntervalPair.getPClipped(); > > Set<IntervalPair> results = new HashSet<IntervalPair>(); >- results.addAll(endPointIntervalPairs); > >- outer: for (IntervalPair cluster : clusters) { >- for (IntervalPair epip : endPointIntervalPairs) { >- if (isNextTo(cluster, epip, CHUNK_SHIFT)) { >- continue outer; >+ for (IntervalPair epip : endPointIntervalPairs) { >+ if (overlapIntervalPair == null >+ || !isNextTo(overlapIntervalPair, epip, CHUNK_SHIFT)) { >+ results.add(epip); >+ } else { >+ for (Iterator<Point> iterator = intersections.iterator(); iterator >+ .hasNext();) { >+ if (overlap.contains(iterator.next())) { >+ iterator.remove(); >+ } > } > } >+ } >+ >+ outer: for (IntervalPair cluster : clusters) { >+ if (overlapIntervalPair != null) >+ if (isNextTo(overlapIntervalPair, cluster, CHUNK_SHIFT)) >+ continue outer; >+ >+ 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); > if (cluster.converges()) { > results.add(cluster.getCopy()); >- } else { >- intersections.add(poi); > } > } > } >@@ -1299,38 +1471,7 @@ public class BezierCurve extends AbstractGeometry implements ICurve { > */ > public Point[] getIntersections(BezierCurve other) { > Set<Point> intersections = new HashSet<Point>(); >- Set<IntervalPair> intervalPairs = new HashSet<IntervalPair>(); >- Set<IntervalPair> endPointIntervalPairs = new HashSet<IntervalPair>(); >- >- 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); >- } >- } >- >+ getIntersectionIntervalPairs(other, intersections); > return intersections.toArray(new Point[] {}); > } > >@@ -1366,16 +1507,14 @@ public class BezierCurve extends AbstractGeometry implements ICurve { > > findEndPointIntersections(ip, endPointIntervalPairs, intersections); > findIntersectionChunks(ip, intervalPairs, intersections); >- IntervalPair[] clusters = clusterChunks( >- intervalPairs.toArray(new IntervalPair[] {}), 0); >+ IntervalPair[] intervalPairs2 = intervalPairs >+ .toArray(new IntervalPair[] {}); >+ normalizeIntervalPairs(intervalPairs2); >+ IntervalPair[] clusters = clusterChunks(intervalPairs2, 0); > >- IntervalPair overlap = isOverlap(clusters, >+ IntervalPair overlap = extractOverlap(clusters, > endPointIntervalPairs.toArray(new IntervalPair[] {})); >- >- if (overlap.p != null) { >- return overlap.getPClipped(); >- } >- return null; >+ return overlap == null ? null : overlap.getPClipped(); > } > > public Point getP1() { >@@ -1455,22 +1594,60 @@ public class BezierCurve extends AbstractGeometry implements ICurve { > return copy; > } > >- /** >- * 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]; >+ public BezierCurve getRotatedCCW(Angle angle) { >+ return getCopy().rotateCCW(angle); >+ } > >- for (int i = 0; i < translated.length; i++) { >- translated[i] = points[i].toPoint().getTranslated(p); >- } >+ public BezierCurve getRotatedCCW(Angle angle, double cx, double cy) { >+ return getCopy().rotateCCW(angle, cx, cy); >+ } >+ >+ public BezierCurve getRotatedCCW(Angle angle, Point center) { >+ return getCopy().rotateCCW(angle, center); >+ } >+ >+ public BezierCurve getRotatedCW(Angle angle) { >+ return getCopy().rotateCW(angle); >+ } >+ >+ public BezierCurve getRotatedCW(Angle angle, double cx, double cy) { >+ return getCopy().rotateCW(angle, cx, cy); >+ } >+ >+ public BezierCurve getRotatedCW(Angle angle, Point center) { >+ return getCopy().rotateCW(angle, center); >+ } > >- return new BezierCurve(translated); >+ public BezierCurve getScaled(double factor) { >+ return getCopy().getScaled(factor); >+ } >+ >+ public BezierCurve getScaled(double fx, double fy) { >+ return getCopy().getScaled(fx, fy); >+ } >+ >+ public BezierCurve getScaled(double factor, double cx, double cy) { >+ return getCopy().getScaled(factor, cx, cy); >+ } >+ >+ public BezierCurve getScaled(double fx, double fy, double cx, double cy) { >+ return getCopy().getScaled(fx, fy, cx, cy); >+ } >+ >+ public BezierCurve getScaled(double fx, double fy, Point center) { >+ return getCopy().getScaled(fx, fy, center); >+ } >+ >+ public BezierCurve getScaled(double factor, Point center) { >+ return getCopy().getScaled(factor, center); >+ } >+ >+ public BezierCurve getTranslated(double dx, double dy) { >+ return getCopy().translate(dx, dy); >+ } >+ >+ public BezierCurve getTranslated(Point d) { >+ return getCopy().translate(d.x, d.y); > } > > public double getX1() { >@@ -1520,20 +1697,7 @@ public class BezierCurve extends AbstractGeometry implements ICurve { > * overlap, otherwise <code>false</code> > */ > public boolean overlaps(BezierCurve other) { >- Set<Point> intersections = new HashSet<Point>(); >- Set<IntervalPair> intervalPairs = new HashSet<IntervalPair>(); >- Set<IntervalPair> endPointIntervalPairs = new HashSet<IntervalPair>(); >- >- 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; >+ return getOverlap(other) != null; > } > > public final boolean overlaps(ICurve c) { >@@ -1545,16 +1709,75 @@ public class BezierCurve extends AbstractGeometry implements ICurve { > return false; > } > >- /** >- * @param alpha >- * @param center >- */ >- public void rotateCCW(Angle alpha, Point center) { >+ public BezierCurve rotateCCW(Angle angle) { >+ Point centroid = PointListUtils.computeCentroid(getPoints()); >+ return rotateCCW(angle, centroid.x, centroid.y); >+ } >+ >+ public BezierCurve rotateCCW(Angle angle, double cx, double cy) { >+ Point[] realPoints = getPoints(); >+ PointListUtils.rotateCCW(realPoints, angle, cx, cy); >+ for (int i = 0; i < realPoints.length; i++) { >+ setPoint(i, realPoints[i]); >+ } >+ return this; >+ } >+ >+ public BezierCurve 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)); > } >+ return this; >+ } >+ >+ public BezierCurve rotateCW(Angle angle) { >+ Point centroid = PointListUtils.computeCentroid(getPoints()); >+ return rotateCW(angle, centroid.x, centroid.y); >+ } >+ >+ public BezierCurve rotateCW(Angle angle, double cx, double cy) { >+ Point[] realPoints = getPoints(); >+ PointListUtils.rotateCW(realPoints, angle, cx, cy); >+ for (int i = 0; i < realPoints.length; i++) { >+ setPoint(i, realPoints[i]); >+ } >+ return this; >+ } >+ >+ public BezierCurve rotateCW(Angle angle, Point center) { >+ return rotateCW(angle, center.x, center.y); >+ } >+ >+ public BezierCurve scale(double factor) { >+ return scale(factor, factor); >+ } >+ >+ public BezierCurve scale(double fx, double fy) { >+ Point centroid = PointListUtils.computeCentroid(getPoints()); >+ return scale(fx, fy, centroid.x, centroid.y); >+ } >+ >+ public BezierCurve scale(double factor, double cx, double cy) { >+ return scale(factor, factor, cx, cy); >+ } >+ >+ public BezierCurve scale(double fx, double fy, double cx, double cy) { >+ Point[] realPoints = getPoints(); >+ PointListUtils.scale(realPoints, fx, fy, cx, cy); >+ for (int i = 0; i < realPoints.length; i++) { >+ setPoint(i, realPoints[i]); >+ } >+ return this; >+ } >+ >+ public BezierCurve scale(double fx, double fy, Point center) { >+ return scale(fx, fy, center.x, center.y); >+ } >+ >+ public BezierCurve scale(double factor, Point center) { >+ return scale(factor, factor, center.x, center.y); > } > > /** >@@ -1641,15 +1864,17 @@ public class BezierCurve extends AbstractGeometry implements ICurve { > /** > * 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}), <code>null</code> is returned. >+ * first three {@link Point}s in this {@link BezierCurve}'s {@link Point}s >+ * array and the end {@link Point} of this {@link BezierCurve}. 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}), <code>null</code> is returned. > * >- * @return a new {@link CubicCurve} that is constructed by the first four >- * control {@link Point}s of this {@link BezierCurve} or >- * <code>null</code> if this {@link BezierCurve} does not have at >- * least four control {@link Point}s >+ * @return a new {@link CubicCurve} that is constructed by the first three >+ * {@link Point}s and the end {@link Point} of this >+ * {@link BezierCurve} or <code>null</code> if this >+ * {@link BezierCurve} does not have at least four control >+ * {@link Point}s > */ > public CubicCurve toCubic() { > if (points.length > 3) { >@@ -1819,15 +2044,17 @@ public class BezierCurve extends AbstractGeometry implements ICurve { > /** > * 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 >+ * from the first two {@link Point}s in this {@link BezierCurve}'s >+ * {@link Point}s array and the end {@link Point} of this >+ * {@link BezierCurve}. 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}), <code>null</code> is returned. > * >- * @return a new {@link QuadraticCurve} that is constructed by the first >- * three control {@link Point}s of this {@link BezierCurve} or >- * <code>null</code> if this {@link BezierCurve} does not have at >- * least three control {@link Point}s >+ * @return a new {@link QuadraticCurve} that is constructed by the first two >+ * {@link Point}s and the end {@link Point} of this >+ * {@link BezierCurve} or <code>null</code> if this >+ * {@link BezierCurve} does not have at least three control >+ * {@link Point}s > */ > public QuadraticCurve toQuadratic() { > if (points.length > 2) { >@@ -1837,261 +2064,17 @@ public class BezierCurve extends AbstractGeometry implements ICurve { > 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 <code>1</code>, in case of a cubic >- // * curve of size <code>2</code>, 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 <code>1</code>, a quadratic curve will have degree >- // <code>2</code>, >- // * and so on. <code>1</code> 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; >- // } >+ public BezierCurve translate(double dx, double dy) { >+ Point[] realPoints = getPoints(); >+ PointListUtils.translate(realPoints, dx, dy); >+ for (int i = 0; i < realPoints.length; i++) { >+ setPoint(i, realPoints[i]); >+ } >+ return this; >+ } >+ >+ public BezierCurve translate(Point d) { >+ return translate(d.x, d.y); >+ } > > } >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 0373f1e..ce94af9 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,7 +14,7 @@ package org.eclipse.gef4.geometry.planar; > import org.eclipse.gef4.geometry.Point; > import org.eclipse.gef4.geometry.transform.AffineTransform; > >-public class BezierSpline implements ICurve { >+public class BezierSpline extends AbstractGeometry implements ICurve { > > public boolean contains(Point p) { > // TODO Auto-generated method stub >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 e77beb5..9fe1bd9 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 >@@ -14,13 +14,14 @@ 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.PolynomCalculationUtils; > > /** > * Represents the geometric shape of a cubic Bézier curve. > * >- * @author anyssen >+ * TODO: Overwrite all BezierCurve methods that return a BezierCurve and add a >+ * cast to a CubicCurve. OR: Make BezierCurve parameterized > * >+ * @author anyssen > */ > public class CubicCurve extends BezierCurve { > >@@ -124,70 +125,40 @@ public class CubicCurve extends BezierCurve { > } > > /** >+ * Erroneous getBounds() implementation... use the generic one instead. >+ * >+ * TODO: find out why the mathematical solution is erroneous in some cases. >+ * > * @see IGeometry#getBounds() > */ >- @Override >- public Rectangle getBounds() { >- // extremes of the x(t) and y(t) functions: >- double[] xts; >- try { >- xts = PolynomCalculationUtils.getQuadraticRoots(-3 * getX1() + 9 >- * getCtrlX1() - 9 * getCtrlX2() + 3 * getX2(), 6 * getX1() >- - 12 * getCtrlX1() + 6 * getCtrlX2(), 3 * getCtrlX1() - 3 >- * getX1()); >- } catch (ArithmeticException x) { >- return new Rectangle(getP1(), getP2()); >- } >- >- double xmin = getX1(), xmax = getX1(); >- if (getX2() < xmin) { >- xmin = getX2(); >- } else { >- xmax = getX2(); >- } >- >- for (double t : xts) { >- if (t >= 0 && t <= 1) { >- double x = get(t).x; >- if (x < xmin) { >- xmin = x; >- } else if (x > xmax) { >- xmax = x; >- } >- } >- } >- >- double[] yts; >- try { >- yts = PolynomCalculationUtils.getQuadraticRoots(-3 * getY1() + 9 >- * getCtrlY1() - 9 * getCtrlY2() + 3 * getY2(), 6 * getY1() >- - 12 * getCtrlY1() + 6 * getCtrlY2(), 3 * getCtrlY1() - 3 >- * getY1()); >- } catch (ArithmeticException x) { >- return new Rectangle(new Point(xmin, getP1().y), new Point(xmax, >- getP2().y)); >- } >- >- double ymin = getY1(), ymax = getY1(); >- if (getY2() < ymin) { >- ymin = getY2(); >- } else { >- ymax = getY2(); >- } >- >- for (double t : yts) { >- if (t >= 0 && t <= 1) { >- double y = get(t).y; >- if (y < ymin) { >- ymin = y; >- } else if (y > ymax) { >- ymax = y; >- } >- } >- } >- >- return new Rectangle(new Point(xmin, ymin), new Point(xmax, ymax)); >- } >+ /* >+ * public Rectangle getBounds() { // extremes of the x(t) and y(t) >+ * functions: double[] xts; try { xts = >+ * PolynomCalculationUtils.getQuadraticRoots(-3 * getX1() + 9 getCtrlX1() - >+ * 9 * getCtrlX2() + 3 * getX2(), 6 * getX1() - 12 * getCtrlX1() + 6 * >+ * getCtrlX2(), 3 * getCtrlX1() - 3 getX1()); } catch (ArithmeticException >+ * x) { return new Rectangle(getP1(), getP2()); } >+ * >+ * double xmin = getX1(), xmax = getX1(); if (getX2() < xmin) { xmin = >+ * getX2(); } else { xmax = getX2(); } >+ * >+ * for (double t : xts) { if (t >= 0 && t <= 1) { double x = get(t).x; if (x >+ * < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } } } >+ * >+ * double[] yts; try { yts = PolynomCalculationUtils.getQuadraticRoots(-3 * >+ * getY1() + 9 getCtrlY1() - 9 * getCtrlY2() + 3 * getY2(), 6 * getY1() - 12 >+ * * getCtrlY1() + 6 * getCtrlY2(), 3 * getCtrlY1() - 3 getY1()); } catch >+ * (ArithmeticException x) { return new Rectangle(new Point(xmin, >+ * getP1().y), new Point(xmax, getP2().y)); } >+ * >+ * double ymin = getY1(), ymax = getY1(); if (getY2() < ymin) { ymin = >+ * getY2(); } else { ymax = getY2(); } >+ * >+ * for (double t : yts) { if (t >= 0 && t <= 1) { double y = get(t).y; if (y >+ * < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } } } >+ * >+ * return new Rectangle(new Point(xmin, ymin), new Point(xmax, ymax)); } >+ */ > > private Polygon getControlPolygon() { > return new Polygon(getP1(), getCtrl1(), getCtrl2(), getP2()); >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 e9d41ce..868f8be 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 >@@ -18,6 +18,7 @@ import java.util.HashSet; > import java.util.Iterator; > import java.util.List; > >+import org.eclipse.gef4.geometry.Angle; > import org.eclipse.gef4.geometry.Point; > import org.eclipse.gef4.geometry.transform.AffineTransform; > import org.eclipse.gef4.geometry.utils.CurveUtils; >@@ -34,8 +35,8 @@ import org.eclipse.gef4.geometry.utils.PrecisionUtils; > * @author anyssen > * @author Matthias Wienand > */ >-public class Ellipse extends AbstractRectangleBasedGeometry<Ellipse> implements >- IShape { >+public class Ellipse extends >+ AbstractRectangleBasedGeometry<Ellipse, PolyBezier> implements IShape { > > private static final long serialVersionUID = 1L; > >@@ -344,40 +345,15 @@ public class Ellipse extends AbstractRectangleBasedGeometry<Ellipse> implements > * @return border-segments > */ > public CubicCurve[] getOutlineSegments() { >- CubicCurve[] segs = new CubicCurve[4]; >- // see http://whizkidtech.redprince.net/bezier/circle/kappa/ for details >- // on the approximation used here >- final double kappa = 4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d; >- double a = width / 2; >- double b = height / 2; >- >- double ox = x + a; >- double oy = y; >- >- segs[0] = new CubicCurve(ox, oy, x + a + kappa * a, y, x + width, y + b >- - kappa * b, x + width, y + b); >- >- ox = x + width; >- oy = y + b; >- >- segs[1] = new CubicCurve(ox, oy, x + width, y + b + kappa * b, x + a >- + kappa * a, y + height, x + a, y + height); >- >- ox = x + a; >- oy = y + height; >- >- segs[2] = new CubicCurve(ox, oy, x + width / 2 - kappa * width / 2, y >- + height, x, y + height / 2 + kappa * height / 2, x, y + height >- / 2); >- >- ox = x; >- oy = y + height / 2; >- >- segs[3] = new CubicCurve(ox, oy, x, >- y + height / 2 - kappa * height / 2, x + width / 2 - kappa >- * width / 2, y, x + width / 2, y); >- >- return segs; >+ return new CubicCurve[] { >+ CurveUtils.computeEllipticalArcApproximation(x, y, width, >+ height, Angle.fromDeg(0), Angle.fromDeg(90)), >+ CurveUtils.computeEllipticalArcApproximation(x, y, width, >+ height, Angle.fromDeg(90), Angle.fromDeg(180)), >+ CurveUtils.computeEllipticalArcApproximation(x, y, width, >+ height, Angle.fromDeg(180), Angle.fromDeg(270)), >+ CurveUtils.computeEllipticalArcApproximation(x, y, width, >+ height, Angle.fromDeg(270), Angle.fromDeg(360)), }; > } > > /** >@@ -398,26 +374,44 @@ public class Ellipse extends AbstractRectangleBasedGeometry<Ellipse> implements > public Path toPath() { > // see http://whizkidtech.redprince.net/bezier/circle/kappa/ for details > // on the approximation used here >- final double kappa = 4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d; >- final Path p = new Path(); >- double a = width / 2; >- double b = height / 2; >- p.moveTo(x + a, y); >- p.curveTo(x + a + kappa * a, y, x + width, y + b - kappa * b, >- x + width, y + b); >- p.curveTo(x + width, y + b + kappa * b, x + a + kappa * a, y + height, >- x + a, y + height); >- p.curveTo(x + width / 2 - kappa * width / 2, y + height, x, y + height >- / 2 + kappa * height / 2, x, y + height / 2); >- p.curveTo(x, y + height / 2 - kappa * height / 2, x + width / 2 - kappa >- * width / 2, y, x + width / 2, y); >- return p; >+ return CurveUtils.toPath(CurveUtils.computeEllipticalArcApproximation( >+ x, y, width, height, Angle.fromDeg(0), Angle.fromDeg(90)), >+ CurveUtils.computeEllipticalArcApproximation(x, y, width, >+ height, Angle.fromDeg(90), Angle.fromDeg(180)), >+ CurveUtils.computeEllipticalArcApproximation(x, y, width, >+ height, Angle.fromDeg(180), Angle.fromDeg(270)), >+ CurveUtils.computeEllipticalArcApproximation(x, y, width, >+ height, Angle.fromDeg(270), Angle.fromDeg(360))); > } > > @Override > public String toString() { >- return "Ellipse: (" + x + ", " + y + ", " + //$NON-NLS-3$//$NON-NLS-2$//$NON-NLS-1$ >+ return "Ellipse (" + x + ", " + y + ", " + //$NON-NLS-3$//$NON-NLS-2$//$NON-NLS-1$ > width + ", " + height + ")";//$NON-NLS-2$//$NON-NLS-1$ > } > >+ public PolyBezier getRotatedCCW(Angle angle) { >+ return new PolyBezier(getOutlineSegments()).rotateCCW(angle); >+ } >+ >+ public PolyBezier getRotatedCCW(Angle angle, double cx, double cy) { >+ return new PolyBezier(getOutlineSegments()).rotateCCW(angle, cx, cy); >+ } >+ >+ public PolyBezier getRotatedCCW(Angle angle, Point center) { >+ return new PolyBezier(getOutlineSegments()).rotateCCW(angle, center); >+ } >+ >+ public PolyBezier getRotatedCW(Angle angle) { >+ return new PolyBezier(getOutlineSegments()).rotateCW(angle); >+ } >+ >+ public PolyBezier getRotatedCW(Angle angle, double cx, double cy) { >+ return new PolyBezier(getOutlineSegments()).rotateCW(angle, cx, cy); >+ } >+ >+ public PolyBezier getRotatedCW(Angle angle, Point center) { >+ return new PolyBezier(getOutlineSegments()).rotateCW(angle, center); >+ } >+ > } >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 3898f50..bd3e9af 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 >@@ -25,6 +25,30 @@ public interface IPolyShape extends IGeometry { > */ > IShape[] getShapes(); > >- // contains() >+ /** >+ * <p> >+ * Computes the outline segments of this {@link IPolyShape}. >+ * </p> >+ * >+ * <p> >+ * Each {@link ICurve} segment of the outline of the internal {@link IShape} >+ * s can be either an inner segment or an outer segment. This method >+ * extracts only the outer segments. The segments bordering voids are >+ * considered to be outer segments, too. >+ * </p> >+ * >+ * @return the outline segments of this {@link IPolyShape} >+ */ >+ public ICurve[] getOutlineSegments(); >+ >+ /** >+ * Checks if the given {@link IGeometry} is fully contained by this >+ * {@link IPolyShape}. >+ * >+ * @param g >+ * @return <code>true</code> if the {@link IGeometry} is contained by this >+ * {@link IPolyShape}, otherwise <code>false</code> >+ */ >+ public boolean contains(final IGeometry g); > > } >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 7ab0e71..49948ff 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,6 +12,9 @@ > *******************************************************************************/ > package org.eclipse.gef4.geometry.planar; > >+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.euclidean.Vector; >@@ -198,6 +201,43 @@ public class Line extends BezierCurve { > : null; > } > >+ /** >+ * Provides an optimized version of the >+ * {@link BezierCurve#getIntersectionIntervalPairs(BezierCurve, Set)} >+ * method. >+ * >+ * @param other >+ * @param intersections >+ * @return see >+ * {@link BezierCurve#getIntersectionIntervalPairs(BezierCurve, Set)} >+ */ >+ public Set<IntervalPair> getIntersectionIntervalPairs(Line other, >+ Set<Point> intersections) { >+ Straight s1 = new Straight(this); >+ Straight s2 = new Straight(other); >+ Vector vi = s1.getIntersection(s2); >+ if (vi != null) { >+ Point pi = vi.toPoint(); >+ if (contains(pi)) { >+ double param1 = s1.getParameterAt(pi); >+ double param2 = s2.getParameterAt(pi); >+ HashSet<IntervalPair> intervalPairs = new HashSet<IntervalPair>(); >+ intervalPairs.add(new IntervalPair(this, new Interval(param1, >+ param1), other, new Interval(param2, param2))); >+ return intervalPairs; >+ } >+ } >+ return new HashSet<IntervalPair>(); >+ } >+ >+ public Set<IntervalPair> getIntersectionIntervalPairs(BezierCurve other, >+ Set<Point> intersections) { >+ if (other instanceof Line) { >+ return getIntersectionIntervalPairs((Line) other, intersections); >+ } >+ return super.getIntersectionIntervalPairs(other, intersections); >+ } >+ > @Override > public Point[] getIntersections(BezierCurve curve) { > if (curve instanceof Line) { >@@ -233,6 +273,13 @@ public class Line extends BezierCurve { > return new Line(transformed[0], transformed[1]); > } > >+ /** >+ * Provides an optimized version of the >+ * {@link BezierCurve#intersects(ICurve)} method. >+ * >+ * @param l >+ * @return see {@link BezierCurve#intersects(ICurve)} >+ */ > public boolean intersects(Line l) { > return getIntersection(l) != null; > } >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 effb8f7..90c69d2 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 >@@ -7,28 +7,110 @@ > * > * 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.CurveUtils; >+import org.eclipse.gef4.geometry.utils.PrecisionUtils; > >-public class Pie extends AbstractGeometry implements IGeometry { >+/** >+ * The {@link Pie} is a closed {@link AbstractArcBasedGeometry}. It is the >+ * complement of the {@link Arc}, which is an open >+ * {@link AbstractArcBasedGeometry}. >+ * >+ * The {@link Pie} covers an area, therefore it implements the {@link IShape} >+ * interface. >+ */ >+public class Pie extends AbstractArcBasedGeometry<Pie> implements IShape { >+ >+ private static final long serialVersionUID = 1L; >+ >+ /** >+ * Constructs a new {@link Pie} from the given values. >+ * >+ * @see AbstractArcBasedGeometry#AbstractArcBasedGeometry(double, double, >+ * double, double, Angle, Angle) >+ * >+ * @param x >+ * @param y >+ * @param width >+ * @param height >+ * @param startAngle >+ * @param angularExtent >+ */ >+ public Pie(double x, double y, double width, double height, >+ Angle startAngle, Angle angularExtent) { >+ super(x, y, width, height, startAngle, angularExtent); >+ } >+ >+ /** >+ * @see org.eclipse.gef4.geometry.planar.IGeometry#getCopy() >+ */ >+ public Pie getCopy() { >+ return new Pie(x, y, width, height, startAngle, angularExtent); >+ } >+ >+ public PolyBezier getOutline() { >+ return new PolyBezier(computeBezierApproximation()); >+ } >+ >+ public CubicCurve[] getOutlineSegments() { >+ return computeBezierApproximation(); >+ } > > public boolean contains(Point p) { >- throw new UnsupportedOperationException("Not yet implemented."); >+ // check if the point is in the arc's angle >+ Angle pAngle = new Vector(1, 0) >+ .getAngleCCW(new Vector(getCentroid(), p)); >+ if (!(PrecisionUtils.greater(pAngle.rad(), startAngle.rad()) && PrecisionUtils >+ .smaller(pAngle.rad(), startAngle.getAdded(angularExtent).rad()))) { >+ return false; >+ } >+ >+ // angle is correct, check if the point is inside the bounding ellipse >+ return new Ellipse(x, y, width, height).contains(p); > } > >- public Rectangle getBounds() { >- throw new UnsupportedOperationException("Not yet implemented."); >+ public boolean contains(IGeometry g) { >+ return CurveUtils.contains(this, g); > } > > public Path toPath() { >- throw new UnsupportedOperationException("Not yet implemented."); >+ CubicCurve[] arc = computeBezierApproximation(); >+ Line endToMid = new Line(arc[arc.length - 1].getP2(), getCentroid()); >+ Line midToStart = new Line(getCentroid(), arc[0].getP1()); >+ ICurve[] curves = new ICurve[arc.length + 2]; >+ for (int i = 0; i < arc.length; i++) { >+ curves[i] = arc[i]; >+ } >+ curves[arc.length] = endToMid; >+ curves[arc.length + 1] = midToStart; >+ return CurveUtils.toPath(curves); >+ } >+ >+ public Path getRotatedCCW(Angle angle, double cx, double cy) { >+ return new PolyBezier(computeBezierApproximation()).rotateCCW(angle, >+ cx, cy).toPath(); >+ } >+ >+ public Path getRotatedCCW(Angle angle, Point center) { >+ return new PolyBezier(computeBezierApproximation()).rotateCCW(angle, >+ center).toPath(); >+ } >+ >+ public Path getRotatedCW(Angle angle, double cx, double cy) { >+ return new PolyBezier(computeBezierApproximation()).rotateCW(angle, cx, >+ cy).toPath(); > } > >- public IGeometry getCopy() { >- throw new UnsupportedOperationException("Not yet implemented."); >+ public Path getRotatedCW(Angle angle, Point center) { >+ return new PolyBezier(computeBezierApproximation()).rotateCW(angle, >+ center).toPath(); > } > > } >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 >index 5124233..4d487ad 100644 >--- 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 >@@ -11,16 +11,37 @@ > *******************************************************************************/ > package org.eclipse.gef4.geometry.planar; > >+import java.util.ArrayList; >+import java.util.Arrays; >+ >+import org.eclipse.gef4.geometry.Angle; > import org.eclipse.gef4.geometry.Point; >+import org.eclipse.gef4.geometry.transform.IRotatable; >+import org.eclipse.gef4.geometry.transform.IScalable; >+import org.eclipse.gef4.geometry.transform.ITranslatable; > import org.eclipse.gef4.geometry.utils.CurveUtils; >+import org.eclipse.gef4.geometry.utils.PointListUtils; > > /** > * A {@link PolyBezier} is an {@link IPolyCurve} which consists of one or more > * connected {@link BezierCurve}s. > */ >-public class PolyBezier extends AbstractGeometry implements IPolyCurve { >+public class PolyBezier extends AbstractGeometry implements IPolyCurve, >+ ITranslatable<PolyBezier>, IScalable<PolyBezier>, >+ IRotatable<PolyBezier> { > > private static final long serialVersionUID = 1L; >+ >+ 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; >+ } >+ > private BezierCurve[] beziers; > > /** >@@ -54,45 +75,96 @@ public class PolyBezier extends AbstractGeometry implements IPolyCurve { > return bounds; > } > >- public Path toPath() { >- // TODO: need a Path.append(Path) >- throw new UnsupportedOperationException("Not yet implemented."); >+ public PolyBezier getCopy() { >+ return new PolyBezier(beziers); > } > >- public IGeometry getCopy() { >- return new PolyBezier(beziers); >+ public BezierCurve[] getCurves() { >+ return copy(beziers); > } > >- public double getY2() { >- return getP2().y; >+ public Point[] getIntersections(ICurve g) { >+ return CurveUtils.getIntersections(g, this); > } > >- public double getY1() { >- return getP1().y; >+ public Point getP1() { >+ return beziers[0].getP1(); > } > >- public double getX2() { >- return getP2().x; >+ public Point getP2() { >+ return beziers[beziers.length - 1].getP2(); > } > >- public double getX1() { >- return getP1().x; >+ public PolyBezier getRotatedCCW(Angle angle) { >+ return getCopy().rotateCCW(angle); > } > >- public Point getP2() { >- return beziers[beziers.length - 1].getP2(); >+ public PolyBezier getRotatedCCW(Angle angle, double cx, double cy) { >+ return getCopy().getRotatedCCW(angle, cx, cy); > } > >- public Point getP1() { >- return beziers[0].getP1(); >+ public PolyBezier getRotatedCCW(Angle angle, Point center) { >+ return getCopy().getRotatedCCW(angle, center); > } > >- public BezierCurve[] toBezier() { >- return copy(beziers); >+ public PolyBezier getRotatedCW(Angle angle) { >+ return getCopy().getRotatedCW(angle); > } > >- public Point[] getIntersections(ICurve g) { >- return CurveUtils.getIntersections(g, this); >+ public PolyBezier getRotatedCW(Angle angle, double cx, double cy) { >+ return getCopy().getRotatedCW(angle, cx, cy); >+ } >+ >+ public PolyBezier getRotatedCW(Angle angle, Point center) { >+ return getCopy().getRotatedCW(angle, center); >+ } >+ >+ public PolyBezier getScaled(double factor) { >+ return getCopy().scale(factor); >+ } >+ >+ public PolyBezier getScaled(double fx, double fy) { >+ return getCopy().scale(fx, fy); >+ } >+ >+ public PolyBezier getScaled(double factor, double cx, double cy) { >+ return getCopy().scale(factor, cx, cy); >+ } >+ >+ public PolyBezier getScaled(double fx, double fy, double cx, double cy) { >+ return getCopy().scale(fx, fy, cx, cy); >+ } >+ >+ public PolyBezier getScaled(double fx, double fy, Point center) { >+ return getCopy().scale(fx, fy, center); >+ } >+ >+ public PolyBezier getScaled(double factor, Point center) { >+ return getCopy().scale(factor, center); >+ } >+ >+ public PolyBezier getTranslated(double dx, double dy) { >+ return getCopy().translate(dx, dy); >+ } >+ >+ public PolyBezier getTranslated(Point d) { >+ return getCopy().translate(d.x, d.y); >+ } >+ >+ public double getX1() { >+ return getP1().x; >+ } >+ >+ public double getX2() { >+ return getP2().x; >+ } >+ >+ public double getY1() { >+ return getP1().y; >+ } >+ >+ public double getY2() { >+ return getP2().y; > } > > public boolean intersects(ICurve c) { >@@ -103,18 +175,98 @@ public class PolyBezier extends AbstractGeometry implements IPolyCurve { > return CurveUtils.overlaps(c, this); > } > >- public BezierCurve[] getCurves() { >+ public PolyBezier rotateCCW(Angle angle) { >+ ArrayList<Point> points = new ArrayList<Point>(); >+ for (BezierCurve c : beziers) { >+ points.addAll(Arrays.asList(c.getPoints())); >+ } >+ Point centroid = PointListUtils.computeCentroid(points >+ .toArray(new Point[] {})); >+ return rotateCCW(angle, centroid.x, centroid.y); >+ } >+ >+ public PolyBezier rotateCCW(Angle angle, double cx, double cy) { >+ for (BezierCurve c : beziers) { >+ c.rotateCCW(angle, cx, cy); >+ } >+ return this; >+ } >+ >+ public PolyBezier rotateCCW(Angle angle, Point center) { >+ return rotateCCW(angle, center.x, center.y); >+ } >+ >+ public PolyBezier rotateCW(Angle angle) { >+ ArrayList<Point> points = new ArrayList<Point>(); >+ for (BezierCurve c : beziers) { >+ points.addAll(Arrays.asList(c.getPoints())); >+ } >+ Point centroid = PointListUtils.computeCentroid(points >+ .toArray(new Point[] {})); >+ return rotateCW(angle, centroid.x, centroid.y); >+ } >+ >+ public PolyBezier rotateCW(Angle angle, double cx, double cy) { >+ for (BezierCurve c : beziers) { >+ c.rotateCW(angle, cx, cy); >+ } >+ return this; >+ } >+ >+ public PolyBezier rotateCW(Angle angle, Point center) { >+ return rotateCW(angle, center.x, center.y); >+ } >+ >+ public PolyBezier scale(double factor) { >+ return scale(factor, factor); >+ } >+ >+ public PolyBezier scale(double fx, double fy) { >+ ArrayList<Point> points = new ArrayList<Point>(); >+ for (BezierCurve c : beziers) { >+ points.addAll(Arrays.asList(c.getPoints())); >+ } >+ Point centroid = PointListUtils.computeCentroid(points >+ .toArray(new Point[] {})); >+ return scale(fx, fy, centroid.x, centroid.y); >+ } >+ >+ public PolyBezier scale(double factor, double cx, double cy) { >+ return scale(factor, factor, cx, cy); >+ } >+ >+ public PolyBezier scale(double fx, double fy, double cx, double cy) { >+ for (BezierCurve c : beziers) { >+ c.scale(fx, fy, cx, cy); >+ } >+ return this; >+ } >+ >+ public PolyBezier scale(double fx, double fy, Point center) { >+ return scale(fx, fx, center.x, center.y); >+ } >+ >+ public PolyBezier scale(double factor, Point center) { >+ return scale(factor, factor, center.x, center.y); >+ } >+ >+ public BezierCurve[] toBezier() { > return copy(beziers); > } > >- private static BezierCurve[] copy(BezierCurve... beziers) { >- BezierCurve[] copy = new BezierCurve[beziers.length]; >+ public Path toPath() { >+ return CurveUtils.toPath(beziers); >+ } > >- for (int i = 0; i < beziers.length; i++) { >- copy[i] = beziers[i].getCopy(); >+ public PolyBezier translate(double dx, double dy) { >+ for (BezierCurve c : beziers) { >+ c.translate(dx, dy); > } >+ return this; >+ } > >- return copy; >+ public PolyBezier translate(Point d) { >+ return translate(d.x, d.y); > } > > } >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 d366acd..5e9e3cf 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,6 +13,10 @@ > package org.eclipse.gef4.geometry.planar; > > 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.transform.AffineTransform; >@@ -35,6 +39,24 @@ public class Polygon extends AbstractPointListBasedGeometry<Polygon> implements > IShape { > > /** >+ * The {@link NonSimplePolygonException} is thrown if a non-simple >+ * {@link Polygon}, i.e. a {@link Polygon} with self-intersections, is asked >+ * for its triangulation ({@link Polygon#getTriangulation()}). >+ */ >+ @SuppressWarnings("serial") >+ public class NonSimplePolygonException extends RuntimeException { >+ @SuppressWarnings("javadoc") >+ public NonSimplePolygonException() { >+ super(); >+ } >+ >+ @SuppressWarnings("javadoc") >+ public NonSimplePolygonException(String s) { >+ super(s); >+ } >+ } >+ >+ /** > * Pair of {@link Line} segment and integer counter to count segments of > * {@link Polygon}s. > */ >@@ -73,6 +95,87 @@ public class Polygon extends AbstractPointListBasedGeometry<Polygon> implements > > private static final long serialVersionUID = 1L; > >+ private static Polygon clipEar(Polygon p, int[] ear, ArrayList<Polygon> ears) { >+ Point[] points = p.getPoints(); >+ ears.add(new Polygon(points[ear[0]], points[ear[1]], points[ear[2]])); >+ return new Polygon(getPointsWithout(points, ear[1])); >+ } >+ >+ /** >+ * Searches the given list of {@link Point}s for a vertex that starts an >+ * ear. An ear is a list of 3 vertices which build up a triangle that lies >+ * inside the {@link Polygon} respective to the list of {@link Point}s and >+ * can be clipped out of it so that the remaining {@link Polygon} remains >+ * simple. >+ * >+ * @param points >+ * @return >+ */ >+ private static int[] findEarVertex(Polygon p) { >+ Point[] points = p.getPoints(); >+ >+ for (int start = 0; start < points.length; start++) { >+ int mid = start == points.length - 1 ? 0 : start + 1; >+ int end = start == points.length - 2 ? 0 >+ : start == points.length - 1 ? 1 : start + 2; >+ >+ if (p.contains(new Line(points[start], points[end]))) { >+ return new int[] { start, mid, end }; >+ } >+ } >+ >+ // this should never happen (for simple polygons) >+ return null; >+ } >+ >+ private static Point[] getPointsWithout(Point[] points, >+ int... indicesToRemove) { >+ Point[] rest = new Point[points.length - indicesToRemove.length]; >+ Arrays.sort(indicesToRemove); >+ for (int i = 0, j = 0; i < indicesToRemove.length; i++) { >+ for (int r = j; r < indicesToRemove[i]; r++) >+ rest[r - i] = points[r]; >+ j = indicesToRemove[i] + 1; >+ } >+ for (int i = indicesToRemove[indicesToRemove.length - 1] + 1; i < points.length; i++) >+ rest[i - indicesToRemove.length] = points[i]; >+ return rest; >+ } >+ >+ /** >+ * Clips exactly one ear off of the given {@link Polygon} and adds it to the >+ * list of ears. If the resulting {@link Polygon} is a triangle, this is >+ * added to the list of ears, too. Otherwise, the method recurses. >+ * >+ * @param p >+ * @param ears >+ */ >+ private static void triangulate(Polygon p, ArrayList<Polygon> ears) { >+ if (p == null) { >+ throw new IllegalArgumentException( >+ "The given Polygon may not be null."); >+ } >+ if (ears == null) { >+ throw new IllegalArgumentException( >+ "The given ear-list may not be null."); >+ } >+ if (p.points.length < 3) { >+ throw new IllegalArgumentException( >+ "The given Polygon may not have less than three vertices."); >+ } >+ >+ if (p.points.length == 3) { >+ ears.add(p.getCopy()); >+ return; >+ } >+ >+ int[] ear = findEarVertex(p); >+ Polygon rest = clipEar(p, ear, ears); >+ >+ // recurse >+ triangulate(rest, ears); >+ } >+ > /** > * Constructs a new {@link Polygon} from a even-numbered sequence of > * coordinates. Similar to {@link Polygon#Polygon(Point...)}, only that >@@ -102,6 +205,34 @@ public class Polygon extends AbstractPointListBasedGeometry<Polygon> implements > } > > /** >+ * Assures that this {@link Polygon} is simple, i.e. it does not have any >+ * self-intersections. We do not need to test for voids as they are not >+ * considered in the interpretation of the {@link Polygon}'s {@link Point}s. >+ * >+ * If the {@link Polygon} does not have at least three vertices, a >+ * {@link NonSimplePolygonException} is thrown. >+ * >+ * The edges are added to the {@link Polygon} one after the other. If a >+ * self-intersection is found an {@link NonSimplePolygonException} is >+ * thrown. >+ */ >+ private void assureSimplicity() throws NonSimplePolygonException { >+ if (points.length < 3) >+ throw new NonSimplePolygonException( >+ "A polygon can only be constructed of at least 3 vertices."); >+ >+ for (Line e1 : getOutlineSegments()) >+ for (Line e2 : getOutlineSegments()) >+ if (!e1.getP1().equals(e2.getP1()) >+ && !e1.getP2().equals(e2.getP1()) >+ && !e1.getP1().equals(e2.getP2()) >+ && !e1.getP2().equals(e2.getP2())) >+ if (e1.touches(e2)) >+ throw new NonSimplePolygonException( >+ "Only simple polygons allowed. A polygon without any self-intersections is considered to be simple. This polygon is not simple."); >+ } >+ >+ /** > * Checks whether the point that is represented by its x- and y-coordinates > * is contained within this {@link Polygon}. > * >@@ -117,6 +248,67 @@ public class Polygon extends AbstractPointListBasedGeometry<Polygon> implements > return contains(new Point(x, y)); > } > >+ 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 CurveUtils.contains(this, g); >+ } >+ >+ /** >+ * Checks whether the given {@link Line} is fully contained within this >+ * {@link Polygon}. >+ * >+ * @param line >+ * The {@link Line} to test for containment >+ * @return <code>true</code> if the given {@link Line} is fully contained, >+ * <code>false</code> 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; >+ } >+ >+ Set<Double> intersectionParams = new HashSet<Double>(); >+ >+ for (Line seg : getOutlineSegments()) { >+ Point poi = seg.getIntersection(line); >+ if (poi != null) >+ intersectionParams.add(line.getParameterAt(poi)); >+ } >+ >+ if (intersectionParams.size() <= 1) { >+ return true; >+ } >+ >+ Double[] poiParams = intersectionParams.toArray(new Double[] {}); >+ Arrays.sort(poiParams, new Comparator<Double>() { >+ public int compare(Double t, Double u) { >+ double d = t - u; >+ return d < 0 ? -1 : d > 0 ? 1 : 0; >+ } >+ }); >+ >+ // check the points between the intersections for containment >+ if (!contains(line.get(poiParams[0] / 2))) { >+ return false; >+ } >+ for (int i = 0; i < poiParams.length - 1; i++) { >+ if (!contains(line.get((poiParams[i] + poiParams[i + 1]) / 2))) { >+ return false; >+ } >+ } >+ return contains(line.get((poiParams[poiParams.length - 1] + 1) / 2)); >+ } >+ > /** > * @see IGeometry#contains(Point) > */ >@@ -211,6 +403,57 @@ public class Polygon extends AbstractPointListBasedGeometry<Polygon> implements > } > } > >+ /** >+ * Checks whether the given {@link Polygon} is fully contained within this >+ * {@link Polygon}. >+ * >+ * @param p >+ * The {@link Polygon} to test for containment >+ * @return <code>true</code> if the given {@link Polygon} is fully >+ * contained, <code>false</code> 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; >+ } >+ >+ /** >+ * Checks whether the given {@link Rectangle} is fully contained within this >+ * {@link Polygon}. >+ * >+ * @param r >+ * the {@link Rectangle} to test for containment >+ * @return <code>true</code> if the given {@link Rectangle} is fully >+ * contained, <code>false</code> otherwise. >+ */ >+ public boolean contains(Rectangle r) { >+ return contains(r.toPolygon()); >+ } >+ > @Override > public boolean equals(Object o) { > if (this == o) >@@ -272,21 +515,7 @@ public class Polygon extends AbstractPointListBasedGeometry<Polygon> implements > * @return the area of this {@link Polygon} > */ > public double getArea() { >- if (points.length < 3) { >- return 0; >- } >- >- 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; >- } >- >- // closing segment >- area += points[points.length - 2].x * points[points.length - 1].y >- - points[points.length - 2].y * points[points.length - 1].x; >- >- return Math.abs(area) * 0.5; >+ return Math.abs(getSignedArea()); > } > > /** >@@ -299,6 +528,10 @@ public class Polygon extends AbstractPointListBasedGeometry<Polygon> implements > return new Polygon(getPoints()); > } > >+ public Polyline getOutline() { >+ return new Polyline(PointListUtils.toSegmentsArray(points, true)); >+ } >+ > /** > * Returns a sequence of {@link Line}s, representing the segments that are > * obtained by linking each two successive point of this {@link Polygon} >@@ -312,6 +545,31 @@ public class Polygon extends AbstractPointListBasedGeometry<Polygon> implements > } > > /** >+ * Computes the signed area of this {@link Polygon}. The sign of the area is >+ * negative for counter clockwise ordered vertices. It is positive for >+ * clockwise ordered vertices. >+ * >+ * @return the signed area of this {@link Polygon} >+ */ >+ public double getSignedArea() { >+ if (points.length < 3) { >+ return 0; >+ } >+ >+ 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; >+ } >+ >+ // closing segment >+ area += points[points.length - 2].x * points[points.length - 1].y >+ - points[points.length - 2].y * points[points.length - 1].x; >+ >+ return area * 0.5; >+ } >+ >+ /** > * @see IGeometry#getTransformed(AffineTransform) > */ > @Override >@@ -321,6 +579,21 @@ public class Polygon extends AbstractPointListBasedGeometry<Polygon> implements > } > > /** >+ * Naive, recursive ear-clipping algorithm to triangulate this simple, >+ * planar {@link Polygon}. >+ * >+ * @return triangulation {@link Polygon}s (triangles) >+ * @throws NonSimplePolygonException >+ * if <code>this</code> is a non-simple {@link Polygon} >+ */ >+ public Polygon[] getTriangulation() throws NonSimplePolygonException { >+ assureSimplicity(); >+ ArrayList<Polygon> ears = new ArrayList<Polygon>(points.length - 2); >+ triangulate(this, ears); >+ return ears.toArray(new Polygon[] {}); >+ } >+ >+ /** > * @see IGeometry#toPath() > */ > public Path toPath() { >@@ -351,110 +624,4 @@ public class Polygon extends AbstractPointListBasedGeometry<Polygon> implements > return stringBuffer.toString(); > } > >- public Polyline getOutline() { >- return new Polyline(PointListUtils.toSegmentsArray(points, true)); >- } >- >- /** >- * Checks whether the given {@link Line} is fully contained within this >- * {@link Polygon}. >- * >- * @param line >- * The {@link Line} to test for containment >- * @return <code>true</code> if the given {@link Line} is fully contained, >- * <code>false</code> 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; >- } >- >- /** >- * Checks whether the given {@link Polygon} is fully contained within this >- * {@link Polygon}. >- * >- * @param p >- * The {@link Polygon} to test for containment >- * @return <code>true</code> if the given {@link Polygon} is fully >- * contained, <code>false</code> 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; >- } >- >- /** >- * Checks whether the given {@link Rectangle} is fully contained within this >- * {@link Polygon}. >- * >- * @param r >- * the {@link Rectangle} to test for containment >- * @return <code>true</code> if the given {@link Rectangle} is fully >- * contained, <code>false</code> otherwise. >- */ >- public boolean contains(Rectangle r) { >- return contains(r.toPolygon()); >- } >- >- /** >- * 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; >- } >- >- 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 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 3347b5b..6701353 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 >@@ -48,6 +48,16 @@ public class Polyline extends AbstractPointListBasedGeometry<Polyline> > } > > /** >+ * Constructs a new {@link Polyline} from the given array of {@link Line} >+ * segments. >+ * >+ * @param segmentsArray >+ */ >+ public Polyline(Line[] segmentsArray) { >+ super(PointListUtils.toPointsArray(segmentsArray, false)); >+ } >+ >+ /** > * Constructs a new {@link Polyline} from the given sequence of > * {@link Point} s. The {@link Polyline} that is created will be > * automatically closed, i.e. it will not only contain a segment between >@@ -63,16 +73,6 @@ public class Polyline extends AbstractPointListBasedGeometry<Polyline> > } > > /** >- * 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}. > * >@@ -106,7 +106,7 @@ public class Polyline extends AbstractPointListBasedGeometry<Polyline> > public boolean equals(Object o) { > if (this == o) > return true; >- if (o instanceof Polygon) { >+ if (o instanceof Polyline) { > Polyline p = (Polyline) o; > return equals(p.getPoints()); > } >@@ -129,7 +129,15 @@ public class Polyline extends AbstractPointListBasedGeometry<Polyline> > if (points.length != this.points.length) { > return false; > } >- return PointListUtils.equals(this.points, points); >+ return PointListUtils.equals(this.points, points) >+ || PointListUtils.equalsReverse(this.points, points); >+ } >+ >+ /** >+ * @see org.eclipse.gef4.geometry.planar.IGeometry#getCopy() >+ */ >+ public Polyline getCopy() { >+ return new Polyline(getPoints()); > } > > /** >@@ -144,6 +152,18 @@ public class Polyline extends AbstractPointListBasedGeometry<Polyline> > return PointListUtils.toSegmentsArray(points, false); > } > >+ public Point[] getIntersections(ICurve c) { >+ return CurveUtils.getIntersections(c, this); >+ } >+ >+ public Point getP1() { >+ return points[0].getCopy(); >+ } >+ >+ public Point getP2() { >+ return points[points.length - 1].getCopy(); >+ } >+ > /** > * @see IGeometry#getTransformed(AffineTransform) > */ >@@ -152,18 +172,32 @@ public class Polyline extends AbstractPointListBasedGeometry<Polyline> > return new Polyline(t.getTransformed(points)); > } > >- /** >- * 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 <code>true</code> if this {@link Polyline} and the >- * {@link Rectangle} touch, otherwise <code>false</code> >- * @see IGeometry#touches(IGeometry) >- */ >- public boolean touches(Rectangle rect) { >- throw new UnsupportedOperationException("Not yet implemented."); >+ public double getX1() { >+ return getP1().x; >+ } >+ >+ public double getX2() { >+ return getP2().x; >+ } >+ >+ public double getY1() { >+ return getP1().y; >+ } >+ >+ public double getY2() { >+ return getP2().y; >+ } >+ >+ public boolean intersects(ICurve c) { >+ return CurveUtils.intersects(c, this); >+ } >+ >+ public boolean overlaps(ICurve c) { >+ return CurveUtils.overlaps(c, this); >+ } >+ >+ public Line[] toBezier() { >+ return PointListUtils.toSegmentsArray(points, false); > } > > /** >@@ -208,50 +242,17 @@ public class Polyline extends AbstractPointListBasedGeometry<Polyline> > } > > /** >- * @see org.eclipse.gef4.geometry.planar.IGeometry#getCopy() >+ * 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 <code>true</code> if this {@link Polyline} and the >+ * {@link Rectangle} touch, otherwise <code>false</code> >+ * @see IGeometry#touches(IGeometry) > */ >- public Polyline getCopy() { >- 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); >+ public boolean touches(Rectangle rect) { >+ throw new UnsupportedOperationException("Not yet implemented."); > } > > } >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 dbc1d1d..fb52aea 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 >@@ -13,7 +13,6 @@ > package org.eclipse.gef4.geometry.planar; > > import org.eclipse.gef4.geometry.Point; >-import org.eclipse.gef4.geometry.utils.PolynomCalculationUtils; > > /** > * Represents the geometric shape of a quadratic Bézier curve. >@@ -88,19 +87,6 @@ public class QuadraticCurve extends BezierCurve { > this(p1.x, p1.y, pCtrl.x, pCtrl.y, p2.x, p2.y); > } > >- /** >- * Clips this {@link QuadraticCurve} at parameter values t1 and t2 so that >- * the resulting {@link QuadraticCurve} is the section of the original >- * {@link QuadraticCurve} for the parameter interval [t1, t2]. >- * >- * @param t1 >- * @param t2 >- * @return the {@link QuadraticCurve} on the interval [t1, t2] >- */ >- public QuadraticCurve clip(double t1, double t2) { >- return super.getClipped(t1, t2).toQuadratic(); >- } >- > @Override > public boolean equals(Object other) { > QuadraticCurve o = (QuadraticCurve) other; >@@ -112,68 +98,45 @@ public class QuadraticCurve extends BezierCurve { > } > > /** >+ * Erroneous getBounds() implementation... use the generic one instead. >+ * >+ * TODO: find out why the mathematical solution is erroneous in some cases. >+ * > * Returns the bounds of this QuadraticCurve. The bounds are calculated by > * examining the extreme points of the x(t) and y(t) function > * representations of this QuadraticCurve. > * > * @return the bounds {@link Rectangle} > */ >- @Override >- public Rectangle getBounds() { >- // extremes of the x(t) and y(t) functions: >- double[] xts; >- try { >- xts = PolynomCalculationUtils.getLinearRoots(2 * (getX1() - 2 >- * getCtrlX() + getX2()), 2 * (getCtrlX() - getX1())); >- } catch (ArithmeticException x) { >- return new Rectangle(getP1(), getP2()); >- } >- >- double xmin = getX1(), xmax = getX1(); >- if (getX2() < xmin) { >- xmin = getX2(); >- } else { >- xmax = getX2(); >- } >- >- for (double t : xts) { >- if (t >= 0 && t <= 1) { >- double x = get(t).x; >- if (x < xmin) { >- xmin = x; >- } else if (x > xmax) { >- xmax = x; >- } >- } >- } >- >- double[] yts; >- try { >- yts = PolynomCalculationUtils.getLinearRoots(2 * (getY1() - 2 >- * getCtrlY() + getY2()), 2 * (getCtrlY() - getY1())); >- } catch (ArithmeticException x) { >- return new Rectangle(getP1(), getP2()); >- } >- >- double ymin = getY1(), ymax = getY1(); >- if (getY2() < ymin) { >- ymin = getY2(); >- } else { >- ymax = getY2(); >- } >- >- for (double t : yts) { >- if (t >= 0 && t <= 1) { >- double y = get(t).y; >- if (y < ymin) { >- ymin = y; >- } else if (y > ymax) { >- ymax = y; >- } >- } >- } >+ /* >+ * public Rectangle getBounds() { // extremes of the x(t) and y(t) >+ * functions: double[] xts; try { xts = >+ * PolynomCalculationUtils.getLinearRoots(2 * (getX1() - 2 getCtrlX() + >+ * getX2()), 2 * (getCtrlX() - getX1())); } catch (ArithmeticException x) { >+ * return new Rectangle(getP1(), getP2()); } >+ * >+ * double xmin = getX1(), xmax = getX1(); if (getX2() < xmin) { xmin = >+ * getX2(); } else { xmax = getX2(); } >+ * >+ * for (double t : xts) { if (t >= 0 && t <= 1) { double x = get(t).x; if (x >+ * < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } } } >+ * >+ * double[] yts; try { yts = PolynomCalculationUtils.getLinearRoots(2 * >+ * (getY1() - 2 getCtrlY() + getY2()), 2 * (getCtrlY() - getY1())); } catch >+ * (ArithmeticException x) { return new Rectangle(getP1(), getP2()); } >+ * >+ * double ymin = getY1(), ymax = getY1(); if (getY2() < ymin) { ymin = >+ * getY2(); } else { ymax = getY2(); } >+ * >+ * for (double t : yts) { if (t >= 0 && t <= 1) { double y = get(t).y; if (y >+ * < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } } } >+ * >+ * return new Rectangle(new Point(xmin, ymin), new Point(xmax, ymax)); } >+ */ > >- return new Rectangle(new Point(xmin, ymin), new Point(xmax, ymax)); >+ @Override >+ public QuadraticCurve getClipped(double t1, double t2) { >+ return super.getClipped(t1, t2).toQuadratic(); > } > > private Polygon getControlPolygon() { >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 efa9a75..447eaf8 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 >@@ -39,8 +39,8 @@ import org.eclipse.gef4.geometry.utils.PrecisionUtils; > * @author ahunter > * @author anyssen > */ >-public final class Rectangle extends AbstractRectangleBasedGeometry<Rectangle> >- implements IShape { >+public final class Rectangle extends >+ AbstractRectangleBasedGeometry<Rectangle, Polygon> implements IShape { > > private static final long serialVersionUID = 1L; > >@@ -149,6 +149,37 @@ public final class Rectangle extends AbstractRectangleBasedGeometry<Rectangle> > } > > /** >+ * 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 <code>true</code> if the rectangle characterized by (x,y, width, >+ * height) is (imprecisely) fully contained within this >+ * {@link Rectangle}, <code>false</code> 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.x + this.width, x + width) >+ && PrecisionUtils >+ .greaterEqual(this.y + this.height, y + height); >+ } >+ >+ public boolean contains(IGeometry g) { >+ if (g instanceof Rectangle) { >+ return contains((Rectangle) g); >+ } >+ return CurveUtils.contains(this, g); >+ } >+ >+ /** > * 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. >@@ -163,6 +194,21 @@ public final class Rectangle extends AbstractRectangleBasedGeometry<Rectangle> > } > > /** >+ * 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 <code>true</code> if this {@link Rectangle} contains the other >+ * {@link Rectangle}, otherwise <code>false</code> >+ * @see IShape#contains(IGeometry) >+ */ >+ public boolean contains(Rectangle r) { >+ return contains(r.x, r.y, r.width, r.height); >+ } >+ >+ /** > * Returns <code>true</code> if this Rectangle's x, y, width, and height > * values are identical to the provided ones. > * >@@ -259,33 +305,6 @@ public final class Rectangle extends AbstractRectangleBasedGeometry<Rectangle> > } > > /** >- * Returns an array of {@link Point}s representing the top-left, top-right, >- * bottom-right, and bottom-left border points of this {@link Rectangle}. >- * >- * @return An array containing the border points of this {@link Rectangle} >- */ >- public Point[] getPoints() { >- return new Point[] { getTopLeft(), getTopRight(), getBottomRight(), >- getBottomLeft() }; >- } >- >- /** >- * Returns an array of {@link Line}s representing the top, right, bottom, >- * and left borders of this {@link Rectangle}. >- * >- * @return An array containing {@link Line} representations of this >- * {@link Rectangle}'s borders. >- */ >- public Line[] getOutlineSegments() { >- Line[] segments = new Line[4]; >- segments[0] = new Line(x, y, x + width, y); >- segments[1] = new Line(x + width, y, x + width, y + height); >- segments[2] = new Line(x + width, y + height, x, y + height); >- segments[3] = new Line(x, y + height, x, y); >- return segments; >- } >- >- /** > * Returns a new Point representing the middle point of the bottom side of > * this Rectangle. > * >@@ -383,6 +402,38 @@ public final class Rectangle extends AbstractRectangleBasedGeometry<Rectangle> > return new Point(x, y + height / 2); > } > >+ public Polyline getOutline() { >+ return new Polyline(x, y, x + width, y, x + width, y + height, x, y >+ + height, x, y); >+ } >+ >+ /** >+ * Returns an array of {@link Line}s representing the top, right, bottom, >+ * and left borders of this {@link Rectangle}. >+ * >+ * @return An array containing {@link Line} representations of this >+ * {@link Rectangle}'s borders. >+ */ >+ public Line[] getOutlineSegments() { >+ Line[] segments = new Line[4]; >+ segments[0] = new Line(x, y, x + width, y); >+ segments[1] = new Line(x + width, y, x + width, y + height); >+ segments[2] = new Line(x + width, y + height, x, y + height); >+ segments[3] = new Line(x, y + height, x, y); >+ return segments; >+ } >+ >+ /** >+ * Returns an array of {@link Point}s representing the top-left, top-right, >+ * bottom-right, and bottom-left border points of this {@link Rectangle}. >+ * >+ * @return An array containing the border points of this {@link Rectangle} >+ */ >+ public Point[] getPoints() { >+ return new Point[] { getTopLeft(), getTopRight(), getBottomRight(), >+ getBottomLeft() }; >+ } >+ > /** > * Returns a new Point which represents the middle point of the right hand > * side of this Rectangle. >@@ -394,21 +445,42 @@ public final class Rectangle extends AbstractRectangleBasedGeometry<Rectangle> > } > > /** >- * Rotates this {@link Rectangle} clock-wise by the given {@link Angle} >- * around the center ({@link #getCentroid()}) of this {@link Rectangle}. >+ * 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 #getRotatedCW(Angle, Point) >+ * @see #getRotatedCCW(Angle, Point) > * @param alpha >- * the rotation {@link Angle} > * @return the resulting {@link Polygon} > */ >- public Polygon getRotatedCW(Angle alpha) { >- return getRotatedCW(alpha, getCentroid()); >+ public Polygon getRotatedCCW(Angle alpha) { >+ Point centroid = getCentroid(); >+ return toPolygon().rotateCCW(alpha, centroid.x, centroid.y); > } > > /** >- * Rotates this {@link Rectangle} clock-wise by the given {@link Angle} >- * alpha around the given {@link Point}. >+ * Rotates this {@link Rectangle} counter-clock-wise by the given >+ * {@link Angle} around the given {@link Point}. >+ * >+ * 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 cx >+ * x-component of the center point for the rotation >+ * @param cy >+ * y-component of the center point for the rotation >+ * @return the resulting {@link Polygon} >+ */ >+ public Polygon getRotatedCCW(Angle alpha, double cx, double cy) { >+ return toPolygon().rotateCCW(alpha, cx, cy); >+ } >+ >+ /** >+ * Rotates this {@link Rectangle} counter-clock-wise by the given >+ * {@link Angle} around the given {@link Point}. > * > * If the rotation {@link Angle} is not an integer multiple of 90 degrees, > * the resulting figure cannot be expressed as a {@link Rectangle} object. >@@ -420,26 +492,47 @@ public final class Rectangle extends AbstractRectangleBasedGeometry<Rectangle> > * the center point for the rotation > * @return the resulting {@link Polygon} > */ >- public Polygon getRotatedCW(Angle alpha, Point center) { >- return toPolygon().rotateCW(alpha, center); >+ public Polygon getRotatedCCW(Angle alpha, Point center) { >+ return toPolygon().rotateCCW(alpha, center.x, center.y); > } > > /** >- * Rotates this {@link Rectangle} counter-clock-wise by the given >- * {@link Angle} around the center {@link Point} of this {@link Rectangle} >- * (see {@link #getCentroid()}). >+ * Rotates this {@link Rectangle} clock-wise by the given {@link Angle} >+ * around the center ({@link #getCentroid()}) of this {@link Rectangle}. > * >- * @see #getRotatedCCW(Angle, Point) >+ * @see #getRotatedCW(Angle, Point) > * @param alpha >+ * the rotation {@link Angle} > * @return the resulting {@link Polygon} > */ >- public Polygon getRotatedCCW(Angle alpha) { >- return getRotatedCCW(alpha, getCentroid()); >+ public Polygon getRotatedCW(Angle alpha) { >+ Point centroid = getCentroid(); >+ return toPolygon().rotateCW(alpha, centroid.x, centroid.y); > } > > /** >- * Rotates this {@link Rectangle} counter-clock-wise by the given >- * {@link Angle} around the given {@link Point}. >+ * Rotates this {@link Rectangle} clock-wise by the given {@link Angle} >+ * alpha around the given {@link Point} (cx, cy). >+ * >+ * 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 cx >+ * x-component of the center point for the rotation >+ * @param cy >+ * y-component of the center point for the rotation >+ * @return the resulting {@link Polygon} >+ */ >+ public Polygon getRotatedCW(Angle alpha, double cx, double cy) { >+ return toPolygon().rotateCW(alpha, cx, cy); >+ } >+ >+ /** >+ * 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 of 90 degrees, > * the resulting figure cannot be expressed as a {@link Rectangle} object. >@@ -451,8 +544,8 @@ public final class Rectangle extends AbstractRectangleBasedGeometry<Rectangle> > * the center point for the rotation > * @return the resulting {@link Polygon} > */ >- public Polygon getRotatedCCW(Angle alpha, Point center) { >- return toPolygon().rotateCCW(alpha, center); >+ public Polygon getRotatedCW(Angle alpha, Point center) { >+ return toPolygon().rotateCW(alpha, center.x, center.y); > } > > /** >@@ -592,58 +685,6 @@ public final class Rectangle extends AbstractRectangleBasedGeometry<Rectangle> > } > > /** >- * 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. >- * @return <code>true</code> if this {@link Rectangle} and the given >- * {@link Line} share at least one common point, <code>false</code> >- * otherwise. >- */ >- public boolean touches(Line l) { >- if (contains(l.getP1()) || contains(l.getP2())) { >- return true; >- } >- >- for (Line segment : getOutlineSegments()) { >- if (segment.intersects(l)) { >- return true; >- } >- } >- return false; >- } >- >- /** >- * 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 <code>true</code> if this {@link Rectangle} and the given >- * {@link Rectangle} share at least one common point, >- * <code>false</code> 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); >- } >- >- @Override >- 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); >- } >- >- /** > * Returns <code>true</code> if this Rectangle's width or height is less > * than or equal to 0. > * >@@ -744,6 +785,57 @@ public final class Rectangle extends AbstractRectangleBasedGeometry<Rectangle> > (int) Math.ceil(height + y - Math.floor(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); >+ } >+ >+ /** >+ * 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. >+ * @return <code>true</code> if this {@link Rectangle} and the given >+ * {@link Line} share at least one common point, <code>false</code> >+ * otherwise. >+ */ >+ public boolean touches(Line l) { >+ if (contains(l.getP1()) || contains(l.getP2())) { >+ return true; >+ } >+ >+ for (Line segment : getOutlineSegments()) { >+ if (segment.intersects(l)) { >+ return true; >+ } >+ } >+ return false; >+ } >+ >+ /** >+ * 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 <code>true</code> if this {@link Rectangle} and the given >+ * {@link Rectangle} share at least one common point, >+ * <code>false</code> 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); >+ } >+ > /** > * Switches the x and y values, as well as the width and height of this > * Rectangle. Useful for orientation changes. >@@ -836,55 +928,4 @@ public final class Rectangle extends AbstractRectangleBasedGeometry<Rectangle> > 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 <code>true</code> if the rectangle characterized by (x,y, width, >- * height) is (imprecisely) fully contained within this >- * {@link Rectangle}, <code>false</code> 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.x + this.width, x + width) >- && PrecisionUtils >- .greaterEqual(this.y + this.height, y + 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 <code>true</code> if this {@link Rectangle} contains the other >- * {@link Rectangle}, otherwise <code>false</code> >- * @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 b821610..ea0806b 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 >@@ -7,46 +7,377 @@ > * > * 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.Iterator; >+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.transform.IRotatable; >+import org.eclipse.gef4.geometry.transform.IScalable; >+import org.eclipse.gef4.geometry.transform.ITranslatable; >+import org.eclipse.gef4.geometry.utils.CurveUtils; > > /** >- * a combination of rectangles... >+ * A combination of {@link Rectangle}s. The {@link Rectangle}s that build up a >+ * {@link Region} do not have to be touching. The area covered by the >+ * {@link Region} is exactly the area that all of its corresponding >+ * {@link Rectangle}s are covering. >+ * >+ * A {@link Region} differentiates between the internal {@link Rectangle}s and >+ * the external {@link Rectangle}s. The external {@link Rectangle}s are those >+ * that you feed it, in order to construct the {@link Region}. The internal >+ * {@link Rectangle}s are those used for computations of the {@link Region}. >+ * They are defined to not share any area, so that only their borders can be >+ * overlapping. > * > * @author nyssen > * > */ >-public class Region extends AbstractGeometry implements IPolyShape { >+public class Region extends AbstractPolyShape implements ITranslatable<Region>, >+ IScalable<Region>, IRotatable<Ring> { > >- public Rectangle[] getShapes() { >- throw new UnsupportedOperationException("Not yet implemented."); >+ private static final long serialVersionUID = 1L; >+ >+ /** >+ * Cuts the given {@link Rectangle}s along the given parallel to the x-axis. >+ * >+ * @param y >+ * the distance of the cut-line to the x-axis >+ * @param parts >+ * the {@link Rectangle}s to cut along that line >+ */ >+ private static void cutH(double y, ArrayList<Rectangle> parts) { >+ for (Rectangle r : new ArrayList<Rectangle>(parts)) { >+ if (r.y < y && y < r.y + r.height) { >+ parts.remove(r); >+ parts.add(new Rectangle(r.x, r.y, r.width, y - r.y)); >+ parts.add(new Rectangle(r.x, y, r.width, r.y + r.height - y)); >+ } >+ } >+ } >+ >+ /** >+ * Cuts the given {@link Rectangle}s along the given parallel to the y-axis. >+ * >+ * @param x >+ * the distance of the cut-line to the y-axis >+ * @param parts >+ * the {@link Rectangle}s to cut along that line >+ */ >+ private static void cutV(double x, ArrayList<Rectangle> parts) { >+ for (Rectangle r : new ArrayList<Rectangle>(parts)) { >+ if (r.x < x && x < r.x + r.width) { >+ parts.remove(r); >+ parts.add(new Rectangle(r.x, r.y, x - r.x, r.height)); >+ parts.add(new Rectangle(x, r.y, r.x + r.width - x, r.height)); >+ } >+ } >+ } >+ >+ private ArrayList<Rectangle> rects; >+ >+ /** >+ * Constructs a new {@link Region} not covering any area. >+ */ >+ public Region() { >+ rects = new ArrayList<Rectangle>(); >+ } >+ >+ /** >+ * Constructs a new {@link Region} from the given list of {@link Rectangle} >+ * s. >+ * >+ * The given {@link Rectangle}s are {@link #add(Rectangle)}ed to the >+ * {@link Region} one after the other. >+ * >+ * @param rectangles >+ */ >+ public Region(Rectangle... rectangles) { >+ this(); >+ rects.add(rectangles[0].getCopy()); >+ >+ for (int i = 1; i < rectangles.length; i++) { >+ add(rectangles[i].getCopy()); >+ } >+ } >+ >+ /** >+ * Constructs a new {@link Region} from the given other {@link Region}. In >+ * other words, it copies the given other {@link Region}. >+ * >+ * @param other >+ */ >+ public Region(Region other) { >+ rects = new ArrayList<Rectangle>(other.rects.size()); >+ >+ for (Rectangle or : other.rects) { >+ rects.add(or.getCopy()); >+ } >+ } >+ >+ /** >+ * Adds the given {@link Rectangle} to this {@link Region}. >+ * >+ * To assure the required conditions for internal {@link Rectangle}s, the >+ * given {@link Rectangle} is cut into several sub-{@link Rectangle}s so >+ * that no internal {@link Rectangle}s share any area. >+ * >+ * @param rectangle >+ * the {@link Rectangle} to add to this {@link Region} >+ * @return <code>this</code> for convenience >+ */ >+ public Region add(Rectangle rectangle) { >+ ArrayList<Rectangle> toAdd = new ArrayList<Rectangle>(1); >+ >+ toAdd.add(rectangle.getCopy()); >+ >+ for (Rectangle retain : rects) { >+ for (Rectangle addend : new ArrayList<Rectangle>(toAdd)) { >+ ArrayList<Rectangle> parts = new ArrayList<Rectangle>(8); >+ parts.add(addend); >+ >+ if (addend.x <= retain.x && retain.x <= addend.x + addend.width >+ || retain.x <= addend.x >+ && addend.x <= retain.x + retain.width) { >+ cutH(retain.y, parts); >+ cutH(retain.y + retain.height, parts); >+ } >+ >+ if (addend.y <= retain.y >+ && retain.y <= addend.y + addend.height >+ || retain.y <= addend.y >+ && addend.y <= retain.y + retain.height) { >+ cutV(retain.x, parts); >+ cutV(retain.x + retain.width, parts); >+ } >+ >+ // filter inner parts: >+ for (Iterator<Rectangle> p = parts.iterator(); p.hasNext();) { >+ if (retain.contains(p.next())) { >+ p.remove(); >+ } >+ } >+ >+ toAdd.remove(addend); >+ toAdd.addAll(parts); >+ } >+ } >+ >+ rects.addAll(toAdd); >+ >+ return this; > } > >- public boolean contains(Point p) { >- throw new UnsupportedOperationException("Not yet implemented."); >+ public boolean contains(IGeometry g) { >+ return CurveUtils.contains(this, g); > } > >- public boolean contains(Rectangle r) { >- throw new UnsupportedOperationException("Not yet implemented."); >+ /** >+ * Collects all outline segments of the internal {@link Rectangle}s. >+ * >+ * @return all the outline segments of the internal {@link Rectangle}s >+ */ >+ protected Line[] getAllEdges() { >+ Stack<Line> edges = new Stack<Line>(); >+ >+ for (Rectangle r : rects) { >+ for (Line e : r.getOutlineSegments()) { >+ edges.push(e); >+ } >+ } >+ return edges.toArray(new Line[] {}); > } > > public Rectangle getBounds() { >- throw new UnsupportedOperationException("Not yet implemented."); >+ if (rects.size() == 0) >+ return null; >+ >+ Rectangle bounds = rects.get(0).getBounds(); >+ for (int i = 1; i < rects.size(); i++) >+ bounds.union(rects.get(i).getBounds()); >+ >+ return bounds; >+ } >+ >+ public Region getCopy() { >+ return new Region(this); > } > >- public boolean touches(Rectangle r) { >- throw new UnsupportedOperationException("Not yet implemented."); >+ /** >+ * Computes the {@link Point}s of intersection of this {@link Region} with >+ * the given {@link ICurve}. >+ * >+ * @param c >+ * @return the intersection {@link Point}s >+ */ >+ public Point[] getOutlineIntersections(ICurve c) { >+ Set<Point> intersections = new HashSet<Point>(0); >+ >+ for (Line seg : getOutlineSegments()) { >+ intersections.addAll(Arrays.asList(seg.getIntersections(c))); >+ } >+ >+ return intersections.toArray(new Point[] {}); >+ } >+ >+ public Rectangle[] getShapes() { >+ return rects.toArray(new Rectangle[] {}); > } > > public Path toPath() { >- throw new UnsupportedOperationException("Not yet implemented."); >+ return getOutline().toPath(); >+ } >+ >+ /** >+ * Constructs a new {@link Ring} that covers the same area as this >+ * {@link Region}. >+ * >+ * @return a new {@link Ring} that covers the same area as this >+ * {@link Region} >+ */ >+ public Ring toRing() { >+ Polygon[] polys = new Polygon[rects.size()]; >+ Iterator<Rectangle> i = rects.iterator(); >+ for (int j = 0; j < rects.size(); j++) { >+ polys[j] = i.next().toPolygon(); >+ } >+ return new Ring(polys); >+ } >+ >+ /** >+ * <p> >+ * Constructs a new {@link org.eclipse.swt.graphics.Region} that covers the >+ * same area as this {@link Region}. This is to ease the use of a >+ * {@link Region} for clipping: >+ * </p> >+ * >+ * <p> >+ * <code>gc.setClipping(region.toSWTRegion());</code> >+ * </p> >+ * >+ * @return a new {@link org.eclipse.swt.graphics.Region} that covers the >+ * same area as this {@link Region} >+ */ >+ public org.eclipse.swt.graphics.Region toSWTRegion() { >+ org.eclipse.swt.graphics.Region swtRegion = new org.eclipse.swt.graphics.Region(); >+ >+ for (Rectangle r : rects) { >+ swtRegion.add(r.toSWTRectangle()); >+ } >+ >+ return swtRegion; >+ } >+ >+ public Ring getRotatedCCW(Angle angle) { >+ Point centroid = getBounds().getCentroid(); >+ return getRotatedCCW(angle, centroid.x, centroid.y); >+ } >+ >+ public Ring getRotatedCCW(Angle angle, double cx, double cy) { >+ Polygon[] polys = new Polygon[rects.size()]; >+ for (int i = 0; i < polys.length; i++) >+ polys[i] = rects.get(i).getRotatedCCW(angle, cx, cy); >+ return new Ring(polys); >+ } >+ >+ public Ring getRotatedCCW(Angle angle, Point center) { >+ return getRotatedCCW(angle, center.x, center.y); >+ } >+ >+ public Ring getRotatedCW(Angle angle) { >+ Point centroid = getBounds().getCentroid(); >+ return getRotatedCW(angle, centroid.x, centroid.y); >+ } >+ >+ public Ring getRotatedCW(Angle angle, double cx, double cy) { >+ Polygon[] polys = new Polygon[rects.size()]; >+ for (int i = 0; i < polys.length; i++) >+ polys[i] = rects.get(i).getRotatedCW(angle, cx, cy); >+ return new Ring(polys); >+ } >+ >+ public Ring getRotatedCW(Angle angle, Point center) { >+ return getRotatedCW(angle, center.x, center.y); >+ } >+ >+ public Region scale(double factor) { >+ return scale(factor, factor); >+ } >+ >+ public Region scale(double factor, double cx, double cy) { >+ return scale(factor, factor, cx, cy); >+ } >+ >+ public Region scale(double factor, Point center) { >+ return scale(factor, factor, center.x, center.y); >+ } >+ >+ public Region scale(double fx, double fy) { >+ Point centroid = getBounds().getCentroid(); >+ return scale(fx, fy, centroid.x, centroid.y); >+ } >+ >+ public Region scale(double fx, double fy, double cx, double cy) { >+ for (Rectangle r : rects) { >+ r.scale(fx, fy, cx, cy); >+ } >+ return this; >+ } >+ >+ public Region scale(double fx, double fy, Point center) { >+ return scale(fx, fy, center.x, center.y); >+ } >+ >+ public Region getScaled(double factor) { >+ return getCopy().scale(factor); >+ } >+ >+ public Region getScaled(double factor, double cx, double cy) { >+ return getCopy().scale(factor, cx, cy); >+ } >+ >+ public Region getScaled(double factor, Point center) { >+ return getCopy().scale(factor, center); >+ } >+ >+ public Region getScaled(double fx, double fy) { >+ return getCopy().scale(fx, fy); >+ } >+ >+ public Region getScaled(double fx, double fy, double cx, double cy) { >+ return getCopy().scale(fx, fy, cx, cy); >+ } >+ >+ public Region getScaled(double fx, double fy, Point center) { >+ return getCopy().scale(fx, fy, center); >+ } >+ >+ public Region translate(double dx, double dy) { >+ for (Rectangle r : rects) { >+ r.translate(dx, dy); >+ } >+ return this; >+ } >+ >+ public Region translate(Point d) { >+ return translate(d.x, d.y); >+ } >+ >+ public Region getTranslated(double dx, double dy) { >+ return getCopy().translate(dx, dy); > } > >- public IGeometry getCopy() { >- throw new UnsupportedOperationException("Not yet implemented."); >+ public Region getTranslated(Point d) { >+ return getCopy().translate(d.x, d.y); > } > > } >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 403c453..806122b 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 >@@ -7,11 +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 java.util.ArrayList; >+import java.util.Iterator; >+import java.util.Stack; >+ >+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.IRotatable; >+import org.eclipse.gef4.geometry.transform.IScalable; >+import org.eclipse.gef4.geometry.transform.ITranslatable; >+import org.eclipse.gef4.geometry.utils.CurveUtils; > > /** > * >@@ -20,34 +32,502 @@ import org.eclipse.gef4.geometry.Point; > * @author anyssen > * > */ >-public class Ring extends AbstractGeometry implements IPolyShape { >+public class Ring extends AbstractPolyShape implements ITranslatable<Ring>, >+ IScalable<Ring>, IRotatable<Ring> { > >- public Polygon[] getShapes() { >- throw new UnsupportedOperationException("Not yet implemented."); >+ private static final long serialVersionUID = 1L; >+ >+ /** >+ * Triangulates the given triangle ({@link Polygon}) at the given >+ * {@link Line}. The triangulation is done using the simpler >+ * {@link #triangulate(Polygon, Point, Point)} method. The real and >+ * imaginary {@link Point}s of intersection of the {@link Line} and the >+ * {@link Polygon} are used as the split {@link Point}s. >+ * >+ * The triangulation is only done, if the {@link Line} intersects the >+ * {@link Polygon}, i.e. at least one {@link Point} of the {@link Line} lies >+ * inside the {@link Polygon} but not on its outline. >+ * >+ * @param p >+ * the triangle ({@link Polygon}) to triangulate >+ * @param l >+ * the line that determines the split {@link Point}s >+ * @return at least one and up to three {@link Polygon}s which are the >+ * resulting triangles >+ */ >+ private static Polygon[] triangulate(Polygon p, Line l) { >+ if (p == null) { >+ throw new IllegalArgumentException( >+ "The given Polygon parameter may not be null."); >+ } >+ if (l == null) { >+ throw new IllegalArgumentException( >+ "The given Line parameter may not be null."); >+ } >+ >+ boolean intersecting = l.getIntersections(p.getOutline()).length == 2; >+ >+ if (!intersecting) { >+ // test if at least one point of the line is really inside the >+ // polygon >+ for (Point lp : l.getPoints()) { >+ if (p.contains(lp) && !p.getOutline().contains(lp)) { >+ intersecting = true; >+ break; >+ } >+ } >+ >+ if (!intersecting) { >+ return new Polygon[] { p.getCopy() }; >+ } >+ } >+ >+ // calculate real and imaginary intersection points >+ Straight s = new Straight(l); >+ Point inters[] = new Point[2]; >+ int i = 0; >+ >+ for (Line e : p.getOutlineSegments()) { >+ Vector vi = s.getIntersection(new Straight(e)); >+ if (vi != null) { >+ Point poi = vi.toPoint(); >+ if (e.contains(poi)) >+ if (i > 0 && inters[0].equals(poi)) >+ continue; >+ else >+ inters[i++] = poi; >+ } >+ if (i > 1) >+ break; >+ } >+ >+ if (inters[0] == null || inters[1] == null) { >+ throw new IllegalStateException( >+ "The determined points of intersection do not lie on the polygon."); >+ } >+ >+ return triangulate(p, inters[0], inters[1]); >+ } >+ >+ /** >+ * <p> >+ * Splits a triangle at the line through points p1 and p2, which are >+ * required to lie on the outline of the triangle. >+ * </p> >+ * >+ * <p> >+ * If the points p1 and p2 lie on the same edge on the triangle, a copy of >+ * the given {@link Polygon} is returned. >+ * </p> >+ * >+ * <p> >+ * If one of the points lies on an edge, and the other point lies on a >+ * vertex of the triangle, two {@link Polygon}s are returned. They represent >+ * the areas left and right to the line through p1 and p2. >+ * </p> >+ * >+ * <p> >+ * If both points lie on different edges, three {@link Polygon}s are >+ * returned. One of them represents the triangle which lies on one side of >+ * the line through p1 and p2. The other two triangles are the triangulation >+ * of the tetragon on the other side of the line through p1 and p2. >+ * </p> >+ * >+ * @param p >+ * @param p1 >+ * @param p2 >+ * @return >+ */ >+ private static Polygon[] triangulate(Polygon p, Point p1, Point p2) { >+ Point[] v = p == null ? new Point[] {} : p.getPoints(); >+ if (v.length != 3) { >+ throw new IllegalArgumentException( >+ "Only triangles are allowed as the Polygon parameter."); >+ } >+ if (p1 == null) { >+ throw new IllegalArgumentException( >+ "The given p1 Point parameter may not be null."); >+ } >+ if (p2 == null) { >+ throw new IllegalArgumentException( >+ "The given p2 Point parameter may not be null."); >+ } >+ >+ Line[] e = new Line[] { new Line(v[0], v[1]), new Line(v[1], v[2]), >+ new Line(v[2], v[0]) }; >+ >+ // determine the edges on which the points lie >+ boolean p1_on_e0 = e[0].contains(p1); >+ boolean p1_on_e1 = e[1].contains(p1); >+ boolean p1_on_e2 = e[2].contains(p1); >+ boolean p2_on_e0 = e[0].contains(p2); >+ boolean p2_on_e1 = e[1].contains(p2); >+ boolean p2_on_e2 = e[2].contains(p2); >+ >+ // if both points lie on the same edge, we have nothing to do >+ if (p1_on_e0 && p2_on_e0 || p1_on_e1 && p2_on_e1 || p1_on_e2 >+ && p2_on_e2) { >+ return new Polygon[] { p.getCopy() }; >+ } >+ >+ // check if both points are on the triangle >+ else if (!(p1_on_e0 || p1_on_e1 || p1_on_e2) >+ || !(p2_on_e0 || p2_on_e1 || p2_on_e2)) { >+ throw new IllegalArgumentException( >+ "The Point objects have to lie on the outline of the Polygon object."); >+ } >+ >+ // determine if one of the points lies on a vertex >+ else if (p1.equals(v[0])) { >+ return new Polygon[] { new Polygon(v[0], v[1], p2), >+ new Polygon(v[0], v[2], p2) }; >+ } else if (p1.equals(v[1])) { >+ return new Polygon[] { new Polygon(v[0], v[1], p2), >+ new Polygon(v[1], v[2], p2) }; >+ } else if (p1.equals(v[2])) { >+ return new Polygon[] { new Polygon(v[0], v[2], p2), >+ new Polygon(v[1], v[2], p2) }; >+ } else if (p2.equals(v[0])) { >+ return new Polygon[] { new Polygon(v[0], v[1], p1), >+ new Polygon(v[0], v[2], p1) }; >+ } else if (p2.equals(v[1])) { >+ return new Polygon[] { new Polygon(v[0], v[1], p1), >+ new Polygon(v[1], v[2], p1) }; >+ } else if (p2.equals(v[2])) { >+ return new Polygon[] { new Polygon(v[0], v[2], p1), >+ new Polygon(v[1], v[2], p1) }; >+ } >+ >+ // both points on different edges, determine isolated vertex >+ else if (p1_on_e0 && p2_on_e2 || p1_on_e2 && p2_on_e0) { >+ // v0 isolated >+ return new Polygon[] { new Polygon(v[0], p1, p2), >+ new Polygon(p1, p2, v[1]), >+ new Polygon(p1_on_e0 ? p2 : p1, v[1], v[2]) }; >+ } else if (p1_on_e0 && p2_on_e1 || p1_on_e1 && p2_on_e0) { >+ // v1 isolated >+ return new Polygon[] { new Polygon(v[1], p1, p2), >+ new Polygon(p1, p2, v[0]), >+ new Polygon(p1_on_e0 ? p2 : p1, v[0], v[2]) }; >+ } else if (p1_on_e1 && p2_on_e2 || p1_on_e2 && p2_on_e1) { >+ // v2 isolated >+ return new Polygon[] { new Polygon(v[2], p1, p2), >+ new Polygon(p1, p2, v[1]), >+ new Polygon(p1_on_e1 ? p2 : p1, v[1], v[0]) }; >+ } else { >+ throw new IllegalStateException( >+ "Unreachable, because for two points on a triangle, they have to be located either (edge, edge), (vertex, edge), or (edge, vertex)."); >+ } > } > >- public boolean contains(Point p) { >- throw new UnsupportedOperationException("Not yet implemented."); >+ private ArrayList<Polygon> triangles; >+ >+ /** >+ * Constructs a new empty {@link Ring}. >+ */ >+ public Ring() { >+ triangles = new ArrayList<Polygon>(); > } > >- public boolean contains(Rectangle r) { >- throw new UnsupportedOperationException("Not yet implemented."); >+ /** >+ * Constructs a new {@link Ring} from the given {@link Polygon}s. >+ * >+ * @param polygons >+ */ >+ public Ring(Polygon... polygons) { >+ this(); >+ for (Polygon p : polygons) { >+ add(p); >+ } >+ } >+ >+ /** >+ * Constructs a new {@link Ring} of the given other {@link Ring}. The >+ * internal {@link IShape}s of the other {@link Ring} are copied to prevent >+ * actions at a distance. >+ * >+ * @param other >+ */ >+ public Ring(Ring other) { >+ this(); >+ for (Polygon p : other.triangles) { >+ add(p); >+ } >+ } >+ >+ /** >+ * Adds the given {@link Polygon} to this {@link Ring}. >+ * >+ * @param p >+ * @return <code>this</code> for convenience >+ */ >+ public Ring add(Polygon p) { >+ Stack<Polygon> toAdd = new Stack<Polygon>(); >+ for (Polygon triangleToAdd : p.getTriangulation()) >+ toAdd.push(triangleToAdd); >+ >+ while (!toAdd.empty()) { >+ Polygon triangleToAdd = toAdd.pop(); >+ Stack<Polygon> localAddends = new Stack<Polygon>(); >+ localAddends.push(triangleToAdd); >+ for (Polygon triangleAlreadyThere : triangles) { >+ for (Line e : triangleAlreadyThere.getOutlineSegments()) { >+ Stack<Polygon> nextAddends = new Stack<Polygon>(); >+ for (Iterator<Polygon> i = localAddends.iterator(); i >+ .hasNext();) { >+ Polygon addend = i.next(); >+ i.remove(); >+ for (Polygon subTriangleToAdd : triangulate(addend, e)) >+ if (!triangleAlreadyThere >+ .contains(subTriangleToAdd)) >+ nextAddends.push(subTriangleToAdd); >+ } >+ localAddends = nextAddends; >+ } >+ } >+ for (Polygon addend : localAddends) { >+ triangles.add(addend); >+ } >+ } >+ >+ optimizeTriangles(); >+ >+ return this; >+ } >+ >+ public boolean contains(IGeometry g) { >+ return CurveUtils.contains(this, g); >+ } >+ >+ private boolean findSharedAndOuterVertices(Polygon t1, Polygon t2, >+ Point[] shared, Point[] outer) { >+ Point[] t1Points = t1.getPoints(); >+ Point[] t2Points = t2.getPoints(); >+ boolean[] t1IsShared = new boolean[] { false, false, false }; >+ boolean[] t2IsShared = new boolean[] { false, false, false }; >+ >+ int sc = 0; >+ for (int i = 0; i < t1Points.length; i++) { >+ for (int j = 0; j < t2Points.length; j++) { >+ if (t1Points[i].equals(t2Points[j])) { >+ if (sc++ == 2) { >+ return false; >+ } >+ t1IsShared[i] = true; >+ t2IsShared[j] = true; >+ } >+ } >+ } >+ if (sc != 2) { >+ return false; >+ } >+ >+ for (int i = 0, c = 0; i < t1Points.length; i++) { >+ if (t1IsShared[i]) { >+ shared[c++] = t1Points[i]; >+ } else { >+ outer[0] = t1Points[i]; >+ } >+ >+ if (!t2IsShared[i]) { >+ outer[1] = t2Points[i]; >+ } >+ } >+ >+ return true; >+ } >+ >+ @Override >+ protected Line[] getAllEdges() { >+ Stack<Line> edges = new Stack<Line>(); >+ >+ for (Polygon t : triangles) { >+ for (Line e : t.getOutlineSegments()) { >+ edges.push(e); >+ } >+ } >+ return edges.toArray(new Line[] {}); > } > > public Rectangle getBounds() { >- throw new UnsupportedOperationException("Not yet implemented."); >+ if (triangles.size() == 0) >+ return null; >+ >+ Rectangle bounds = triangles.get(0).getBounds(); >+ for (int i = 1; i < triangles.size(); i++) >+ bounds.union(triangles.get(i).getBounds()); >+ >+ return bounds; > } > >- public boolean touches(Rectangle r) { >- throw new UnsupportedOperationException("Not yet implemented."); >+ public Ring getCopy() { >+ return new Ring(this); >+ } >+ >+ public Polygon[] getShapes() { >+ return triangles.toArray(new Polygon[] {}); >+ } >+ >+ private Polygon mergeTriangles(Polygon t1, Polygon t2) { >+ Point[] shared = new Point[2], outer = new Point[2]; >+ boolean found = findSharedAndOuterVertices(t1, t2, shared, outer); >+ if (found) { >+ Line outerLink = new Line(outer[0], outer[1]); >+ if (outerLink.contains(shared[0])) { >+ return new Polygon(outer[0], outer[1], shared[1]); >+ } else if (outerLink.contains(shared[1])) { >+ return new Polygon(outer[0], outer[1], shared[0]); >+ } >+ } >+ >+ return null; >+ } >+ >+ private void optimizeTriangles() { >+ for (int i = 0; i < triangles.size(); i++) { >+ Polygon t1 = triangles.get(i); >+ for (int j = i + 1; j < triangles.size(); j++) { >+ Polygon t2 = triangles.get(j); >+ Polygon merge = mergeTriangles(t1, t2); >+ if (merge != null) { >+ triangles.set(i, merge); >+ t1 = merge; >+ triangles.remove(j); >+ j = i; >+ } >+ } >+ } > } > > public Path toPath() { >- throw new UnsupportedOperationException("Not yet implemented."); >+ return getOutline().toPath(); >+ } >+ >+ public Ring getRotatedCCW(Angle angle) { >+ return getCopy().rotateCCW(angle); >+ } >+ >+ public Ring getRotatedCCW(Angle angle, double cx, double cy) { >+ return getCopy().rotateCCW(angle, cx, cy); >+ } >+ >+ public Ring getRotatedCCW(Angle angle, Point center) { >+ return getCopy().rotateCCW(angle, center); >+ } >+ >+ public Ring getRotatedCW(Angle angle) { >+ return getCopy().rotateCW(angle); >+ } >+ >+ public Ring getRotatedCW(Angle angle, double cx, double cy) { >+ return getCopy().rotateCW(angle, cx, cy); >+ } >+ >+ public Ring getRotatedCW(Angle angle, Point center) { >+ return getCopy().rotateCW(angle, center); >+ } >+ >+ public Ring rotateCCW(Angle angle) { >+ Point centroid = getBounds().getCentroid(); >+ return rotateCCW(angle, centroid.x, centroid.y); >+ } >+ >+ public Ring rotateCCW(Angle angle, double cx, double cy) { >+ for (Polygon p : triangles) { >+ p.rotateCCW(angle, cx, cy); >+ } >+ return this; >+ } >+ >+ public Ring rotateCCW(Angle angle, Point center) { >+ return rotateCCW(angle, center.x, center.y); >+ } >+ >+ public Ring rotateCW(Angle angle) { >+ Point centroid = getBounds().getCentroid(); >+ return rotateCW(angle, centroid.x, centroid.y); >+ } >+ >+ public Ring rotateCW(Angle angle, double cx, double cy) { >+ for (Polygon p : triangles) { >+ p.rotateCW(angle, cx, cy); >+ } >+ return this; >+ } >+ >+ public Ring rotateCW(Angle angle, Point center) { >+ return rotateCW(angle, center.x, center.y); >+ } >+ >+ public Ring scale(double factor) { >+ return scale(factor, factor); >+ } >+ >+ public Ring scale(double factor, double cx, double cy) { >+ return scale(factor, factor, cx, cy); >+ } >+ >+ public Ring scale(double factor, Point center) { >+ return scale(factor, factor, center.x, center.y); >+ } >+ >+ public Ring scale(double fx, double fy) { >+ Point centroid = getBounds().getCentroid(); >+ return scale(fx, fy, centroid.x, centroid.y); >+ } >+ >+ public Ring scale(double fx, double fy, double cx, double cy) { >+ for (Polygon p : triangles) { >+ p.scale(fx, fy, cx, cy); >+ } >+ return this; >+ } >+ >+ public Ring scale(double fx, double fy, Point center) { >+ return scale(fx, fy, center.x, center.y); >+ } >+ >+ public Ring getScaled(double factor) { >+ return getCopy().scale(factor); >+ } >+ >+ public Ring getScaled(double factor, double cx, double cy) { >+ return getCopy().scale(factor, cx, cy); >+ } >+ >+ public Ring getScaled(double factor, Point center) { >+ return getCopy().scale(factor, center); >+ } >+ >+ public Ring getScaled(double fx, double fy) { >+ return getCopy().scale(fx, fy); >+ } >+ >+ public Ring getScaled(double fx, double fy, double cx, double cy) { >+ return getCopy().scale(fx, fy, cx, cy); >+ } >+ >+ public Ring getScaled(double fx, double fy, Point center) { >+ return getCopy().scale(fx, fy, center); >+ } >+ >+ public Ring translate(double dx, double dy) { >+ for (Polygon p : triangles) { >+ p.translate(dx, dy); >+ } >+ return this; >+ } >+ >+ public Ring translate(Point d) { >+ return translate(d.x, d.y); >+ } >+ >+ public Ring getTranslated(double dx, double dy) { >+ return getCopy().translate(dx, dy); > } > >- public IGeometry getCopy() { >- throw new UnsupportedOperationException("Not yet implemented."); >+ public Ring getTranslated(Point d) { >+ return getCopy().translate(d.x, d.y); > } > > } >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 028d581..2e975ca 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 >@@ -29,7 +29,8 @@ import org.eclipse.gef4.geometry.utils.PrecisionUtils; > * @author anyssen > */ > public final class RoundedRectangle extends >- AbstractRectangleBasedGeometry<Rectangle> implements IShape { >+ AbstractRectangleBasedGeometry<RoundedRectangle, PolyBezier> implements >+ IShape { > > private static final long serialVersionUID = 1L; > >@@ -85,6 +86,10 @@ public final class RoundedRectangle extends > arcHeight); > } > >+ public boolean contains(IGeometry g) { >+ return CurveUtils.contains(this, g); >+ } >+ > /** > * @see IGeometry#contains(Point) > */ >@@ -126,6 +131,19 @@ public final class RoundedRectangle extends > return false; > } > >+ @Override >+ public boolean equals(Object obj) { >+ if (obj == null || !(obj instanceof RoundedRectangle)) { >+ return false; >+ } >+ RoundedRectangle o = (RoundedRectangle) obj; >+ return PrecisionUtils.equal(x, o.x) && PrecisionUtils.equal(y, o.y) >+ && PrecisionUtils.equal(width, o.width) >+ && PrecisionUtils.equal(height, o.height) >+ && PrecisionUtils.equal(arcWidth, o.arcWidth) >+ && PrecisionUtils.equal(arcHeight, o.arcHeight); >+ } >+ > /** > * Returns the arc height of this {@link RoundedRectangle}, which is the > * height of the arc used to define its rounded corners. >@@ -147,63 +165,81 @@ public final class RoundedRectangle extends > } > > /** >- * Sets the arc height of this {@link RoundedRectangle}, which is the height >- * of the arc used to define its rounded corners. >+ * Returns the bottom edge of this {@link RoundedRectangle}. > * >- * @param arcHeight >- * the new arc height >+ * @return the bottom edge of this {@link RoundedRectangle}. > */ >- public void setArcHeight(double arcHeight) { >- this.arcHeight = arcHeight; >+ public Line getBottom() { >+ return new Line(x + arcWidth, y + height, x + width - arcWidth, y >+ + height); > } > > /** >- * Sets the arc width of this {@link RoundedRectangle}, which is the width >- * of the arc used to define its rounded corners. >+ * Returns the bottom left {@link Arc} of this {@link RoundedRectangle}. > * >- * @param arcWidth >- * the new arc width >+ * @return the bottom left {@link Arc} of this {@link RoundedRectangle}. > */ >- public void setArcWidth(double arcWidth) { >- this.arcWidth = arcWidth; >+ public Arc getBottomLeftArc() { >+ return new Arc(x, y + height - 2 * arcHeight, 2 * arcWidth, >+ 2 * arcHeight, Angle.fromDeg(180), Angle.fromDeg(90)); > } > > /** >- * @see IGeometry#toPath() >+ * Returns the bottom right {@link Arc} of this {@link RoundedRectangle}. >+ * >+ * @return the bottom right {@link Arc} of this {@link RoundedRectangle}. > */ >- public Path toPath() { >- // overwritten to optimize w.r.t. object creation (could otherwise use >- // the segments) >- Path path = new Path(); >- path.moveTo(x, y - arcHeight); >- path.quadTo(x + arcWidth, y, x, y); >- path.lineTo(x + width - arcWidth * 2, y); >- path.quadTo(x + width, y + arcHeight, x + width, y); >- path.lineTo(x + width, y + height - arcHeight * 2); >- path.quadTo(x + width - arcWidth * 2, y + height, x + width, y + width); >- path.lineTo(x + arcWidth, y + height); >- path.quadTo(x, y + height - arcHeight * 2, x, y + height); >- path.close(); >- return path; >+ public Arc getBottomRightArc() { >+ return new Arc(x + width - 2 * arcWidth, y + height - 2 * arcHeight, >+ 2 * arcWidth, 2 * arcHeight, Angle.fromDeg(270), >+ Angle.fromDeg(90)); > } > > /** >- * Returns the top edge of this {@link RoundedRectangle}. >- * >- * @return the top edge of this {@link RoundedRectangle}. >+ * @see IGeometry#getCopy() > */ >- public Line getTop() { >- return new Line(x + arcWidth, y, x + width - arcWidth, y); >+ public RoundedRectangle getCopy() { >+ return new RoundedRectangle(x, y, width, height, arcWidth, arcHeight); > } > > /** >- * Returns the bottom edge of this {@link RoundedRectangle}. >+ * Returns the left edge of this {@link RoundedRectangle}. > * >- * @return the bottom edge of this {@link RoundedRectangle}. >+ * @return the left edge of this {@link RoundedRectangle}. > */ >- public Line getBottom() { >- return new Line(x + arcWidth, y + height - arcHeight, x + width >- - arcWidth, y + height - arcHeight); >+ public Line getLeft() { >+ return new Line(x, y + arcHeight, x, y + height - arcHeight); >+ } >+ >+ public PolyBezier getOutline() { >+ return CurveUtils.getOutline(this); >+ } >+ >+ /** >+ * @see org.eclipse.gef4.geometry.planar.IShape#getOutlineSegments() >+ */ >+ public ICurve[] getOutlineSegments() { >+ // see http://whizkidtech.redprince.net/bezier/circle/kappa/ for details >+ // on the approximation used here >+ return new ICurve[] { >+ CurveUtils.computeEllipticalArcApproximation(x + width - 2 >+ * arcWidth, y, 2 * arcWidth, 2 * arcHeight, >+ Angle.fromDeg(0), Angle.fromDeg(90)), >+ new Line(x + width - arcWidth, y, x + arcWidth, y), >+ CurveUtils.computeEllipticalArcApproximation(x, y, >+ 2 * arcWidth, 2 * arcHeight, Angle.fromDeg(90), >+ Angle.fromDeg(180)), >+ new Line(x, y + arcHeight, x, y + height - arcHeight), >+ CurveUtils.computeEllipticalArcApproximation(x, y + height - 2 >+ * arcHeight, 2 * arcWidth, 2 * arcHeight, >+ Angle.fromDeg(180), Angle.fromDeg(270)), >+ new Line(x + arcWidth, y + height, x + width - arcWidth, y >+ + height), >+ CurveUtils.computeEllipticalArcApproximation(x + width - 2 >+ * arcWidth, y + height - 2 * arcHeight, 2 * arcWidth, >+ 2 * arcHeight, Angle.fromDeg(270), Angle.fromDeg(360)), >+ new Line(x + width, y + height - arcHeight, x + width, y >+ + arcHeight) }; > } > > /** >@@ -216,32 +252,37 @@ public final class RoundedRectangle extends > - arcHeight); > } > >- /** >- * @see org.eclipse.gef4.geometry.planar.IShape#getOutlineSegments() >- */ >- public ICurve[] getOutlineSegments() { >- return new ICurve[] { >- new QuadraticCurve(x, y - arcHeight, x + arcWidth, y, x, y), >- new Line(x, y, x + width - arcWidth * 2, y), >- new QuadraticCurve(x + width - arcWidth * 2, y, x + width, y >- + arcHeight, x + width, y), >- new Line(x + width, y, x + width, y + height - arcHeight * 2), >- new QuadraticCurve(x + width, y + height - arcHeight * 2, x >- + width - arcWidth * 2, y + height, x + width, y >- + width), >- new Line(x + width, y + width, x + arcWidth, y + height), >- new QuadraticCurve(x + arcWidth, y + height, x, y + height >- - arcHeight * 2, x, y + height), >- new Line(x, y + height, x, y - arcHeight) }; >+ public PolyBezier getRotatedCCW(Angle angle) { >+ return getOutline().rotateCCW(angle); >+ } >+ >+ public PolyBezier getRotatedCCW(Angle angle, double cx, double cy) { >+ return getOutline().rotateCCW(angle, cx, cy); >+ } >+ >+ public PolyBezier getRotatedCCW(Angle angle, Point center) { >+ return getOutline().rotateCCW(angle, center); >+ } >+ >+ public PolyBezier getRotatedCW(Angle angle) { >+ return getOutline().rotateCW(angle); >+ } >+ >+ public PolyBezier getRotatedCW(Angle angle, double cx, double cy) { >+ return getOutline().rotateCW(angle, cx, cy); >+ } >+ >+ public PolyBezier getRotatedCW(Angle angle, Point center) { >+ return getOutline().rotateCW(angle, center); > } > > /** >- * Returns the left edge of this {@link RoundedRectangle}. >+ * Returns the top edge of this {@link RoundedRectangle}. > * >- * @return the left edge of this {@link RoundedRectangle}. >+ * @return the top edge of this {@link RoundedRectangle}. > */ >- public Line getLeft() { >- return new Line(x, y + arcHeight, x, y + height - arcHeight); >+ public Line getTop() { >+ return new Line(x + arcWidth, y, x + width - arcWidth, y); > } > > /** >@@ -250,7 +291,7 @@ public final class RoundedRectangle extends > * @return the top left {@link Arc} of this {@link RoundedRectangle}. > */ > public Arc getTopLeftArc() { >- return new Arc(x, y, arcWidth, arcHeight, Angle.fromDeg(90), >+ return new Arc(x, y, 2 * arcWidth, 2 * arcHeight, Angle.fromDeg(90), > Angle.fromDeg(90)); > } > >@@ -260,48 +301,56 @@ public final class RoundedRectangle extends > * @return the top right {@link Arc} of this {@link RoundedRectangle}. > */ > public Arc getTopRightArc() { >- return new Arc(x + width - arcWidth, y, arcWidth, arcHeight, >- Angle.fromDeg(0), Angle.fromDeg(90)); >+ return new Arc(x + width - 2 * arcWidth, y, 2 * arcWidth, >+ 2 * arcHeight, Angle.fromDeg(0), Angle.fromDeg(90)); > } > > /** >- * Returns the bottom left {@link Arc} of this {@link RoundedRectangle}. >+ * Sets the arc height of this {@link RoundedRectangle}, which is the height >+ * of the arc used to define its rounded corners. > * >- * @return the bottom left {@link Arc} of this {@link RoundedRectangle}. >+ * @param arcHeight >+ * the new arc height > */ >- public Arc getBottomLeftArc() { >- return new Arc(x, y + height - arcHeight, arcWidth, arcHeight, >- Angle.fromDeg(180), Angle.fromDeg(90)); >+ public void setArcHeight(double arcHeight) { >+ this.arcHeight = arcHeight; > } > > /** >- * Returns the bottom right {@link Arc} of this {@link RoundedRectangle}. >+ * Sets the arc width of this {@link RoundedRectangle}, which is the width >+ * of the arc used to define its rounded corners. > * >- * @return the bottom right {@link Arc} of this {@link RoundedRectangle}. >+ * @param arcWidth >+ * the new arc width > */ >- public Arc getBottomRightArc() { >- return new Arc(x + width - arcWidth, y + height - arcHeight, arcWidth, >- arcHeight, Angle.fromDeg(270), Angle.fromDeg(90)); >- } >- >- @Override >- public String toString() { >- return "RoundedRectangle: (" + x + ", " + y + ", " + width + ", " + height + ", " + arcWidth + ", " + arcHeight; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ >+ public void setArcWidth(double arcWidth) { >+ this.arcWidth = arcWidth; > } > > /** >- * @see IGeometry#getCopy() >+ * @see IGeometry#toPath() > */ >- public RoundedRectangle getCopy() { >- return new RoundedRectangle(x, y, width, height, arcWidth, arcHeight); >- } >- >- public IPolyCurve getOutline() { >- return CurveUtils.getOutline(this); >+ public Path toPath() { >+ // return CurveUtils.toPath(getOutlineSegments()); >+ // TODO: use cubic curves instead of quadratic curves here! >+ // overwritten to optimize w.r.t. object creation (could otherwise use >+ // the segments) >+ Path path = new Path(); >+ path.moveTo(x, y + arcHeight); >+ path.quadTo(x, y, x + arcWidth, y); >+ path.lineTo(x + width - arcWidth, y); >+ path.quadTo(x + width, y, x + width, y + arcHeight); >+ path.lineTo(x + width, y + height - arcHeight); >+ path.quadTo(x + width, y + height, x + width - arcWidth, y + height); >+ path.lineTo(x + arcWidth, y + height); >+ path.quadTo(x, y + height, x, y + height - arcHeight); >+ path.close(); >+ return path; > } > >- public boolean contains(IGeometry g) { >- return CurveUtils.contains(this, g); >+ @Override >+ public String toString() { >+ return "RoundedRectangle(" + x + ", " + y + ", " + width + ", " + height + ", " + arcWidth + ", " + arcHeight + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ > } > > } >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 >index 4b238ed..5cc50cd 100644 >--- 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 >@@ -184,7 +184,7 @@ public final class Vector3D { > > @Override > public String toString() { >- return "Vector3D (" + x + ", " + y + ", " + z + ")"; >+ return "Vector3D(" + x + ", " + y + ", " + z + ")"; > } > > @Override >@@ -193,4 +193,4 @@ public final class Vector3D { > // comparisons > return 0; > } >-} >\ No newline at end of file >+} >diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/transform/IRotatable.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/transform/IRotatable.java >new file mode 100644 >index 0000000..96413b3 >--- /dev/null >+++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/transform/IRotatable.java >@@ -0,0 +1,134 @@ >+/******************************************************************************* >+ * Copyright (c) 2012 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.transform; >+ >+import org.eclipse.gef4.geometry.Angle; >+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.gef4.geometry.planar.Region; >+import org.eclipse.gef4.geometry.planar.RoundedRectangle; >+ >+/** >+ * <p> >+ * The {@link IRotatable} interface collects the out-of-place rotation short-cut >+ * methods. >+ * </p> >+ * >+ * <p> >+ * Rotation cannot be applied directly to all {@link IGeometry}s. For example, >+ * {@link Rectangle}, {@link Ellipse}, {@link Region} and >+ * {@link RoundedRectangle} cannot be slanted. Therefore, you have to specify >+ * the result type for the rotation methods via a type parameter. >+ * </p> >+ * >+ * <p> >+ * There are two directions of rotation: clock-wise (CW) and counter-clock-wise >+ * (CCW). The individual method names reflect the direction of rotation that is >+ * used. These are the rotation methods: {@link #getRotatedCCW(Angle)}, >+ * {@link #getRotatedCCW(Angle, Point)}, >+ * {@link #getRotatedCCW(Angle, double, double)}, {@link #getRotatedCW(Angle)}, >+ * {@link #getRotatedCW(Angle, Point)}, >+ * {@link #getRotatedCW(Angle, double, double)}. >+ * </p> >+ * >+ * <p> >+ * If you do not specify a {@link Point} to rotate around, the implementation >+ * can appropriately choose one. In most cases, this will be the center >+ * {@link Point} of the rotated object. >+ * </p> >+ * >+ * @param <T> >+ * type of the rotation results >+ */ >+public interface IRotatable<T extends IGeometry> { >+ >+ /** >+ * Rotates the calling object by specified {@link Angle} counter-clock-wise >+ * (CCW) around its center {@link Point}. Does not necessarily return an >+ * object of the same type. >+ * >+ * @param angle >+ * rotation {@link Angle} >+ * @return an {@link IGeometry} representing the result of the rotation >+ */ >+ public T getRotatedCCW(Angle angle); >+ >+ /** >+ * Rotates the calling object by the specified {@link Angle} >+ * counter-clock-wise (CCW) around the specified center {@link Point} (cx, >+ * cy). Does not necessarily return an object of the same type. >+ * >+ * @param angle >+ * rotation {@link Angle} >+ * @param cx >+ * x-coordinate of the relative {@link Point} for the rotation >+ * @param cy >+ * y-coordinate of the relative {@link Point} for the rotation >+ * @return an {@link IGeometry} representing the result of the rotation >+ */ >+ public T getRotatedCCW(Angle angle, double cx, double cy); >+ >+ /** >+ * Rotates the calling object by the specified {@link Angle} >+ * counter-clock-wise (CCW) around the specified center {@link Point}. Does >+ * not necessarily return an object of the same type. >+ * >+ * @param angle >+ * rotation {@link Angle} >+ * @param center >+ * relative {@link Point} for the rotation >+ * @return an {@link IGeometry} representing the result of the rotation >+ */ >+ public T getRotatedCCW(Angle angle, Point center); >+ >+ /** >+ * Rotates the calling object by specified {@link Angle} clock-wise (CW) >+ * around its center {@link Point}. Does not necessarily return an object of >+ * the same type. >+ * >+ * @param angle >+ * rotation {@link Angle} >+ * @return an {@link IGeometry} representing the result of the rotation >+ */ >+ public T getRotatedCW(Angle angle); >+ >+ /** >+ * Rotates the calling object by the specified {@link Angle} clock-wise (CW) >+ * around the specified center {@link Point} (cx, cy). Does not necessarily >+ * return an object of the same type. >+ * >+ * @param angle >+ * rotation {@link Angle} >+ * @param cx >+ * x-coordinate of the relative {@link Point} for the rotation >+ * @param cy >+ * y-coordinate of the relative {@link Point} for the rotation >+ * @return an {@link IGeometry} representing the result of the rotation >+ */ >+ public T getRotatedCW(Angle angle, double cx, double cy); >+ >+ /** >+ * Rotates the calling object by the specified {@link Angle} clock-wise (CW) >+ * around the specified center {@link Point}. Does not necessarily return an >+ * object of the same type. >+ * >+ * @param angle >+ * rotation {@link Angle} >+ * @param center >+ * relative {@link Point} for the rotation >+ * @return an {@link IGeometry} representing the result of the rotation >+ */ >+ public T getRotatedCW(Angle angle, Point center); >+ >+} >diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/transform/IScalable.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/transform/IScalable.java >new file mode 100644 >index 0000000..9200f0a >--- /dev/null >+++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/transform/IScalable.java >@@ -0,0 +1,207 @@ >+/******************************************************************************* >+ * Copyright (c) 2012 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.transform; >+ >+import org.eclipse.gef4.geometry.Point; >+import org.eclipse.gef4.geometry.planar.IGeometry; >+ >+/** >+ * <p> >+ * The {@link IScalable} interface collects all scaling short-cut methods. >+ * </p> >+ * >+ * <p> >+ * The {@link #scale(double)}, {@link #scale(double, double)}, >+ * {@link #scale(double, Point)}, {@link #scale(double, double, double)}, >+ * {@link #scale(double, double, Point)} and >+ * {@link #scale(double, double, double, double)} methods are directly applied >+ * to the calling object. They scale it by the given factor(s) around the given >+ * {@link Point} or an appropriate default. >+ * </p> >+ * >+ * <p> >+ * On the other hand, the {@link #getScaled(double)}, >+ * {@link #getScaled(double, double)}, {@link #getScaled(double, Point)}, >+ * {@link #getScaled(double, double, double)}, >+ * {@link #getScaled(double, double, Point)} and >+ * {@link #getScaled(double, double, double, double)} methods are applied to a >+ * copy of the calling object. >+ * </p> >+ * >+ * <p> >+ * If you do not specify the relative {@link Point} for the scaling, the >+ * implementation will appropriately choose one. In most cases, this will be the >+ * center of the scaled object. >+ * </p> >+ * >+ * @param <T> >+ * the implementing type >+ */ >+public interface IScalable<T extends IGeometry> { >+ >+ /** >+ * Scales the calling object by the given factor relative to its center >+ * {@link Point}. >+ * >+ * @param factor >+ * scale-factor >+ * @return <code>this</code> for convenience >+ */ >+ public T scale(double factor); >+ >+ /** >+ * Scales the calling object by the given factor relative to the given >+ * center {@link Point} (cx, cy). >+ * >+ * @param factor >+ * scale-factor >+ * @param cx >+ * x-coordinate of the relative {@link Point} for the scaling >+ * @param cy >+ * y-coordinate of the relative {@link Point} for the scaling >+ * @return <code>this</code> for convenience >+ */ >+ public T scale(double factor, double cx, double cy); >+ >+ /** >+ * Scales the calling object by the given factor relative to the given >+ * center {@link Point}. >+ * >+ * @param factor >+ * scale-factor >+ * @param center >+ * relative {@link Point} for the scaling >+ * @return <code>this</code> for convenience >+ */ >+ public T scale(double factor, Point center); >+ >+ /** >+ * Scales the calling object by the given factors relative to the given >+ * center {@link Point}. >+ * >+ * @param fx >+ * x-scale-factor >+ * @param fy >+ * y-scale-factor >+ * @return <code>this</code> for convenience >+ */ >+ public T scale(double fx, double fy); >+ >+ /** >+ * Scales the calling object by the given factors relative to the given >+ * center {@link Point} (cx, cy). >+ * >+ * @param fx >+ * x-scale-factor >+ * @param fy >+ * y-scale-factor >+ * @param cx >+ * x-coordinate of the relative {@link Point} for the scaling >+ * @param cy >+ * y-coordinate of the relative {@link Point} for the scaling >+ * @return <code>this</code> for convenience >+ */ >+ public T scale(double fx, double fy, double cx, double cy); >+ >+ /** >+ * Scales the calling object by the given factors relative to the given >+ * center {@link Point}. >+ * >+ * @param fx >+ * x-scale-factor >+ * @param fy >+ * y-scale-factor >+ * @param center >+ * relative {@link Point} for the scaling >+ * @return <code>this</code> for convenience >+ */ >+ public T scale(double fx, double fy, Point center); >+ >+ /** >+ * Scales a copy of the calling object by the given factor relative to its >+ * center {@link Point}. >+ * >+ * @param factor >+ * scale-factor >+ * @return the new, scaled object >+ */ >+ public T getScaled(double factor); >+ >+ /** >+ * Scales a copy of the calling object by the given factor relative to the >+ * given center {@link Point} (cx, cy). >+ * >+ * @param factor >+ * scale-factor >+ * @param cx >+ * x-coordinate of the relative {@link Point} for the scaling >+ * @param cy >+ * y-coordinate of the relative {@link Point} for the scaling >+ * @return the new, scaled object >+ */ >+ public T getScaled(double factor, double cx, double cy); >+ >+ /** >+ * Scales a copy of the calling object by the given factor relative to the >+ * given center {@link Point}. >+ * >+ * @param factor >+ * scale-factor >+ * @param center >+ * relative {@link Point} for the scaling >+ * @return the new, scaled object >+ */ >+ public T getScaled(double factor, Point center); >+ >+ /** >+ * Scales a copy of the calling object by the given factors relative to its >+ * center {@link Point}. >+ * >+ * @param fx >+ * x-scale-factor >+ * @param fy >+ * y-scale-factor >+ * @return the new, scaled object >+ */ >+ public T getScaled(double fx, double fy); >+ >+ /** >+ * Scales a copy of the calling object by the given factors relative to the >+ * given center {@link Point} (cx, cy). >+ * >+ * @param fx >+ * x-scale-factor >+ * @param fy >+ * y-scale-factor >+ * @param cx >+ * x-coordinate of the relative {@link Point} for the scaling >+ * @param cy >+ * y-coordinate of the relative {@link Point} for the scaling >+ * @return the new, scaled object >+ */ >+ public T getScaled(double fx, double fy, double cx, double cy); >+ >+ /** >+ * Scales a copy of the calling object by the given factors relative to the >+ * given center {@link Point}. >+ * >+ * @param fx >+ * x-scale-factor >+ * @param fy >+ * y-scale-factor >+ * @param center >+ * relative {@link Point} for the scaling >+ * @return the new, scaled object >+ */ >+ public T getScaled(double fx, double fy, Point center); >+ >+} >diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/transform/ITranslatable.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/transform/ITranslatable.java >new file mode 100644 >index 0000000..063301b >--- /dev/null >+++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/transform/ITranslatable.java >@@ -0,0 +1,81 @@ >+/******************************************************************************* >+ * Copyright (c) 2012 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.transform; >+ >+import org.eclipse.gef4.geometry.Point; >+import org.eclipse.gef4.geometry.planar.IGeometry; >+ >+/** >+ * <p> >+ * The {@link ITranslatable} interface collects all translation short-cut >+ * methods. >+ * </p> >+ * >+ * <p> >+ * Translation can be applied directly on an object via the >+ * {@link #translate(Point)} and {@link #translate(double, double)} methods. >+ * They return the scaled, calling object for convenience. >+ * </p> >+ * >+ * <p> >+ * On the other hand, the {@link #getTranslated(Point)} and >+ * {@link #getTranslated(double, double)} methods create a translated copy of >+ * the original object. >+ * </p> >+ * >+ * @param <T> >+ * the implementing type >+ */ >+public interface ITranslatable<T extends IGeometry> { >+ >+ /** >+ * Translates the object by the given values in x and y direction. >+ * >+ * @param dx >+ * x-translation >+ * @param dy >+ * y-translation >+ * @return <code>this</code> for convenience >+ */ >+ public T translate(double dx, double dy); >+ >+ /** >+ * Translates the object by the given {@link Point}. >+ * >+ * @param d >+ * translation {@link Point} >+ * @return <code>this</code> for convenience >+ */ >+ public T translate(Point d); >+ >+ /** >+ * Translates a copy of this object by the given values in x and y >+ * direction. >+ * >+ * @param dx >+ * x-translation >+ * @param dy >+ * y-translation >+ * @return a new, translated object >+ */ >+ public T getTranslated(double dx, double dy); >+ >+ /** >+ * Translates a copy of this object by the given {@link Point}. >+ * >+ * @param d >+ * translation {@link Point} >+ * @return a new, translated object >+ */ >+ public T getTranslated(Point d); >+ >+} >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 4605a63..8115b18 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 >@@ -17,17 +17,22 @@ import java.util.Comparator; > import java.util.HashSet; > import java.util.Set; > >+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.Arc; > import org.eclipse.gef4.geometry.planar.BezierCurve; > import org.eclipse.gef4.geometry.planar.BezierCurve.IntervalPair; >+import org.eclipse.gef4.geometry.planar.CubicCurve; > 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.Path; > import org.eclipse.gef4.geometry.planar.PolyBezier; >+import org.eclipse.gef4.geometry.planar.QuadraticCurve; > > /** > * The {@link CurveUtils} class provides functionality that can be used for >@@ -259,12 +264,7 @@ public class CurveUtils { > for (ICurve segC : shape.getOutlineSegments()) { > for (BezierCurve seg : segC.toBezier()) { > Set<Point> inters = new HashSet<Point>(); >- Set<IntervalPair> 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()); >- } >+ c.getIntersectionIntervalPairs(seg, inters); > for (Point poi : inters) { > intersectionParams.add(c.getParameterAt(poi)); > } >@@ -278,7 +278,7 @@ public class CurveUtils { > * > * 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. >+ * if the Bezier curve has a self intersection at that point. > */ > if (intersectionParams.size() <= 1) { > return true; >@@ -305,6 +305,155 @@ public class CurveUtils { > } > > /** >+ * TODO: generalize the contains() method for IShape and IPolyShape. >+ * >+ * @param polyShape >+ * @param c >+ * @return <code>true</code> if the {@link BezierCurve} is contained by the >+ * {@link IPolyShape}, otherwise <code>false</code> >+ */ >+ public static boolean contains(IPolyShape polyShape, BezierCurve c) { >+ if (!(polyShape.contains(c.getP1()) && polyShape.contains(c.getP2()))) { >+ return false; >+ } >+ >+ Set<Double> intersectionParams = new HashSet<Double>(); >+ >+ for (ICurve segC : polyShape.getOutlineSegments()) { >+ for (BezierCurve seg : segC.toBezier()) { >+ Set<Point> inters = new HashSet<Point>(); >+ Set<IntervalPair> ips = c.getIntersectionIntervalPairs(seg, >+ inters); >+ for (IntervalPair ip : ips) { >+ intersectionParams.add(ip.p == c ? ip.pi.getMid() : ip.qi >+ .getMid()); >+ } >+ for (Point poi : inters) { >+ intersectionParams.add(c.getParameterAt(poi)); >+ } >+ } >+ } >+ >+ /* >+ * Start and end point of the curve are guaranteed to lie inside the >+ * IPolyShape. If the curve would not be contained by the shape, at >+ * least two intersections could be found. >+ * >+ * 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 intersection at that point. >+ */ >+ if (intersectionParams.size() <= 1) { >+ return true; >+ } >+ >+ Double[] poiParams = intersectionParams.toArray(new Double[] {}); >+ Arrays.sort(poiParams, new Comparator<Double>() { >+ public int compare(Double t, Double u) { >+ double d = t - u; >+ return d < 0 ? -1 : d > 0 ? 1 : 0; >+ } >+ }); >+ >+ // check the points between the intersections for containment >+ if (!polyShape.contains(c.get(poiParams[0] / 2))) { >+ return false; >+ } >+ for (int i = 0; i < poiParams.length - 1; i++) { >+ if (!polyShape.contains(c >+ .get((poiParams[i] + poiParams[i + 1]) / 2))) { >+ return false; >+ } >+ } >+ return polyShape.contains(c >+ .get((poiParams[poiParams.length - 1] + 1) / 2)); >+ } >+ >+ /** >+ * Checks if the given {@link ICurve} is contained by the given >+ * {@link IPolyShape}. >+ * >+ * @param ps >+ * @param c >+ * @return <code>true</code> if the {@link ICurve} is contained by the >+ * {@link IPolyShape}, otherwise <code>false</code> >+ */ >+ public static boolean contains(IPolyShape ps, ICurve c) { >+ for (BezierCurve bc : c.toBezier()) >+ if (!contains(ps, bc)) >+ return false; >+ return true; >+ } >+ >+ /** >+ * Checks if the {@link IShape} is contained by the {@link IPolyShape}. >+ * >+ * @param ps >+ * @param s >+ * @return <code>true</code> if the {@link IShape} is contained by the >+ * {@link IPolyShape}, otherwise <code>false</code> >+ */ >+ public static boolean contains(IPolyShape ps, IShape s) { >+ for (ICurve c : s.getOutlineSegments()) >+ if (!contains(ps, c)) >+ return false; >+ return true; >+ } >+ >+ /** >+ * Checks if the {@link IPolyCurve} is contained by the {@link IPolyShape}. >+ * >+ * @param ps >+ * @param pc >+ * @return <code>true</code> if the {@link IPolyCurve} is contained by the >+ * {@link IPolyShape}, otherwise <code>false</code> >+ */ >+ public static boolean contains(IPolyShape ps, IPolyCurve pc) { >+ for (ICurve c : pc.getCurves()) >+ if (!contains(ps, c)) >+ return false; >+ return true; >+ } >+ >+ /** >+ * Checks if the second {@link IPolyShape} is contained by the first >+ * {@link IPolyShape}. >+ * >+ * @param ps >+ * @param ps2 >+ * @return <code>true</code> if the second {@link IPolyShape} is contained >+ * by the first {@link IPolyShape}, otherwise <code>false</code> >+ */ >+ public static boolean contains(IPolyShape ps, IPolyShape ps2) { >+ for (IShape s : ps2.getShapes()) >+ if (!contains(ps, s)) >+ return false; >+ return true; >+ } >+ >+ /** >+ * Checks if the {@link IGeometry} is contained by the {@link IPolyShape}. >+ * >+ * @param ps >+ * @param g >+ * @return <code>true</code> if the {@link IGeometry} is contained by the >+ * {@link IPolyShape}, otherwise <code>false</code> >+ */ >+ public static boolean contains(IPolyShape ps, IGeometry g) { >+ if (g instanceof ICurve) { >+ return contains(ps, (ICurve) g); >+ } else if (g instanceof IShape) { >+ return contains(ps, (IShape) g); >+ } else if (g instanceof IPolyCurve) { >+ return contains(ps, (IPolyCurve) g); >+ } else if (g instanceof IPolyShape) { >+ return contains(ps, (IPolyShape) g); >+ } else { >+ throw new UnsupportedOperationException("Not yet implemented."); >+ } >+ } >+ >+ /** > * Returns <code>true</code> if the given {@link IShape} fully contains the > * given {@link ICurve}. Otherwise, <code>false</code> is returned. A > * {@link ICurve} is contained by a {@link IShape} if the {@link ICurve}'s >@@ -457,7 +606,7 @@ public class CurveUtils { > if (geom1 instanceof IShape) { > return contains((IShape) geom1, geom2); > } else if (geom1 instanceof IPolyShape) { >- throw new UnsupportedOperationException("Not yet implemented."); >+ return contains((IPolyShape) geom1, geom2); > } else { > return false; > } >@@ -487,4 +636,102 @@ public class CurveUtils { > > return new PolyBezier(beziers.toArray(new BezierCurve[] {})); > } >+ >+ /** >+ * Builds up a {@link Path} from the given {@link ICurve}s. Only >+ * {@link Line}, {@link QuadraticCurve} and {@link CubicCurve} objects can >+ * be integrated into the constructed {@link Path}. >+ * >+ * @param curves >+ * @return a {@link Path} representing the given {@link ICurve}s >+ */ >+ public static final Path toPath(ICurve... curves) { >+ Path p = new Path(); >+ for (int i = 0; i < curves.length; i++) { >+ if (i == 0) { >+ p.moveTo(curves[i].getX1(), curves[i].getY1()); >+ } >+ ICurve c = curves[i]; >+ if (c instanceof Line) { >+ p.lineTo(c.getX2(), c.getY2()); >+ } else if (c instanceof QuadraticCurve) { >+ p.quadTo(((QuadraticCurve) c).getCtrlX(), >+ ((QuadraticCurve) c).getCtrlY(), c.getX2(), c.getY2()); >+ } else if (c instanceof CubicCurve) { >+ p.curveTo(((CubicCurve) c).getCtrlX1(), >+ ((CubicCurve) c).getCtrlY1(), >+ ((CubicCurve) c).getCtrlX2(), >+ ((CubicCurve) c).getCtrlY2(), ((CubicCurve) c).getX2(), >+ ((CubicCurve) c).getY2()); >+ } else { >+ throw new UnsupportedOperationException( >+ "This type of ICurve is not yet implemented."); >+ } >+ } >+ return p; >+ } >+ >+ /** >+ * <p> >+ * Computes a {@link CubicCurve} that approximates the elliptical >+ * {@link Arc} given by the location, the width, and the height of the >+ * implied ellipse and the start and end {@link Angle}s of the arc. >+ * </p> >+ * >+ * <p> >+ * The given start and end {@link Angle}s may not span an {@link Angle} of >+ * more than 90 degrees. >+ * </p> >+ * >+ * @param x >+ * left coordinate value of the aforementioned ellipse >+ * @param y >+ * top coordinate value of the aforementioned ellipse >+ * @param width >+ * width of the aforementioned ellipse >+ * @param height >+ * height of the aforementioned ellipse >+ * @param start >+ * start angle (in radiant) of the elliptical arc >+ * @param end >+ * end angle (in radiant) of the elliptical arc >+ * @return {@link CubicCurve} approximating the determinated elliptical arc >+ */ >+ public static CubicCurve computeEllipticalArcApproximation(double x, >+ double y, double width, double height, Angle start, Angle end) { >+ // TODO: verify that the following test is valid >+ if (!PrecisionUtils.smallerEqual(end.getAdded(start.getOppositeFull()) >+ .deg(), 90)) { >+ throw new IllegalArgumentException( >+ "Only angular extents of up to 90 degrees are allowed."); >+ } >+ >+ // compute major and minor axis length >+ double a = width / 2; >+ double b = height / 2; >+ >+ double srad = start.rad(); >+ double erad = end.rad(); >+ >+ // calculate start and end points of the arc from start to end >+ Point startPoint = new Point(x + a + a * Math.cos(srad), y + b - b >+ * Math.sin(srad)); >+ Point endPoint = new Point(x + a + a * Math.cos(erad), y + b - b >+ * Math.sin(erad)); >+ >+ // approximation by cubic Bezier according to approximation provided in: >+ // http://www.spaceroots.org/documents/ellipse/elliptical-arc.pdf >+ double t = Math.tan((erad - srad) / 2); >+ double alpha = Math.sin(erad - srad) >+ * (Math.sqrt(4.0d + 3.0d * t * t) - 1) / 3; >+ Point controlPoint1 = new Point(startPoint.x + alpha * -a >+ * Math.sin(srad), startPoint.y - alpha * b * Math.cos(srad)); >+ Point controlPoint2 = new Point(endPoint.x - alpha * -a >+ * Math.sin(erad), endPoint.y + alpha * b * Math.cos(erad)); >+ >+ Point[] points = new Point[] { startPoint, controlPoint1, >+ controlPoint2, endPoint }; >+ return new CubicCurve(points); >+ } >+ > } >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 5911ef9..e373eef 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 >@@ -16,8 +16,10 @@ import java.util.ArrayList; > import java.util.Arrays; > import java.util.Comparator; > >+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.planar.Line; > import org.eclipse.gef4.geometry.planar.Polygon; > import org.eclipse.gef4.geometry.planar.Polyline; >@@ -32,13 +34,15 @@ import org.eclipse.gef4.geometry.planar.Rectangle; > public class PointListUtils { > > /** >- * Compares two array of {@link Point} for equality. >+ * Compares two arrays of {@link Point} for equality. >+ * >+ * TODO: What is the benefit over using Arrays.equals()? > * > * @param p1 > * the first array of points to compare > * @param p2 > * the second array of points to compare >- * @return <code>true</code> in case both arrays are of the same lenght and >+ * @return <code>true</code> in case both arrays are of the same length and > * for each index <code>i</code> it holds that <code>p1[i]</code> > * equals <code>p2[i]</code>, <code>false</code> otherwise > */ >@@ -55,6 +59,29 @@ public class PointListUtils { > } > > /** >+ * Compares two arrays of {@link Point} for reverse equality, i.e. if one >+ * array is the reverse of the other array. >+ * >+ * @param p1 >+ * the first array of {@link Point} to compare >+ * @param p2 >+ * the second array of {@link Point} to compare >+ * @return <code>true</code> in case one array is the reverse of the other >+ * array, <code>false</code> otherwise >+ */ >+ public static boolean equalsReverse(Point[] p1, Point[] p2) { >+ if (p1.length != p2.length) { >+ return false; >+ } >+ for (int i = 0; i < p1.length; i++) { >+ if (!p1[i].equals(p2[p1.length - i - 1])) { >+ return false; >+ } >+ } >+ return true; >+ } >+ >+ /** > * Returns the smallest {@link Rectangle} that encloses all {@link Point}s > * in the given sequence. Note that the right and bottom borders of a > * {@link Rectangle} are regarded as being part of the {@link Rectangle}. >@@ -323,4 +350,99 @@ public class PointListUtils { > return points; > } > >+ /** >+ * Computes the centroid of the given {@link Point}s. The centroid is the >+ * "center of gravity", i.e. assuming the {@link Polygon} spanned by the >+ * {@link Point}s is made of a material of constant density, it will be in a >+ * balanced state, if you put it on a pin that is placed exactly on its >+ * centroid. >+ * >+ * @param points >+ * @return the center {@link Point} (or centroid) of the given {@link Point} >+ * s >+ */ >+ public static Point computeCentroid(Point... points) { >+ if (points.length == 0) { >+ return null; >+ } else if (points.length == 1) { >+ return points[0].getCopy(); >+ } >+ >+ double cx = 0, cy = 0, a, sa = 0; >+ for (int i = 0; i < points.length - 1; i++) { >+ a = points[i].x * points[i + 1].y - points[i].y * points[i + 1].x; >+ sa += a; >+ cx += (points[i].x + points[i + 1].x) * a; >+ cy += (points[i].y + points[i + 1].y) * a; >+ } >+ >+ // closing segment >+ a = points[points.length - 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)); >+ } >+ >+ /** >+ * Rotates (in-place) the given {@link Point}s counter-clock-wise (CCW) by >+ * the specified {@link Angle} around the given center {@link Point}. >+ * >+ * @param points >+ * @param angle >+ * @param cx >+ * @param cy >+ */ >+ public static void rotateCCW(Point[] points, Angle angle, double cx, >+ double cy) { >+ translate(points, -cx, -cy); >+ for (Point p : points) { >+ Point np = new Vector(p).rotateCCW(angle).toPoint(); >+ p.x = np.x; >+ p.y = np.y; >+ } >+ translate(points, cx, cy); >+ } >+ >+ /** >+ * Rotates (in-place) the given {@link Point}s clock-wise (CW) by the >+ * specified {@link Angle} around the given center {@link Point}. >+ * >+ * @param points >+ * @param angle >+ * @param cx >+ * @param cy >+ */ >+ public static void rotateCW(Point[] points, Angle angle, double cx, >+ double cy) { >+ translate(points, -cx, -cy); >+ for (Point p : points) { >+ Point np = new Vector(p).rotateCW(angle).toPoint(); >+ p.x = np.x; >+ p.y = np.y; >+ } >+ translate(points, cx, cy); >+ } >+ >+ /** >+ * Scales the given array of {@link Point}s by the given x and y scale >+ * factors around the given center {@link Point} (cx, cy). >+ * >+ * @param points >+ * @param fx >+ * @param fy >+ * @param cx >+ * @param cy >+ */ >+ public static void scale(Point[] points, double fx, double fy, double cx, >+ double cy) { >+ translate(points, -cx, -cy); >+ for (Point p : points) { >+ p.scale(fx, fy); >+ } >+ translate(points, cx, cy); >+ } >+ > } >diff --git a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/utils/PolynomCalculationUtils.java b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/utils/PolynomCalculationUtils.java >index 452fded..a936b33 100644 >--- a/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/utils/PolynomCalculationUtils.java >+++ b/org.eclipse.gef4.geometry/src/org/eclipse/gef4/geometry/utils/PolynomCalculationUtils.java >@@ -80,8 +80,8 @@ public final class PolynomCalculationUtils { > public static final double[] getCubicRoots(double A, double B, double C, > double D) { > // TODO: use an algorithm that abstracts the polynom's order. A >- // possibility would be to use the CurveUtils$BezierCurve#contains(Point >- // p) method. >+ // possibility would be to use the BezierCurve#contains(Point p) >+ // method. > > if (A == 0) { > return getQuadraticRoots(B, C, D); >-- >1.7.4.1 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Flags:
nyssen
:
iplog+
Actions:
View
|
Diff
Attachments on
bug 355997
:
202484
|
202501
|
205718
|
206845
|
206852
|
207479
|
207484
|
208179
|
208923
|
212739
|
212771
|
212775
|
212776
|
213615
|
216278
| 216482 |
217706