View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  package org.apache.commons.geometry.euclidean.twod;
    18
    19  import java.text.MessageFormat;
    20
    21  import org.apache.commons.geometry.euclidean.oned.Interval;
    22  import org.apache.commons.numbers.core.Precision;
    23
    24  /** Class containing factory methods for constructing {@link Line} and {@link LineSubset} instances.
    25   */
    26  public final class Lines {
    27
    28      /** Utility class; no instantiation. */
    29      private Lines() {
    30      }
    31
    32      /** Create a line from two points lying on the line. The line points in the direction
    33       * from {@code p1} to {@code p2}.
    34       * @param p1 first point
    35       * @param p2 second point
    36       * @param precision precision context used to compare floating point values
    37       * @return new line containing {@code p1} and {@code p2} and pointing in the direction
    38       *      from {@code p1} to {@code p2}
    39       * @throws IllegalArgumentException If the vector between {@code p1} and {@code p2} has zero length,
    40       *      as evaluated by the given precision context
    41       */
    42      public static Line fromPoints(final Vector2D p1, final Vector2D p2, final Precision.DoubleEquivalence precision) {
    43          return fromPointAndDirection(p1, p1.vectorTo(p2), precision);
    44      }
    45
    46      /** Create a line from a point and direction.
    47       * @param pt point belonging to the line
    48       * @param dir the direction of the line
    49       * @param precision precision context used to compare floating point values
    50       * @return new line containing {@code pt} and pointing in direction {@code dir}
    51       * @throws IllegalArgumentException If {@code dir} has zero length, as evaluated by the
    52       *      given precision context
    53       */
    54      public static Line fromPointAndDirection(final Vector2D pt, final Vector2D dir,
    55              final Precision.DoubleEquivalence precision) {
    56          if (dir.isZero(precision)) {
    57              throw new IllegalArgumentException("Line direction cannot be zero");
    58          }
    59
    60          final Vector2D.Unit normalizedDir = dir.normalize();
    61          final double originOffset = normalizedDir.signedArea(pt);
    62
    63          return new Line(normalizedDir, originOffset, precision);
    64      }
    65
    66      /** Create a line from a point lying on the line and an angle relative to the abscissa (x) axis. Note that the
    67       * line does not need to intersect the x-axis; the given angle is simply relative to it.
    68       * @param pt point belonging to the line
    69       * @param angle angle of the line with respect to abscissa (x) axis, in radians
    70       * @param precision precision context used to compare floating point values
    71       * @return new line containing {@code pt} and forming the given angle with the
    72       *      abscissa (x) axis.
    73       */
    74      public static Line fromPointAndAngle(final Vector2D pt, final double angle,
    75              final Precision.DoubleEquivalence precision) {
    76          final Vector2D.Unit dir = Vector2D.Unit.from(Math.cos(angle), Math.sin(angle));
    77          return fromPointAndDirection(pt, dir, precision);
    78      }
    79
    80      /** Construct a ray from a start point and a direction.
    81       * @param startPoint ray start point
    82       * @param direction ray direction
    83       * @param precision precision context used for floating point comparisons
    84       * @return a new ray instance with the given start point and direction
    85       * @throws IllegalArgumentException If {@code direction} has zero length, as evaluated by the
    86       *      given precision context
    87       * @see #fromPointAndDirection(Vector2D, Vector2D, Precision.DoubleEquivalence)
    88       */
    89      public static Ray rayFromPointAndDirection(final Vector2D startPoint, final Vector2D direction,
    90              final Precision.DoubleEquivalence precision) {
    91          final Line line = Lines.fromPointAndDirection(startPoint, direction, precision);
    92
    93          return new Ray(line, startPoint);
    94      }
    95
    96      /** Construct a ray starting at the given point and continuing to infinity in the direction
    97       * of {@code line}. The given point is projected onto the line.
    98       * @param line line for the ray
    99       * @param startPoint start point for the ray
    100      * @return a new ray instance starting at the given point and continuing in the direction of
    101      *      {@code line}
    102      * @throws IllegalArgumentException if any coordinate in {@code startPoint} is NaN or infinite
    103      */
    104     public static Ray rayFromPoint(final Line line, final Vector2D startPoint) {
    105         if (!startPoint.isFinite()) {
    106             throw new IllegalArgumentException("Invalid ray start point: " + startPoint);
    107         }
    108         return new Ray(line, line.project(startPoint));
    109     }
    110
    111     /** Construct a ray starting at the given 1D location on {@code line} and continuing in the
    112      * direction of the line to infinity.
    113      * @param line line for the ray
    114      * @param startLocation 1D location of the ray start point
    115      * @return a new ray instance starting at the given 1D location and continuing to infinity
    116      *      along {@code line}
    117      * @throws IllegalArgumentException if {@code startLocation} is NaN or infinite
    118      */
    119     public static Ray rayFromLocation(final Line line, final double startLocation) {
    120         if (!Double.isFinite(startLocation)) {
    121             throw new IllegalArgumentException("Invalid ray start location: " + startLocation);
    122         }
    123         return new Ray(line, line.toSpace(startLocation));
    124     }
    125
    126     /** Construct a reverse ray from an end point and a line direction.
    127      * @param endPoint instance end point
    128      * @param lineDirection line direction
    129      * @param precision precision context used for floating point comparisons
    130      * @return a new instance with the given end point and line direction
    131      * @throws IllegalArgumentException If {@code lineDirection} has zero length, as evaluated by the
    132      *      given precision context
    133      * @see #fromPointAndDirection(Vector2D, Vector2D, Precision.DoubleEquivalence)
    134      */
    135     public static ReverseRay reverseRayFromPointAndDirection(final Vector2D endPoint, final Vector2D lineDirection,
    136             final Precision.DoubleEquivalence precision) {
    137         final Line line = Lines.fromPointAndDirection(endPoint, lineDirection, precision);
    138
    139         return new ReverseRay(line, endPoint);
    140     }
    141
    142     /** Construct a reverse ray starting at infinity and continuing in the direction of {@code line}
    143      * to the given end point. The point is projected onto the line.
    144      * @param line line for the instance
    145      * @param endPoint end point for the instance
    146      * @return a new instance starting at infinity and continuing along the line to {@code endPoint}
    147      * @throws IllegalArgumentException if any coordinate in {@code endPoint} is NaN or infinite
    148      */
    149     public static ReverseRay reverseRayFromPoint(final Line line, final Vector2D endPoint) {
    150         if (!endPoint.isFinite()) {
    151             throw new IllegalArgumentException("Invalid reverse ray end point: " + endPoint);
    152         }
    153         return new ReverseRay(line, line.project(endPoint));
    154     }
    155
    156     /** Construct a reverse ray starting at infinity and continuing in the direction of {@code line}
    157      * to the given 1D end location.
    158      * @param line line for the instance
    159      * @param endLocation 1D location of the instance end point
    160      * @return a new instance starting infinity and continuing in the direction of {@code line}
    161      *      to the given 1D end location
    162      * @throws IllegalArgumentException if {@code endLocation} is NaN or infinite
    163      */
    164     public static ReverseRay reverseRayFromLocation(final Line line, final double endLocation) {
    165         if (!Double.isFinite(endLocation)) {
    166             throw new IllegalArgumentException("Invalid reverse ray end location: " + endLocation);
    167         }
    168
    169         return new ReverseRay(line, line.toSpace(endLocation));
    170     }
    171
    172     /** Construct a new line segment from two points. A new line is created for the segment and points in the
    173      * direction from {@code startPoint} to {@code endPoint}.
    174      * @param startPoint segment start point
    175      * @param endPoint segment end point
    176      * @param precision precision context to use for floating point comparisons
    177      * @return a new line segment instance with the given start and end points
    178      * @throws IllegalArgumentException If the vector between {@code startPoint} and {@code endPoint} has zero length,
    179      *      as evaluated by the given precision context
    180      */
    181     public static Segment segmentFromPoints(final Vector2D startPoint, final Vector2D endPoint,
    182             final Precision.DoubleEquivalence precision) {
    183         final Line line = Lines.fromPoints(startPoint, endPoint, precision);
    184
    185         // we know that the points lie on the line and are in increasing abscissa order
    186         // since they were used to create the line
    187         return new Segment(line, startPoint, endPoint);
    188     }
    189
    190     /** Construct a new line segment from a line and a pair of points. The returned segment represents
    191      * all points on the line between the projected locations of {@code a} and {@code b}. The points may
    192      * be given in any order.
    193      * @param line line forming the base of the segment
    194      * @param a first point
    195      * @param b second point
    196      * @return a new line segment representing the points between the projected locations of {@code a}
    197      *      and {@code b} on the given line
    198      * @throws IllegalArgumentException if either point contains NaN or infinite coordinate values
    199      */
    200     public static Segment segmentFromPoints(final Line line, final Vector2D a, final Vector2D b) {
    201         return segmentFromLocations(line, line.abscissa(a), line.abscissa(b));
    202     }
    203
    204     /** Construct a new line segment from a pair of 1D locations on a line. The returned line
    205      * segment consists of all points between the two locations, regardless of the order the
    206      * arguments are given.
    207      * @param line line forming the base of the segment
    208      * @param a first 1D location on the line
    209      * @param b second 1D location on the line
    210      * @return a new line segment representing the points between {@code a} and {@code b} on
    211      *      the given line
    212      * @throws IllegalArgumentException if either of the locations is NaN or infinite
    213      */
    214     public static Segment segmentFromLocations(final Line line, final double a, final double b) {
    215
    216         if (Double.isFinite(a) && Double.isFinite(b)) {
    217             final double min = Math.min(a, b);
    218             final double max = Math.max(a, b);
    219
    220             return new Segment(line, line.toSpace(min), line.toSpace(max));
    221         }
    222
    223         throw new IllegalArgumentException(
    224                 MessageFormat.format("Invalid line segment locations: {0}, {1}",
    225                         Double.toString(a), Double.toString(b)));
    226     }
    227
    228     /** Create a {@link LineConvexSubset} spanning the entire line. In other words, the returned
    229      * subset is infinite and contains all points on the given line.
    230      * @param line the line to span
    231      * @return a convex subset spanning the entire line
    232      */
    233     public static LineConvexSubset span(final Line line) {
    234         return new LineSpanningSubset(line);
    235     }
    236
    237     /** Create a line subset from a line and a 1D interval on the line. The returned subset
    238      * uses the precision context from the line and not any precision contexts referenced by the interval.
    239      * @param line the line containing the subset
    240      * @param interval 1D interval on the line
    241      * @return a convex subset defined by the given line and interval
    242      */
    243     public static LineConvexSubset subsetFromInterval(final Line line, final Interval interval) {
    244         return subsetFromInterval(line, interval.getMin(), interval.getMax());
    245     }
    246
    247     /** Create a line subset from a line and a 1D interval on the line. The double values may be given in any
    248      * order and support the use of infinite values. For example, the call
    249      * {@code Lines.subsetFromInterval(line, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY)} will return
    250      * an instance representing the full span of the line.
    251      * @param line the line containing the subset
    252      * @param a first 1D location on the line
    253      * @param b second 1D location on the line
    254      * @return a line subset defined by the given line and interval
    255      * @throws IllegalArgumentException if either double value is NaN or both are infinite with the same sign
    256      *      (eg, both positive infinity or both negative infinity)
    257      */
    258     public static LineConvexSubset subsetFromInterval(final Line line, final double a, final double b) {
    259         final double min = Math.min(a, b);
    260         final double max = Math.max(a, b);
    261
    262         final boolean hasMin = Double.isFinite(min);
    263         final boolean hasMax = Double.isFinite(max);
    264
    265         if (hasMin) {
    266             if (hasMax) {
    267                 // has both
    268                 return new Segment(line, line.toSpace(min), line.toSpace(max));
    269             }
    270             // min only
    271             return new Ray(line, line.toSpace(min));
    272         } else if (hasMax) {
    273             // max only
    274             return new ReverseRay(line, line.toSpace(max));
    275         } else if (Double.isInfinite(min) && Double.isInfinite(max) && Double.compare(min, max) < 0) {
    276             return new LineSpanningSubset(line);
    277         }
    278
    279         throw new IllegalArgumentException(MessageFormat.format(
    280                 "Invalid line subset interval: {0}, {1}", Double.toString(a), Double.toString(b)));
    281     }
    282
    283     /** Validate that the actual line is equivalent to the expected line, throwing an exception if not.
    284      * @param expected the expected line
    285      * @param actual the actual line
    286      * @throws IllegalArgumentException if the actual line is not equivalent to the expected line
    287      */
    288     static void validateLinesEquivalent(final Line expected, final Line actual) {
    289         if (!expected.eq(actual, expected.getPrecision())) {
    290             throw new IllegalArgumentException("Arguments do not represent the same line. Expected " +
    291                     expected + " but was " + actual + ".");
    292         }
    293     }
    294 }