Line3D.java

  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. import java.text.MessageFormat;
  19. import java.util.Objects;

  20. import org.apache.commons.geometry.core.Embedding;
  21. import org.apache.commons.geometry.core.Transform;
  22. import org.apache.commons.geometry.euclidean.oned.AffineTransformMatrix1D;
  23. import org.apache.commons.geometry.euclidean.oned.Vector1D;
  24. import org.apache.commons.geometry.euclidean.threed.Vector3D;
  25. import org.apache.commons.numbers.core.Precision;

  26. /** Class representing a line in 3D space.
  27.  *
  28.  * <p>Instances of this class are guaranteed to be immutable.</p>
  29.  * @see Lines3D
  30.  */
  31. public final class Line3D implements Embedding<Vector3D, Vector1D> {

  32.     /** Format string for creating line string representations. */
  33.     static final String TO_STRING_FORMAT = "{0}[origin= {1}, direction= {2}]";

  34.     /** Line point closest to the origin. */
  35.     private final Vector3D origin;

  36.     /** Line direction. */
  37.     private final Vector3D direction;

  38.     /** Precision context used to compare floating point numbers. */
  39.     private final Precision.DoubleEquivalence precision;

  40.     /** Simple constructor.
  41.      * @param origin the origin of the line, meaning the point on the line closest to the origin of the
  42.      *      3D space
  43.      * @param direction the direction of the line
  44.      * @param precision precision context used to compare floating point numbers
  45.      */
  46.     Line3D(final Vector3D origin, final Vector3D direction, final Precision.DoubleEquivalence precision) {
  47.         this.origin = origin;
  48.         this.direction = direction;
  49.         this.precision = precision;
  50.     }

  51.     /** Get the line point closest to the origin.
  52.      * @return line point closest to the origin
  53.      */
  54.     public Vector3D getOrigin() {
  55.         return origin;
  56.     }

  57.     /** Get the normalized direction vector.
  58.      * @return normalized direction vector
  59.      */
  60.     public Vector3D getDirection() {
  61.         return direction;
  62.     }

  63.     /** Get the object used to determine floating point equality for this instance.
  64.      * @return the floating point precision context for the instance
  65.      */
  66.     public Precision.DoubleEquivalence getPrecision() {
  67.         return precision;
  68.     }

  69.     /** Return a line containing the same points as this instance but pointing
  70.      * in the opposite direction.
  71.      * @return an instance containing the same points but pointing in the opposite
  72.      *      direction
  73.      */
  74.     public Line3D reverse() {
  75.         return new Line3D(origin, direction.negate(), precision);
  76.     }

  77.     /** Transform this instance.
  78.      * @param transform object used to transform the instance
  79.      * @return a transformed instance
  80.      */
  81.     public Line3D transform(final Transform<Vector3D> transform) {
  82.         final Vector3D p1 = transform.apply(origin);
  83.         final Vector3D p2 = transform.apply(origin.add(direction));

  84.         return Lines3D.fromPoints(p1, p2, precision);
  85.     }

  86.     /** Get an object containing the current line transformed by the argument along with a
  87.      * 1D transform that can be applied to subspace points. The subspace transform transforms
  88.      * subspace points such that their 3D location in the transformed line is the same as their
  89.      * 3D location in the original line after the 3D transform is applied. For example, consider
  90.      * the code below:
  91.      * <pre>
  92.      *      SubspaceTransform st = line.subspaceTransform(transform);
  93.      *
  94.      *      Vector1D subPt = Vector1D.of(1);
  95.      *
  96.      *      Vector3D a = transform.apply(line.toSpace(subPt)); // transform in 3D space
  97.      *      Vector3D b = st.getLine().toSpace(st.getTransform().apply(subPt)); // transform in 1D space
  98.      * </pre>
  99.      * At the end of execution, the points {@code a} (which was transformed using the original
  100.      * 3D transform) and {@code b} (which was transformed in 1D using the subspace transform)
  101.      * are equivalent.
  102.      *
  103.      * @param transform the transform to apply to this instance
  104.      * @return an object containing the transformed line along with a transform that can be applied
  105.      *      to subspace points
  106.      * @see #transform(Transform)
  107.      */
  108.     public SubspaceTransform subspaceTransform(final Transform<Vector3D> transform) {
  109.         final Vector3D p1 = transform.apply(origin);
  110.         final Vector3D p2 = transform.apply(origin.add(direction));

  111.         final Line3D tLine = Lines3D.fromPoints(p1, p2, precision);

  112.         final Vector1D tSubspaceOrigin = tLine.toSubspace(p1);
  113.         final Vector1D tSubspaceDirection = tSubspaceOrigin.vectorTo(tLine.toSubspace(p2));

  114.         final double translation = tSubspaceOrigin.getX();
  115.         final double scale = tSubspaceDirection.getX();

  116.         final AffineTransformMatrix1D subspaceTransform = AffineTransformMatrix1D.of(scale, translation);

  117.         return new SubspaceTransform(tLine, subspaceTransform);
  118.     }

  119.     /** Get the abscissa of the given point on the line. The abscissa represents
  120.      * the distance the projection of the point on the line is from the line's
  121.      * origin point (the point on the line closest to the origin of the
  122.      * 2D space). Abscissa values increase in the direction of the line. This method
  123.      * is exactly equivalent to {@link #toSubspace(Vector3D)} except that this method
  124.      * returns a double instead of a {@link Vector1D}.
  125.      * @param pt point to compute the abscissa for
  126.      * @return abscissa value of the point
  127.      * @see #toSubspace(Vector3D)
  128.      */
  129.     public double abscissa(final Vector3D pt) {
  130.         return pt.subtract(origin).dot(direction);
  131.     }

  132.     /** Get one point from the line.
  133.      * @param abscissa desired abscissa for the point
  134.      * @return one point belonging to the line, at specified abscissa
  135.      */
  136.     public Vector3D pointAt(final double abscissa) {
  137.         return Vector3D.Sum.of(origin)
  138.                 .addScaled(abscissa, direction)
  139.                 .get();
  140.     }

  141.     /** {@inheritDoc} */
  142.     @Override
  143.     public Vector1D toSubspace(final Vector3D pt) {
  144.         return Vector1D.of(abscissa(pt));
  145.     }

  146.     /** {@inheritDoc} */
  147.     @Override
  148.     public Vector3D toSpace(final Vector1D pt) {
  149.         return toSpace(pt.getX());
  150.     }

  151.     /** Get the 3 dimensional point at the given abscissa position
  152.      * on the line.
  153.      * @param abscissa location on the line
  154.      * @return the 3 dimensional point at the given abscissa position
  155.      *      on the line
  156.      */
  157.     public Vector3D toSpace(final double abscissa) {
  158.         return pointAt(abscissa);
  159.     }

  160.     /** Check if the instance is similar to another line.
  161.      * <p>Lines are considered similar if they contain the same
  162.      * points. This does not mean they are equal since they can have
  163.      * opposite directions.</p>
  164.      * @param line line to which instance should be compared
  165.      * @return true if the lines are similar
  166.      */
  167.     public boolean isSimilarTo(final Line3D line) {
  168.         final double angle = direction.angle(line.direction);
  169.         return (precision.eqZero(angle) || precision.eq(Math.abs(angle), Math.PI)) &&
  170.                 contains(line.origin);
  171.     }

  172.     /** Check if the instance contains a point.
  173.      * @param pt point to check
  174.      * @return true if p belongs to the line
  175.      */
  176.     public boolean contains(final Vector3D pt) {
  177.         return precision.eqZero(distance(pt));
  178.     }

  179.     /** Compute the distance between the instance and a point.
  180.      * @param pt to check
  181.      * @return distance between the instance and the point
  182.      */
  183.     public double distance(final Vector3D pt) {
  184.         final Vector3D delta = pt.subtract(origin);
  185.         final Vector3D orthogonal = delta.reject(direction);

  186.         return orthogonal.norm();
  187.     }

  188.     /** Compute the shortest distance between the instance and another line.
  189.      * @param line line to check against the instance
  190.      * @return shortest distance between the instance and the line
  191.      */
  192.     public double distance(final Line3D line) {

  193.         final Vector3D normal = direction.cross(line.direction);
  194.         final double norm = normal.norm();

  195.         if (precision.eqZero(norm)) {
  196.             // the lines are parallel
  197.             return distance(line.origin);
  198.         }

  199.         // signed separation of the two parallel planes that contains the lines
  200.         final double offset = line.origin.subtract(origin).dot(normal) / norm;

  201.         return Math.abs(offset);
  202.     }

  203.     /** Compute the point of the instance closest to another line.
  204.      * @param line line to check against the instance
  205.      * @return point of the instance closest to another line
  206.      */
  207.     public Vector3D closest(final Line3D line) {

  208.         final double cos = direction.dot(line.direction);
  209.         final double n = 1 - cos * cos;

  210.         if (precision.eqZero(n)) {
  211.             // the lines are parallel
  212.             return origin;
  213.         }

  214.         final Vector3D delta = line.origin.subtract(origin);
  215.         final double a = delta.dot(direction);
  216.         final double b = delta.dot(line.direction);

  217.         return Vector3D.Sum.of(origin)
  218.                 .addScaled((a - (b * cos)) / n, direction)
  219.                 .get();
  220.     }

  221.     /** Get the intersection point of the instance and another line.
  222.      * @param line other line
  223.      * @return intersection point of the instance and the other line
  224.      * or null if there are no intersection points
  225.      */
  226.     public Vector3D intersection(final Line3D line) {
  227.         final Vector3D closestPt = closest(line);
  228.         return line.contains(closestPt) ? closestPt : null;
  229.     }

  230.     /** Return a new infinite line subset representing the entire line.
  231.      * @return a new infinite line subset representing the entire line
  232.      * @see Lines3D#span(Line3D)
  233.      */
  234.     public LineConvexSubset3D span() {
  235.         return Lines3D.span(this);
  236.     }

  237.     /** Create a new line segment from the given 1D interval. The returned line
  238.      * segment consists of all points between the two locations, regardless of the order the
  239.      * arguments are given.
  240.      * @param a first 1D location for the interval
  241.      * @param b second 1D location for the interval
  242.      * @return a new line segment on this line
  243.      * @throws IllegalArgumentException if either of the locations is NaN or infinite
  244.      * @see Lines3D#segmentFromLocations(Line3D, double, double)
  245.      */
  246.     public Segment3D segment(final double a, final double b) {
  247.         return Lines3D.segmentFromLocations(this, a, b);
  248.     }

  249.     /** Create a new line segment from two points. The returned segment represents all points on this line
  250.      * between the projected locations of {@code a} and {@code b}. The points may be given in any order.
  251.      * @param a first point
  252.      * @param b second point
  253.      * @return a new line segment on this line
  254.      * @throws IllegalArgumentException if either point contains NaN or infinite coordinate values
  255.      * @see Lines3D#segmentFromPoints(Line3D, Vector3D, Vector3D)
  256.      */
  257.     public Segment3D segment(final Vector3D a, final Vector3D b) {
  258.         return Lines3D.segmentFromPoints(this, a, b);
  259.     }

  260.     /** Create a new line convex subset that starts at infinity and continues along
  261.      * the line up to the projection of the given end point.
  262.      * @param endPoint point defining the end point of the line subset; the end point
  263.      *      is equal to the projection of this point onto the line
  264.      * @return a new, half-open line subset that ends at the given point
  265.      * @throws IllegalArgumentException if any coordinate in {@code endPoint} is NaN or infinite
  266.      * @see Lines3D#reverseRayFromPoint(Line3D, Vector3D)
  267.      */
  268.     public ReverseRay3D reverseRayTo(final Vector3D endPoint) {
  269.         return Lines3D.reverseRayFromPoint(this, endPoint);
  270.     }

  271.     /** Create a new line convex subset that starts at infinity and continues along
  272.      * the line up to the given 1D location.
  273.      * @param endLocation the 1D location of the end of the half-line
  274.      * @return a new, half-open line subset that ends at the given 1D location
  275.      * @throws IllegalArgumentException if {@code endLocation} is NaN or infinite
  276.      * @see Lines3D#reverseRayFromLocation(Line3D, double)
  277.      */
  278.     public ReverseRay3D reverseRayTo(final double endLocation) {
  279.         return Lines3D.reverseRayFromLocation(this, endLocation);
  280.     }

  281.     /** Create a new ray instance that starts at the projection of the given point
  282.      * and continues in the direction of the line to infinity.
  283.      * @param startPoint point defining the start point of the ray; the start point
  284.      *      is equal to the projection of this point onto the line
  285.      * @return a ray starting at the projected point and extending along this line
  286.      *      to infinity
  287.      * @throws IllegalArgumentException if any coordinate in {@code startPoint} is NaN or infinite
  288.      * @see Lines3D#rayFromPoint(Line3D, Vector3D)
  289.      */
  290.     public Ray3D rayFrom(final Vector3D startPoint) {
  291.         return Lines3D.rayFromPoint(this, startPoint);
  292.     }

  293.     /** Create a new ray instance that starts at the given 1D location and continues in
  294.      * the direction of the line to infinity.
  295.      * @param startLocation 1D location defining the start point of the ray
  296.      * @return a ray starting at the given 1D location and extending along this line
  297.      *      to infinity
  298.      * @throws IllegalArgumentException if {@code startLocation} is NaN or infinite
  299.      * @see Lines3D#rayFromLocation(Line3D, double)
  300.      */
  301.     public Ray3D rayFrom(final double startLocation) {
  302.         return Lines3D.rayFromLocation(this, startLocation);
  303.     }

  304.     /** Return true if this instance should be considered equivalent to the argument, using the
  305.      * given precision context for comparison. Instances are considered equivalent if they have
  306.      * equivalent {@code origin}s and {@code direction}s.
  307.      * @param other the point to compare with
  308.      * @param ctx precision context to use for the comparison
  309.      * @return true if this instance should be considered equivalent to the argument
  310.      * @see Vector3D#eq(Vector3D, Precision.DoubleEquivalence)
  311.      */
  312.     public boolean eq(final Line3D other, final Precision.DoubleEquivalence ctx) {
  313.         return getOrigin().eq(other.getOrigin(), ctx) &&
  314.                 getDirection().eq(other.getDirection(), ctx);
  315.     }

  316.     /** {@inheritDoc} */
  317.     @Override
  318.     public int hashCode() {
  319.         return Objects.hash(origin, direction, precision);
  320.     }

  321.     /** {@inheritDoc} */
  322.     @Override
  323.     public boolean equals(final Object obj) {
  324.         if (this == obj) {
  325.             return true;
  326.         }
  327.         if (!(obj instanceof Line3D)) {
  328.             return false;
  329.         }
  330.         final Line3D other = (Line3D) obj;
  331.         return this.origin.equals(other.origin) &&
  332.                 this.direction.equals(other.direction) &&
  333.                 this.precision.equals(other.precision);
  334.     }

  335.     /** {@inheritDoc} */
  336.     @Override
  337.     public String toString() {
  338.         return MessageFormat.format(TO_STRING_FORMAT,
  339.                 getClass().getSimpleName(),
  340.                 getOrigin(),
  341.                 getDirection());
  342.     }

  343.     /** Class containing a transformed line instance along with a subspace (1D) transform. The subspace
  344.      * transform produces the equivalent of the 3D transform in 1D.
  345.      */
  346.     public static final class SubspaceTransform {
  347.         /** The transformed line. */
  348.         private final Line3D line;

  349.         /** The subspace transform instance. */
  350.         private final AffineTransformMatrix1D transform;

  351.         /** Simple constructor.
  352.          * @param line the transformed line
  353.          * @param transform 1D transform that can be applied to subspace points
  354.          */
  355.         public SubspaceTransform(final Line3D line, final AffineTransformMatrix1D transform) {
  356.             this.line = line;
  357.             this.transform = transform;
  358.         }

  359.         /** Get the transformed line instance.
  360.          * @return the transformed line instance
  361.          */
  362.         public Line3D getLine() {
  363.             return line;
  364.         }

  365.         /** Get the 1D transform that can be applied to subspace points. This transform can be used
  366.          * to perform the equivalent of the 3D transform in 1D space.
  367.          * @return the subspace transform instance
  368.          */
  369.         public AffineTransformMatrix1D getTransform() {
  370.             return transform;
  371.         }
  372.     }
  373. }