Revision as of 02:59, 2 July 2012

Note to non-wiki readers: This documentation is generated from the Eclipse wiki - if you have corrections or additions it would be awesome if you added them in the original wiki page.

Introduction

This is the reference documentation of the GEF 4 Geometry API. You can use this component independent of the other GEF 4 components. The Geometry API provides classes to store geometric objects and to do geometric computations on those objects. It is developed to be intuitively usable. Nonetheless, it is of no harm to know the underlying design decisions which are explained in the following chapters.

To find your way, this navigation is organized into packages (org.eclipse.gef4.geometry.*) and the associated classes. Because of its size, the planar package is subdivided into the different interface implementations. The planar package collects classes to do computations on two-dimensional geometric objects. The euclidean package provides core abstractions (Vector, Straight, and Angle) to support calculations within two-dimensional Euclidean space. The projective package provides classes (Vector3D, Straight3D) to represent euclidean elements in the projective plane. The convert package contains helper classes to transfer data from AWT/SWT/Geometry to one another. The transform package presents the AffineTransform class to apply affine transformations to geometric objects.

Planar Geometry

package: org.eclipse.gef4.geometry.planar

The planar package collects classes to do computations on two-dimensional geometric objects. To structure the individual classes, they are implementing different interfaces which you will get to know in this chapter.

This diagram depicts the interface hierarchy which underlies the individual geometry classes. It classifies the geometry classes mainly into either being ICurves or IShapes. An ICurve is a one dimensional geometry, i.e. the result that you get by drawing a continuous line with a pencil. It has a start and an end point and you can approximate it by a series of BezierCurves. On the other hand, an IShape is a two dimensional geometry, i.e. it continuously encloses a region on the drawing area, without holes. You can retrieve the outline of an IShape, which is an IPolyCurve, a special case of an ICurve. It defines a curve that is composed of multiple connected ICurves and its purpose is to be able to operate on them as a whole. Similarly and especially important for clipping is another set of planar geometries, the IPolyShapes. Other than the relationship between ICurve and IPolyCurve, an IPolyShape is not an IShape. An IPolyShape is a (possibly) non-continuous set of IShapes. An example for an IPolyShape is the Region. A Region is the area that results from composing multiple Rectangles. A Ring accordingly is an area that results from composing multiple Polygons. It corresponds to the SWT Region.

This interface hierarchy structures the geometry API. It describes how the individual classes are related to each other and how to transfer objects of one type to a number of objects of other types, which is the most important part of the geometry API.

The most general type in the hierarchy is the Path, because every geometric object can be transfered into it. Unfortunately, the Path is incompatible to the rest of the API in that it does not implement the different interfaces, it does not ensure a certain precision for the results of its test and manipulation methods, and it cannot be transferred back into compatible objects.

As you can see in this diagram, an ICurve can be approximated by a number of BezierCurves using the toBezier() method. The outline of an IShape can be retrieved using its getOutline() method. Additionally, you can split an IShape into a number of ICurves -- which form the outline -- using the getOutlineSegments() method. IPolyShape provides a getShapes() method to get the individual IShapes that are combined by the IPolyShape. It does provide a getOutlineSegments() method, too, which is used to split the IPolyShape into several ICurves. These transfer methods allow the decomposition of any geometric object into a bunch of BezierCurves:

An important part of a geometry API in general, is the possibility to test the relationship of two geometric objects. The GEF 4 Geometry API provides four methods that perform relation tests. Universally usable is the touches() method for planar objects. It tests if two objects have at least one point in common. Additionally, ICurves can be tested for points of intersection using the intersects() method and for an overlap using the overlaps() method, among each other. An IShape provides a contains() method to test if it fully contains a given planar object. Moreover, the point test is available for every geometric object. It tests if a given Point is incidental to the particular object. Supplementary to the intersects() test, a getIntersections() method is offered among ICurves. BezierCurves do also facilitate the extraction of overlapping segments via the getOverlap() method.

Noticeably, the use of interfaces unifies similar operations on different types. Therefore, the fundamental interfaces (ICurve, IShape, etc.) are complemented by three transformation interfaces.

You can either transform your geometric objects via instances of the AffineTransform class, or by using the short-cut methods provided by the ITranslatable, IScalable, and IRotatable interfaces. Transformations can either be directly applied to an object, modifying the object in-place, or to a copy of the original object. This distinction is represented by the names of the short-cut methods. All names starting with 'get' are applied to a copy of the original object. The other methods modify the object in-place.

Translating an object means moving the object. You can move an object in x- and y-direction. Scaling an object means resizing the object. You can individually scale the object in x- and y-direction. Additionally, scaling requires a relative Point to scale to/away from. If you omit this Point, the scaling method will appropriately choose the relative Point. Normally, this will be the center Point of the geometric object that you want to scale. Rotation is special in that not all geometric objects can be rotated in-place. Rectangles, for example, are always parallel to the x- and y-axes. That's why the IRotatable interface does only include the getRotated*() short-cut methods which are not directly applied. However, some geometric objects do provide in-place rotation methods. As with scaling, rotation is performed around a relative Point. If you omit this Point, the rotation method will appropriately choose it. Normally, this will be the center Point of the geometric object that you want to rotate.

Augmenting the interface hierarchy, all concrete classes are based on abstract geometry classes, depending on the type of geometry used for constructing the objects (i.e. Ellipse is an AbstractRectangleBasedGeometry, because it is constructed by means of a Rectangle).

This classification by the construction type of the individual geometry objects allows the generalization of many operations in a few abstract classes. Those abstract classes implement methods that should return an object of the same type as the inheriting class. Thus, type parameters are used to specify such return types.

Point

Point objects represent a point in two dimensional space. For the purpose of imagination, you can assume the coordinate system to be originated in the top left corner of your drawing area, expanding to the right and to the bottom. From a list of Point objects, you can build up most of the planar geometric objects:

Additionally, the Point class provides static utility methods to operate on a list of Points: getBounds(Point...), getCentroid(Point...), and getConvexHull(Point...). They construct a bounding box, the centroid, and a convex hull of the given Point list, respectively.

Polygon convexHull =Point.getConvexHull(points);

Dimension

The Dimension class is the pendant of the org.eclipse.draw2d.geometry.Dimension class. It decouples the location and the width and height of a rectangular object.

Line

As it inherits from the BezierCurve class, all the operations for BezierCurves are available for Line objects, too. Because of its frequent use, Line overrides many of those operations to provide faster implementations for the Line/Line and Line/Point cases (equals(), touches(), contains(), intersects(), overlaps(), getIntersections(), and many more). If you want to display a Line using SWT, you can use its toSWTPointArray() method as follows:

gc.drawPolyline(line.toSWTPointArray());

Rectangle

extends: AbstractRectangleBasedGeometry

implements: IShape, ITranslatable, IScalable, IRotatable

A Rectangle is the axes-parallel rectangle defined by a location (x- and y-coordinates) and a Dimension (width and height):

Rectangle objects are frequently used, that's why some operations are overridden to provide faster implementations for designated parameter types (equals(), contains(), touches()). If you want to display a Rectangle using SWT, you can use its toSWTRectangle() method as follows:

Region

extends: AbstractPolyShape

implements: IPolyShape, ITranslatable, IScalable, IRotatable

A Region is build up of multiple Rectangles to address their enclosing area as a unit. The Rectangles that build up the Region do not have to touch each other. If they intersect, the Rectangles are divided into a number of internal Rectangles that do not intersect:

Region region =new Region(rect0, rect1, rect2);

You can use a Region as a clipping area as in the example image above. For this purpose, it can be transfered into a SWT Region using its toSWTRegion() method as follows:

Ring

extends: AbstractPolyShape

implements: IPolyShape, ITranslatable, IScalable, IRotatable

A Ring is build up of multiple Polygons to address their enclosing area as a unit. The Polygons that build up the Ring do not have to touch each other. They are transfered into an internal number of triangles that do not intersect:

Ring ring =new Ring(poly0, poly1, poly2);

Path

extends: AbstractGeometry

Using a Path is like drawing the joker. You can transfer every other geometric object into a Path using the toPath() method. But the Path does not implement the GEF 4 Geometry interfaces. It simply delegates to the java.awt.geom.Path2D. That's why you should try to avoid using the Path if you want to perform further computations. On the other hand, a Path is easy to render via SWT:

Euclidean Geometry

Angle

Considering rotation and the angular relationship of two straight lines, Angle objects come into play. They abstract over the two commonly used angle units, degrees and radians. The user has to specify the unit of the value an Angle object is constructed from. Moreover, the user can read the value of an Angle object in either degrees or radians. Therefore, the use of Angle objects assures that correct values are used in calculations. This indirection is done due to an inconsistency of several APIs, for example, org.eclipse.swt.graphics.Transform vs. org.eclipse.draw2d.geometry.Transform.

Vector

A Vector has two components x and y. It can be interpreted as a planar Point (toPoint()). The Vector class implements the common arithmetic operations for vectors: addition, multiplication with a scalar, dot product, cross product, and Angle calculation between two Vectors:

Projective Geometry

Projective geometry is an interesting perspective to (planar) geometry. A point and a line can both be represented by a (x, y, z) triple. And, in fact, people speak of the duality between points and lines, because for any relation between points and lines, the inverse relation holds if you substitute point by line and vice versa. To retain the semantic distinction of points and lines, both concepts are separated in the Vector3D and Straight3D classes. You may wonder why two dimensional objects are specified by three components. This is the case, because a planar projective point is really a three dimensional euclidean line, that passes through the origin of the three dimensional coordinate system. The x, y, and z values are the components of the direction vector of that line. Similarly, a planar projective line is really a three dimensional euclidean plane, that contains the origin of the three dimensional coordinate system. The x, y, and z components specify that plane's normal vector. Notice that the z = 1 plane is considered to be the two dimensional plane. Therefore, a Vector3D with the components (x, y, z) represents a Point with components (x/z, y/z).

This approach leads to elegant mathematical solutions to some of the basic planar geometric operations. You want to know if a point lies on a line? Simply substitute its coordinate values into the Straight3D's formula. You want to know the distance of a point to a line? If the Straight3D's vector is normalized (a^2 + b^2 = 1), the formula evaluates to the signed distance of the point to the line, i.e. on one side of the line it is positive and on the other side it is negative. You want to know where two lines are intersecting? Simply compute the cross product of two (x, y, z) triples. These operations are packed in appropriately named methods.

Straight3D

A Straight3D consists of three components (x, y, z). It represents the line ax + by + z = 0 in two dimensional space, where (a, b) is a planar Point. You can use a Straight3D to calculate the distance of a Vector3D to the Straight3D. Moreover, you can compute the point of intersection of two Straight3Ds:

Transformations

package: org.eclipse.gef4.geometry.transform

The transform package presents the AffineTransform class to apply affine transformations to geometric objects. It delegates to the java.awt.geom.AffineTransform implementation and should be way more efficient over using the short-cut transformation methods. At least if you combine several transformations into one AffineTransform object, because the AffineTransform manages one matrix that handles rotation, scaling, shearing, and translation altogether.

Conversions

package: org.eclipse.gef4.geometry.convert

The convert package contains helper classes to transfer data from AWT/SWT/Geometry to one another.

From Geometry to SWT

Many geometric objects provide a toSWT*() method that transfers the individual object into an SWT alternative. Besides, the transformation into a Path using the toPath() method enables you to construct an SWT Path using the Path.toSWTPathData() method:

From AWT to Geometry

From AWT to SWT

The AWT2SWT class contains a toSWTPathData() method which transfers an AWT PathIterator into an SWT PathData. Consider that the winding rule of the AWT PathIterator is not kept in the SWT PathData, because the latter does not store this information. Instead, it is provided by an SWT Path or by the SWT GC that is used to draw the SWT PathData object:

PathData pathData = AWT2SWT.toSWTPathData(pathIterator);

From SWT to AWT

The SWT2AWT class contains a toPathIterator() method which transfers an SWT PathData into an AWT PathIterator.