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.threed.line;
    18
    19  import java.text.MessageFormat;
    20  import java.util.Objects;
    21
    22  import org.apache.commons.geometry.core.Embedding;
    23  import org.apache.commons.geometry.core.Transform;
    24  import org.apache.commons.geometry.euclidean.oned.AffineTransformMatrix1D;
    25  import org.apache.commons.geometry.euclidean.oned.Vector1D;
    26  import org.apache.commons.geometry.euclidean.threed.Vector3D;
    27  import org.apache.commons.numbers.core.Precision;
    28
    29  /** Class representing a line in 3D space.
    30   *
    31   * <p>Instances of this class are guaranteed to be immutable.</p>
    32   * @see Lines3D
    33   */
    34  public final class Line3D implements Embedding<Vector3D, Vector1D> {
    35
    36      /** Format string for creating line string representations. */
    37      static final String TO_STRING_FORMAT = "{0}[origin= {1}, direction= {2}]";
    38
    39      /** Line point closest to the origin. */
    40      private final Vector3D origin;
    41
    42      /** Line direction. */
    43      private final Vector3D direction;
    44
    45      /** Precision context used to compare floating point numbers. */
    46      private final Precision.DoubleEquivalence precision;
    47
    48      /** Simple constructor.
    49       * @param origin the origin of the line, meaning the point on the line closest to the origin of the
    50       *      3D space
    51       * @param direction the direction of the line
    52       * @param precision precision context used to compare floating point numbers
    53       */
    54      Line3D(final Vector3D origin, final Vector3D direction, final Precision.DoubleEquivalence precision) {
    55          this.origin = origin;
    56          this.direction = direction;
    57          this.precision = precision;
    58      }
    59
    60      /** Get the line point closest to the origin.
    61       * @return line point closest to the origin
    62       */
    63      public Vector3D getOrigin() {
    64          return origin;
    65      }
    66
    67      /** Get the normalized direction vector.
    68       * @return normalized direction vector
    69       */
    70      public Vector3D getDirection() {
    71          return direction;
    72      }
    73
    74      /** Get the object used to determine floating point equality for this instance.
    75       * @return the floating point precision context for the instance
    76       */
    77      public Precision.DoubleEquivalence getPrecision() {
    78          return precision;
    79      }
    80
    81      /** Return a line containing the same points as this instance but pointing
    82       * in the opposite direction.
    83       * @return an instance containing the same points but pointing in the opposite
    84       *      direction
    85       */
    86      public Line3D reverse() {
    87          return new Line3D(origin, direction.negate(), precision);
    88      }
    89
    90      /** Transform this instance.
    91       * @param transform object used to transform the instance
    92       * @return a transformed instance
    93       */
    94      public Line3D transform(final Transform<Vector3D> transform) {
    95          final Vector3D p1 = transform.apply(origin);
    96          final Vector3D p2 = transform.apply(origin.add(direction));
    97
    98          return Lines3D.fromPoints(p1, p2, precision);
    99      }
    100
    101     /** Get an object containing the current line transformed by the argument along with a
    102      * 1D transform that can be applied to subspace points. The subspace transform transforms
    103      * subspace points such that their 3D location in the transformed line is the same as their
    104      * 3D location in the original line after the 3D transform is applied. For example, consider
    105      * the code below:
    106      * <pre>
    107      *      SubspaceTransform st = line.subspaceTransform(transform);
    108      *
    109      *      Vector1D subPt = Vector1D.of(1);
    110      *
    111      *      Vector3D a = transform.apply(line.toSpace(subPt)); // transform in 3D space
    112      *      Vector3D b = st.getLine().toSpace(st.getTransform().apply(subPt)); // transform in 1D space
    113      * </pre>
    114      * At the end of execution, the points {@code a} (which was transformed using the original
    115      * 3D transform) and {@code b} (which was transformed in 1D using the subspace transform)
    116      * are equivalent.
    117      *
    118      * @param transform the transform to apply to this instance
    119      * @return an object containing the transformed line along with a transform that can be applied
    120      *      to subspace points
    121      * @see #transform(Transform)
    122      */
    123     public SubspaceTransform subspaceTransform(final Transform<Vector3D> transform) {
    124         final Vector3D p1 = transform.apply(origin);
    125         final Vector3D p2 = transform.apply(origin.add(direction));
    126
    127         final Line3D tLine = Lines3D.fromPoints(p1, p2, precision);
    128
    129         final Vector1D tSubspaceOrigin = tLine.toSubspace(p1);
    130         final Vector1D tSubspaceDirection = tSubspaceOrigin.vectorTo(tLine.toSubspace(p2));
    131
    132         final double translation = tSubspaceOrigin.getX();
    133         final double scale = tSubspaceDirection.getX();
    134
    135         final AffineTransformMatrix1D subspaceTransform = AffineTransformMatrix1D.of(scale, translation);
    136
    137         return new SubspaceTransform(tLine, subspaceTransform);
    138     }
    139
    140     /** Get the abscissa of the given point on the line. The abscissa represents
    141      * the distance the projection of the point on the line is from the line's
    142      * origin point (the point on the line closest to the origin of the
    143      * 2D space). Abscissa values increase in the direction of the line. This method
    144      * is exactly equivalent to {@link #toSubspace(Vector3D)} except that this method
    145      * returns a double instead of a {@link Vector1D}.
    146      * @param pt point to compute the abscissa for
    147      * @return abscissa value of the point
    148      * @see #toSubspace(Vector3D)
    149      */
    150     public double abscissa(final Vector3D pt) {
    151         return pt.subtract(origin).dot(direction);
    152     }
    153
    154     /** Get one point from the line.
    155      * @param abscissa desired abscissa for the point
    156      * @return one point belonging to the line, at specified abscissa
    157      */
    158     public Vector3D pointAt(final double abscissa) {
    159         return Vector3D.Sum.of(origin)
    160                 .addScaled(abscissa, direction)
    161                 .get();
    162     }
    163
    164     /** {@inheritDoc} */
    165     @Override
    166     public Vector1D toSubspace(final Vector3D pt) {
    167         return Vector1D.of(abscissa(pt));
    168     }
    169
    170     /** {@inheritDoc} */
    171     @Override
    172     public Vector3D toSpace(final Vector1D pt) {
    173         return toSpace(pt.getX());
    174     }
    175
    176     /** Get the 3 dimensional point at the given abscissa position
    177      * on the line.
    178      * @param abscissa location on the line
    179      * @return the 3 dimensional point at the given abscissa position
    180      *      on the line
    181      */
    182     public Vector3D toSpace(final double abscissa) {
    183         return pointAt(abscissa);
    184     }
    185
    186     /** Check if the instance is similar to another line.
    187      * <p>Lines are considered similar if they contain the same
    188      * points. This does not mean they are equal since they can have
    189      * opposite directions.</p>
    190      * @param line line to which instance should be compared
    191      * @return true if the lines are similar
    192      */
    193     public boolean isSimilarTo(final Line3D line) {
    194         final double angle = direction.angle(line.direction);
    195         return (precision.eqZero(angle) || precision.eq(Math.abs(angle), Math.PI)) &&
    196                 contains(line.origin);
    197     }
    198
    199     /** Check if the instance contains a point.
    200      * @param pt point to check
    201      * @return true if p belongs to the line
    202      */
    203     public boolean contains(final Vector3D pt) {
    204         return precision.eqZero(distance(pt));
    205     }
    206
    207     /** Compute the distance between the instance and a point.
    208      * @param pt to check
    209      * @return distance between the instance and the point
    210      */
    211     public double distance(final Vector3D pt) {
    212         final Vector3D delta = pt.subtract(origin);
    213         final Vector3D orthogonal = delta.reject(direction);
    214
    215         return orthogonal.norm();
    216     }
    217
    218     /** Compute the shortest distance between the instance and another line.
    219      * @param line line to check against the instance
    220      * @return shortest distance between the instance and the line
    221      */
    222     public double distance(final Line3D line) {
    223
    224         final Vector3D normal = direction.cross(line.direction);
    225         final double norm = normal.norm();
    226
    227         if (precision.eqZero(norm)) {
    228             // the lines are parallel
    229             return distance(line.origin);
    230         }
    231
    232         // signed separation of the two parallel planes that contains the lines
    233         final double offset = line.origin.subtract(origin).dot(normal) / norm;
    234
    235         return Math.abs(offset);
    236     }
    237
    238     /** Compute the point of the instance closest to another line.
    239      * @param line line to check against the instance
    240      * @return point of the instance closest to another line
    241      */
    242     public Vector3D closest(final Line3D line) {
    243
    244         final double cos = direction.dot(line.direction);
    245         final double n = 1 - cos * cos;
    246
    247         if (precision.eqZero(n)) {
    248             // the lines are parallel
    249             return origin;
    250         }
    251
    252         final Vector3D delta = line.origin.subtract(origin);
    253         final double a = delta.dot(direction);
    254         final double b = delta.dot(line.direction);
    255
    256         return Vector3D.Sum.of(origin)
    257                 .addScaled((a - (b * cos)) / n, direction)
    258                 .get();
    259     }
    260
    261     /** Get the intersection point of the instance and another line.
    262      * @param line other line
    263      * @return intersection point of the instance and the other line
    264      * or null if there are no intersection points
    265      */
    266     public Vector3D intersection(final Line3D line) {
    267         final Vector3D closestPt = closest(line);
    268         return line.contains(closestPt) ? closestPt : null;
    269     }
    270
    271     /** Return a new infinite line subset representing the entire line.
    272      * @return a new infinite line subset representing the entire line
    273      * @see Lines3D#span(Line3D)
    274      */
    275     public LineConvexSubset3D span() {
    276         return Lines3D.span(this);
    277     }
    278
    279     /** Create a new line segment from the given 1D interval. The returned line
    280      * segment consists of all points between the two locations, regardless of the order the
    281      * arguments are given.
    282      * @param a first 1D location for the interval
    283      * @param b second 1D location for the interval
    284      * @return a new line segment on this line
    285      * @throws IllegalArgumentException if either of the locations is NaN or infinite
    286      * @see Lines3D#segmentFromLocations(Line3D, double, double)
    287      */
    288     public Segment3D segment(final double a, final double b) {
    289         return Lines3D.segmentFromLocations(this, a, b);
    290     }
    291
    292     /** Create a new line segment from two points. The returned segment represents all points on this line
    293      * between the projected locations of {@code a} and {@code b}. The points may be given in any order.
    294      * @param a first point
    295      * @param b second point
    296      * @return a new line segment on this line
    297      * @throws IllegalArgumentException if either point contains NaN or infinite coordinate values
    298      * @see Lines3D#segmentFromPoints(Line3D, Vector3D, Vector3D)
    299      */
    300     public Segment3D segment(final Vector3D a, final Vector3D b) {
    301         return Lines3D.segmentFromPoints(this, a, b);
    302     }
    303
    304     /** Create a new line convex subset that starts at infinity and continues along
    305      * the line up to the projection of the given end point.
    306      * @param endPoint point defining the end point of the line subset; the end point
    307      *      is equal to the projection of this point onto the line
    308      * @return a new, half-open line subset that ends at the given point
    309      * @throws IllegalArgumentException if any coordinate in {@code endPoint} is NaN or infinite
    310      * @see Lines3D#reverseRayFromPoint(Line3D, Vector3D)
    311      */
    312     public ReverseRay3D reverseRayTo(final Vector3D endPoint) {
    313         return Lines3D.reverseRayFromPoint(this, endPoint);
    314     }
    315
    316     /** Create a new line convex subset that starts at infinity and continues along
    317      * the line up to the given 1D location.
    318      * @param endLocation the 1D location of the end of the half-line
    319      * @return a new, half-open line subset that ends at the given 1D location
    320      * @throws IllegalArgumentException if {@code endLocation} is NaN or infinite
    321      * @see Lines3D#reverseRayFromLocation(Line3D, double)
    322      */
    323     public ReverseRay3D reverseRayTo(final double endLocation) {
    324         return Lines3D.reverseRayFromLocation(this, endLocation);
    325     }
    326
    327     /** Create a new ray instance that starts at the projection of the given point
    328      * and continues in the direction of the line to infinity.
    329      * @param startPoint point defining the start point of the ray; the start point
    330      *      is equal to the projection of this point onto the line
    331      * @return a ray starting at the projected point and extending along this line
    332      *      to infinity
    333      * @throws IllegalArgumentException if any coordinate in {@code startPoint} is NaN or infinite
    334      * @see Lines3D#rayFromPoint(Line3D, Vector3D)
    335      */
    336     public Ray3D rayFrom(final Vector3D startPoint) {
    337         return Lines3D.rayFromPoint(this, startPoint);
    338     }
    339
    340     /** Create a new ray instance that starts at the given 1D location and continues in
    341      * the direction of the line to infinity.
    342      * @param startLocation 1D location defining the start point of the ray
    343      * @return a ray starting at the given 1D location and extending along this line
    344      *      to infinity
    345      * @throws IllegalArgumentException if {@code startLocation} is NaN or infinite
    346      * @see Lines3D#rayFromLocation(Line3D, double)
    347      */
    348     public Ray3D rayFrom(final double startLocation) {
    349         return Lines3D.rayFromLocation(this, startLocation);
    350     }
    351
    352     /** Return true if this instance should be considered equivalent to the argument, using the
    353      * given precision context for comparison. Instances are considered equivalent if they have
    354      * equivalent {@code origin}s and {@code direction}s.
    355      * @param other the point to compare with
    356      * @param ctx precision context to use for the comparison
    357      * @return true if this instance should be considered equivalent to the argument
    358      * @see Vector3D#eq(Vector3D, Precision.DoubleEquivalence)
    359      */
    360     public boolean eq(final Line3D other, final Precision.DoubleEquivalence ctx) {
    361         return getOrigin().eq(other.getOrigin(), ctx) &&
    362                 getDirection().eq(other.getDirection(), ctx);
    363     }
    364
    365     /** {@inheritDoc} */
    366     @Override
    367     public int hashCode() {
    368         return Objects.hash(origin, direction, precision);
    369     }
    370
    371     /** {@inheritDoc} */
    372     @Override
    373     public boolean equals(final Object obj) {
    374         if (this == obj) {
    375             return true;
    376         }
    377         if (!(obj instanceof Line3D)) {
    378             return false;
    379         }
    380         final Line3D other = (Line3D) obj;
    381         return this.origin.equals(other.origin) &&
    382                 this.direction.equals(other.direction) &&
    383                 this.precision.equals(other.precision);
    384     }
    385
    386     /** {@inheritDoc} */
    387     @Override
    388     public String toString() {
    389         return MessageFormat.format(TO_STRING_FORMAT,
    390                 getClass().getSimpleName(),
    391                 getOrigin(),
    392                 getDirection());
    393     }
    394
    395     /** Class containing a transformed line instance along with a subspace (1D) transform. The subspace
    396      * transform produces the equivalent of the 3D transform in 1D.
    397      */
    398     public static final class SubspaceTransform {
    399         /** The transformed line. */
    400         private final Line3D line;
    401
    402         /** The subspace transform instance. */
    403         private final AffineTransformMatrix1D transform;
    404
    405         /** Simple constructor.
    406          * @param line the transformed line
    407          * @param transform 1D transform that can be applied to subspace points
    408          */
    409         public SubspaceTransform(final Line3D line, final AffineTransformMatrix1D transform) {
    410             this.line = line;
    411             this.transform = transform;
    412         }
    413
    414         /** Get the transformed line instance.
    415          * @return the transformed line instance
    416          */
    417         public Line3D getLine() {
    418             return line;
    419         }
    420
    421         /** Get the 1D transform that can be applied to subspace points. This transform can be used
    422          * to perform the equivalent of the 3D transform in 1D space.
    423          * @return the subspace transform instance
    424          */
    425         public AffineTransformMatrix1D getTransform() {
    426             return transform;
    427         }
    428     }
    429 }