| author | Matthias Wienand | 2012-06-13 18:35:27 (EDT) |
|---|---|---|
| committer | anyssen | 2012-06-13 18:35:27 (EDT) |
| commit | 3843de88343b054c74990d9d76c274762e762acb (patch) (side-by-side diff) | |
| tree | 4ee44832d1ef0be999ae5b7b9e7a66d6f61820f9 | |
| parent | c97605d908e5058e0a3c6f19af73cd2453d6d731 (diff) | |
| download | org.eclipse.gef4-3843de88343b054c74990d9d76c274762e762acb.zip org.eclipse.gef4-3843de88343b054c74990d9d76c274762e762acb.tar.gz org.eclipse.gef4-3843de88343b054c74990d9d76c274762e762acb.tar.bz2 | |
[355997] Finalized IPolyShape implementations and added transformation
interfaces (CQ 6545).
62 files changed, 6904 insertions, 1608 deletions
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 Binary files differdeleted file mode 100644 index 33e933d..0000000 --- a/org.eclipse.gef4.geometry.doc/reference/image_src/IGeometry_Planar_Abstractions.PNG +++ b/dev/null 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 Binary files differdeleted file mode 100644 index 089d141..0000000 --- a/org.eclipse.gef4.geometry.doc/reference/image_src/IGeometry_Planar_Overview.PNG +++ b/dev/null 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 --- a/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 --- a/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/intersection/BezierApproximationExample.java b/org.eclipse.gef4.geometry.examples/src/org/eclipse/gef4/geometry/examples/demos/BezierApproximationExample.java index 2bf2bb9..c0beca9 100644 --- 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/demos/BezierApproximationExample.java @@ -9,9 +9,10 @@ * Matthias Wienand (itemis AG) - initial API and implementation * *******************************************************************************/ -package org.eclipse.gef4.geometry.examples.intersection; +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; @@ -33,13 +34,13 @@ public class BezierApproximationExample extends AbstractIntersectionExample { 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)); + 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 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/demos/ConvexHullExample.java index 4e317f6..67955f7 100644 --- 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/demos/ConvexHullExample.java @@ -9,9 +9,11 @@ * Matthias Wienand (itemis AG) - initial API and implementation * *******************************************************************************/ -package org.eclipse.gef4.geometry.examples.intersection; +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; 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/demos/CubicCurveDeCasteljauExample.java index 380f1b2..aa9eda0 100644 --- 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/demos/CubicCurveDeCasteljauExample.java @@ -9,9 +9,11 @@ * Matthias Wienand (itemis AG) - initial API and implementation * *******************************************************************************/ -package org.eclipse.gef4.geometry.examples.intersection; +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; 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/demos/RegionExample.java index 4e317f6..a8a9795 100644 --- 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/demos/RegionExample.java @@ -9,23 +9,26 @@ * Matthias Wienand (itemis AG) - initial API and implementation * *******************************************************************************/ -package org.eclipse.gef4.geometry.examples.intersection; +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.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 ConvexHullExample extends AbstractIntersectionExample { +public class RegionExample extends AbstractIntersectionExample { public static void main(String[] args) { - new ConvexHullExample("Convex Hull Example"); + new RegionExample("Region Example"); } - public ConvexHullExample(String title) { + public RegionExample(String title) { super(title); } @@ -34,26 +37,41 @@ public class ConvexHullExample extends AbstractIntersectionExample { @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)); + addControlPoint(new Point(200, 200)); + + addControlPoint(new Point(150, 150)); + addControlPoint(new Point(250, 250)); } @Override - public IGeometry createGeometry() { - Polygon convexHull = new Polygon( - PointListUtils.getConvexHull(getControlPoints())); - return convexHull; + 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) { - Polygon convexHull = (Polygon) createGeometry(); - gc.drawPath(new org.eclipse.swt.graphics.Path(Display - .getCurrent(), convexHull.toPath().toSWTPathData())); + 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); } }; } 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 --- a/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 --- a/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 --- a/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/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 --- a/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 --- a/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 --- a/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 --- a/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 --- a/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 --- a/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 --- a/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 --- a/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 --- a/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 --- a/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 --- a/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); |

