Lines

The DJUTILS-DRAW line package defines several line-like objects:

  • PolyLine: lines with two end points and zero or more intermediate points.
  • Polygon: closed PolyLine with at least two points.
  • LineSegment: line segment defined by two points. This much like a PolyLine with no intermediate points.
  • Ray: line with one finite end point; going all the way to infinity in the other direction.

All line types exist in 2D and 3D versions. Additionally there is the Bezier class that generates PolyLine objects in 2D or 3D. More such generating classes will be added. E.g. for ellipses or segments thereof, clothoids.

PolyLine

A PolyLine can be constructed from a collection of suitable Point objects (a PolyLine2d can be constructed from Point2d objects). It can also be constructed from a double[] (double array) for each of the dimensions. The following code shows four ways to construct the exact same PolyLine2d object:

    PolyLine2d pl1 = new PolyLine2d(new Point2d(1, 2), new Point2d(3, 4), new Point2d(20, -5));
    System.out.println(pl1);
    Point2d[] pointArray = new Point2d[] {new Point2d(1, 2), new Point2d(3, 4), new Point2d(20, -5)};
    PolyLine2d pl2 = new PolyLine2d(pointArray);
    System.out.println(pl2);
    double[] x = new double[] { 1, 3, 20 };
    double[] y = new double[] { 2, 4, -5 };
    PolyLine2d pl3 = new PolyLine2d(x, y);
    System.out.println(pl3);
    List<Point2d> pointList = new ArrayList<>();
    pointList.add(new Point2d(1, 2));
    pointList.add(new Point2d(3, 4));
    pointList.add(new Point2d(20, -5));
    PolyLine2d pl4 = new PolyLine2d(pointList);
    System.out.println(pl4);

This outputs:

PolyLine2d [x=1.000000, y=2.000000, x=3.000000, y=4.000000, x=20.000000, y=-5.000000]
PolyLine2d [x=1.000000, y=2.000000, x=3.000000, y=4.000000, x=20.000000, y=-5.000000]
PolyLine2d [x=1.000000, y=2.000000, x=3.000000, y=4.000000, x=20.000000, y=-5.000000]
PolyLine2d [x=1.000000, y=2.000000, x=3.000000, y=4.000000, x=20.000000, y=-5.000000]

A PolyLine2d can also be constructed from an Iterator<Point2d> and even from a java.awt.geom.Path2d object, provided it only contains SEG_MOVETO and SEG_LINETO segments. If there is a SEG_CLOSE segment, anything after that is ignored. Any other segment types will cause the constructor to throw a DrawRuntimeException.

A PolyLine3d is very much like a PolyLine2d, except that it has z-coordinates and cannot be constructed from a java.awt.geom.Path2d object.

An attempt to create a PolyLine with two successive, idential points will fail with a DrawRuntimeException. Several PolyLine objects can be concatenated, provided the end point of each one matches the first point of the next. There is also a concatenate operation that allows an error margin for concatenation. The noiseFilteredLine constructor allows a PolyLine to be constructed from a list or array of points while filtering out intermediate points that are within a specified margin from the preceding point.

The total length of a PolyLine can be obtained with thegetLength() method. The number of points that make up a PolyLine can be obtained with the size() method. The individual points with the get(int) method; calling this method creates a new Point object. The start point can be obtained with the getFirst() method; the end point with the getLast() method. Each double coordinate value can be directly obtained (without creating a Point object) with getX(int), getY(int) and (only for PolyLine3d) getZ(int). The cumulative length up to any of the points can be obtained with the lengthAtIndex(int) method. The result of lengthAtIndex(0) is always 0.0; the result of lengthAtIndex(size() - 1) is equal to getLength().

A fragment of a PolyLine can be created with the extract(double, double) method. The parameters specify the distance from the start point and must be ordered by distance and be within the length of the PolyLine. The extractFractional method does the same, but the parameters are specified as fractions of the length of the PolyLine:

    PolyLine2d polyLine2d = new PolyLine2d(new Point2d(1, 1), new Point2d(5, 1), new Point2d(5, 2), new Point2d(9, 5));
    System.out.println("PolyLine: " + polyLine2d);
    System.out.println("length: " + polyLine2d.getLength());
    System.out.println("fragment: " + polyLine2d.extract(2.0, 9.0));
    System.out.println("fragment: " + polyLine2d.extractFractional(0.2, 0.9));

Prints

PolyLine: PolyLine2d [x=1.000000, y=1.000000, x=5.000000, y=1.000000, x=5.000000, y=2.000000, x=9.000000, y=5.000000]
length: 10.0
fragment: PolyLine2d [x=3.000000, y=1.000000, x=5.000000, y=1.000000, x=5.000000, y=2.000000, x=8.200000, y=4.400000]
fragment: PolyLine2d [x=3.000000, y=1.000000, x=5.000000, y=1.000000, x=5.000000, y=2.000000, x=8.200000, y=4.400000]

Any location along the PolyLine can be obtained with the getLocation(double) method. The parameter must be between 0.0 and the length of the PolyLine. Extrapolation is also possible with the getLocationExtended method:

    System.out.println("PolyLine: " + polyLine2d);
    System.out.println("location at distance 7.0: " + polyLine2d.getLocation(7.0));
    System.out.println("extended location at distance 15: " + polyLine2d.getLocationExtended(15.0));
    System.out.println("extended location at distance -8: " + polyLine2d.getLocationExtended(-8.0));

Prints

PolyLine: PolyLine2d [x=1.000000, y=1.000000, x=5.000000, y=1.000000, x=5.000000, y=2.000000, x=9.000000, y=5.000000]
location at distance 7.0: Ray2d [x=6.6 y=3.2 phi=0.6435011087932843]
extended location at distance 15: Ray2d [x=13.0 y=8.0 phi=0.6435011087932844]
extended location at distance -8: Ray2d [x=-7.0 y=1.0 phi=0.0]

As you can see, the getLocation and getLocationExtended methods return Ray objects. A ray is a point with a direction. The getLocation method sets the direction of the ray to the direction of the PolyLine at the requested point. Extrapolation for negative distances is done in the direction of the first segment of the PolyLine. Extrapolation after the length of the PolyLine uses the direction of the last segment of the PolyLine. If the location is at one of the intermediate points of the PolyLine, the direction of the returned ray may depend on rounding errors; it is either the direction of the preceding segment, or that of the succeeding segment.

A PolyLine has a method to extract any of the segments from which it is constructed with thegetSegment(int) method. the parameter selects the first point of the segment. The valid range of this parameter is 0..size() - 2.

    System.out.println("PolyLine: " + polyLine2d);
    for (int index = 0; index < polyLine2d.size() - 1; index++)
    {
        System.out.println("segment " + index + ": " + polyLine2d.getSegment(index));
    }

Prints:

PolyLine: PolyLine2d [x=1.000000, y=1.000000, x=5.000000, y=1.000000, x=5.000000, y=2.000000, x=9.000000, y=5.000000]
segment 0: LineSegment2d [startX=1.000000, startY=1.000000 - endX= 5.0, endY=1.000000]
segment 1: LineSegment2d [startX=5.000000, startY=1.000000 - endX= 5.0, endY=2.000000]
segment 2: LineSegment2d [startX=5.000000, startY=2.000000 - endX= 9.0, endY=5.000000]

Often, it is necessary to find the point on a PolyLine that is closest to a given Point. This can be obtained with the closestPointOnPolyline method. In some cases one is only interested in the closest point if it is not on one of the vertices of the PolyLine. In that case, one of the projectOrthogonal methods can be used. The projectOrthogonal methods return null if there is a closer point, but that projection is not orthogonal to the line segment on which it lies.

    System.out.println("PolyLine: " + polyLine2d);
    System.out.println("closest point to (0,1): " + polyLine2d.closestPointOnPolyLine(new Point2d(0, 1)));
    System.out.println("closest point to (6,0): " + polyLine2d.closestPointOnPolyLine(new Point2d(6, 0)));
    System.out.println("closest point to (10,0): " + polyLine2d.closestPointOnPolyLine(new Point2d(10, 0)));
    System.out.println("closest point to (50,0): " + polyLine2d.closestPointOnPolyLine(new Point2d(50, 0)));
    System.out.println("project (0,0) orthogonal: " + polyLine2d.projectOrthogonal(new Point2d(0, 0)));
    System.out.println("project (4,0) orthogonal: " + polyLine2d.projectOrthogonal(new Point2d(4, 0)));
    System.out.println("project (5,0) orthogonal: " + polyLine2d.projectOrthogonal(new Point2d(5, 0)));
    System.out.println("project (6,0) orthogonal: " + polyLine2d.projectOrthogonal(new Point2d(6, 0)));
    System.out.println("project (10,0) orthogonal: " + polyLine2d.projectOrthogonal(new Point2d(10, 0)));
    System.out.println("project (50,0) orthogonal: " + polyLine2d.projectOrthogonal(new Point2d(50, 0)));
    System.out.println("project (50,0) orthogonal extended: " + polyLine2d.projectOrthogonalExtended(new Point2d(50, 0)));

Prints:

PolyLine: PolyLine2d [x=1.000000, y=1.000000, x=5.000000, y=1.000000, x=5.000000, y=2.000000, x=9.000000, y=5.000000]
closest point to (0,1): Ray2d [x=1.0 y=1.0 phi=0.0]
closest point to (6,0): Ray2d [x=5.0 y=1.0 phi=1.5707963267948966]
closest point to (10,0): Ray2d [x=7.24 y=3.6800000000000006 phi=0.6435011087932843]
closest point to (50,0): Ray2d [x=9.0 y=5.0 phi=0.6435011087932844]
project (0,0) orthogonal: null
project (4,0) orthogonal: Ray2d [x=4.0 y=1.0 phi=0.0]
project (5,0) orthogonal: Ray2d [x=5.0 y=1.0 phi=1.5707963267948966]
project (6,0) orthogonal: null
project (10,0) orthogonal: Ray2d [x=7.24 y=3.6800000000000006 phi=0.6435011087932843]
project (50,0) orthogonal: null
project (50,0) orthogonal extended: Ray2d [x=32.839999999999996 y=22.879999999999995 phi=0.6435011087932844]

There are also variants of these methods that return a fractional location along the PolyLine. A fractional location is a value that is scaled by dividing it by the length of the PolyLine. The result of those methods is a double value. For results that are within the range of the PolyLine, that value is in the range 0.0 .. 1.0. The extended variants can return values outside that range. If there is no valid result, these methods return Double.NaN.

TODO Explain the offsetLine method of PolyLine2d.

LineSegment

A LineSegment is effectively a PolyLine with only two end points and no intermediate points. A LineSegment can be constructed from two Point objects, or one Point and one set of coordinates, or two sets of coordinates:

    LineSegment2d ls1 = new LineSegment2d(new Point2d(1, 2), new Point2d(5, 0));
    System.out.println("ls1: " + ls1);
    LineSegment2d ls2 = new LineSegment2d(new Point2d(1, 2), 5, 0);
    System.out.println("ls2: " + ls2);
    LineSegment2d ls3 = new LineSegment2d(1, 2, new Point2d(5, 0));
    System.out.println("ls3: " + ls3);
    LineSegment2d ls4 = new LineSegment2d(1, 2, 5, 0);
    System.out.println("ls4: " + ls4);

Prints:

ls1: LineSegment2d [startX=1.000000, startY=2.000000 - endX= 5.0, endY=0.000000]
ls2: LineSegment2d [startX=1.000000, startY=2.000000 - endX= 5.0, endY=0.000000]
ls3: LineSegment2d [startX=1.000000, startY=2.000000 - endX= 5.0, endY=0.000000]
ls4: LineSegment2d [startX=1.000000, startY=2.000000 - endX= 5.0, endY=0.000000]

The coordinates of the start and end points can be accessed directly as the startX, startY, endX and endY fields (and, in case of a LineSegment3d, the startZ and endZ fields). The start and end points can also be obtained with getStartPoint() and getEndPoint(). These methods construct a new Point.

Like the PolyLine classes, the LineSegment classes implement the closestPointOnSegment, projectOrthogonal, getLocation and getLocationExtended methods.

Ray

A ray is a line with one finite end point. In the other direction a ray continues to infinity. In DJUTILS-DRAW, rays are available in 2D and 3D versions. The constructors take the (coordinates of the) finite end point and an additional through point, or a direction (just phi to construct a Ray2d, or phi and theta to construct a Ray3d). There are six main ways to construct a Ray:

    Ray2d r1 = new Ray2d(new Point2d(1, 2), new Point2d(1, 6));
    System.out.println(r1);
    Ray2d r2 = new Ray2d(1, 2, 1, 6);
    System.out.println(r2);
    Ray2d r3 = new Ray2d(new Point2d(1, 2), 1, 6);
    System.out.println(r3);
    Ray2d r4 = new Ray2d(1, 2, new Point2d(1, 6));
    System.out.println(r4);
    Ray2d r5 = new Ray2d(1, 2, Math.PI / 2);
    System.out.println(r5);
    Ray2d r6 = new Ray2d(new Point2d(1, 2), Math.PI / 2);
    System.out.println(r6);

Prints:

Ray2d [x=1.0 y=2.0 phi=1.5707963267948966]
Ray2d [x=1.0 y=2.0 phi=1.5707963267948966]
Ray2d [x=1.0 y=2.0 phi=1.5707963267948966]
Ray2d [x=1.0 y=2.0 phi=1.5707963267948966]
Ray2d [x=1.0 y=2.0 phi=1.5707963267948966]
Ray2d [x=1.0 y=2.0 phi=1.5707963267948966]

The direction of a Ray2d is encoded as an angle (in Radians) from the X-axis. These examples all create a ray that points along the Y axis. The Ray3d classes simply take extra arguments (two z coordinates, or one z coordinate and theta; the angle from the Z-axis).

The coordinates of the finite end point can be directly accessed as the x, y (and for Ray3d) z fields. There are also getters for these: getX(), getY() (and for Ray3d getZ()). The field phi (and for Ray3d theta) is also directly accessible, or with the method getPhi() (and for Ray3d getTheta()). The finite end point can also be obtained with the getEndPoint() method.

Rays implement getLocation and getLocationExtended methods. The first takes a non-negative distance argument and returns a new Ray which is coincident with the original, but starts the distance value from the start point of the first ray. The getLocationExtended method allows the argument to be negative (and will return a new Ray which is coincident with the original, but starts the distance value along, or before the original Ray). The closestPointOnRay method takes a Point as parameter and returns a new Ray that starts at some point along the original ray which is closest to the given Point.

Like the LineSegment classes, Ray classes implement four projectOrthogonal methods. If the orthogonal projection of the given Point falls before the finite end point of the Ray, the projectOrthogonal method returns null and the projectOrthogonalFractional method returns Double.NaN. The projectOrthogonalExtended method will return negative values if the given Point projects before the finite end point of the Ray and the projectOrthogonalFractionalExtended method will return negative values if that happens.

The Bounds of a Ray can be obtained with the getBounds() method and is finite is some directions and infinite in others.

The flip() method creates a new Ray with the same finite end point, but pointing in the opposite direction. The neg() method creates a new Ray at a point that has all coordinates inverted from the original and pointing in the opposite direction.

The Ray classes implement epsilonEquals to compare similar rays for equality with specified tolerances for the coordinates and the angles. Before that comparison takes place, the angles are normalized.