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  
21  import org.apache.commons.geometry.euclidean.oned.Interval;
22  import org.apache.commons.geometry.euclidean.oned.Vector1D;
23  import org.apache.commons.geometry.euclidean.threed.Vector3D;
24  import org.apache.commons.numbers.core.Precision;
25  
26  /** Class containing factory methods for constructing {@link Line3D} and {@link LineSubset3D} instances.
27   */
28  public final class Lines3D {
29  
30      /** Utility class; no instantiation. */
31      private Lines3D() {
32      }
33  
34      /** Create a new line instance from two points that lie on the line. The line
35       * direction points from the first point to the second point.
36       * @param p1 first point on the line
37       * @param p2 second point on the line
38       * @param precision floating point precision context
39       * @return a new line instance that contains both of the given point and that has
40       *      a direction going from the first point to the second point
41       * @throws IllegalArgumentException if the points lie too close to create a non-zero direction vector
42       */
43      public static Line3D fromPoints(final Vector3D p1, final Vector3D p2,
44              final Precision.DoubleEquivalence precision) {
45          return fromPointAndDirection(p1, p1.vectorTo(p2), precision);
46      }
47  
48      /** Create a new line instance from a point and a direction.
49       * @param pt a point lying on the line
50       * @param dir the direction of the line
51       * @param precision floating point precision context
52       * @return a new line instance that contains the given point and points in the
53       *      given direction
54       * @throws IllegalArgumentException if {@code dir} has zero length, as evaluated by the
55       *      given precision context
56       */
57      public static Line3D fromPointAndDirection(final Vector3D pt, final Vector3D dir,
58              final Precision.DoubleEquivalence precision) {
59          if (dir.isZero(precision)) {
60              throw new IllegalArgumentException("Line direction cannot be zero");
61          }
62  
63          final Vector3D normDirection = dir.normalize();
64          final Vector3D origin = pt.reject(normDirection);
65  
66          return new Line3D(origin, normDirection, precision);
67      }
68  
69      /** Construct a ray from a start point and a direction.
70       * @param startPoint ray start point
71       * @param direction ray direction
72       * @param precision precision context used for floating point comparisons
73       * @return a new ray instance with the given start point and direction
74       * @throws IllegalArgumentException If {@code direction} has zero length, as evaluated by the
75       *      given precision context
76       * @see Lines3D#fromPointAndDirection(Vector3D, Vector3D, Precision.DoubleEquivalence)
77       */
78      public static Ray3D rayFromPointAndDirection(final Vector3D startPoint, final Vector3D direction,
79              final Precision.DoubleEquivalence precision) {
80          final Line3D line = Lines3D.fromPointAndDirection(startPoint, direction, precision);
81  
82          return new Ray3D(line, startPoint);
83      }
84  
85      /** Construct a ray starting at the given point and continuing to infinity in the direction
86       * of {@code line}. The given point is projected onto the line.
87       * @param line line for the ray
88       * @param startPoint start point for the ray
89       * @return a new ray instance starting at the given point and continuing in the direction of
90       *      {@code line}
91       * @throws IllegalArgumentException if any coordinate in {@code startPoint} is NaN or infinite
92       */
93      public static Ray3D rayFromPoint(final Line3D line, final Vector3D startPoint) {
94          return rayFromLocation(line, line.abscissa(startPoint));
95      }
96  
97      /** Construct a ray starting at the given 1D location on {@code line} and continuing in the
98       * direction of the line to infinity.
99       * @param line line for the ray
100      * @param startLocation 1D location of the ray start point
101      * @return a new ray instance starting at the given 1D location and continuing to infinity
102      *      along {@code line}
103      * @throws IllegalArgumentException if {@code startLocation} is NaN or infinite
104      */
105     public static Ray3D rayFromLocation(final Line3D line, final double startLocation) {
106         if (!Double.isFinite(startLocation)) {
107             throw new IllegalArgumentException("Invalid ray start location: " + startLocation);
108         }
109 
110         return new Ray3D(line, startLocation);
111     }
112 
113     /** Construct a reverse ray from an end point and a line direction.
114      * @param endPoint instance end point
115      * @param lineDirection line direction
116      * @param precision precision context used for floating point comparisons
117      * @return a new reverse ray with the given end point and line direction
118      * @throws IllegalArgumentException If {@code lineDirection} has zero length, as evaluated by the
119      *      given precision context
120      * @see Lines3D#fromPointAndDirection(Vector3D, Vector3D, Precision.DoubleEquivalence)
121      */
122     public static ReverseRay3D reverseRayFromPointAndDirection(final Vector3D endPoint, final Vector3D lineDirection,
123             final Precision.DoubleEquivalence precision) {
124         final Line3D line = Lines3D.fromPointAndDirection(endPoint, lineDirection, precision);
125 
126         return new ReverseRay3D(line, endPoint);
127     }
128 
129     /** Construct a reverse ray starting at infinity and continuing in the direction of {@code line}
130      * to the given end point. The point is projected onto the line.
131      * @param line line for the instance
132      * @param endPoint end point for the instance
133      * @return a new reverse ray starting at infinity and continuing along the line to {@code endPoint}
134      * @throws IllegalArgumentException if any coordinate in {@code endPoint} is NaN or infinite
135      */
136     public static ReverseRay3D reverseRayFromPoint(final Line3D line, final Vector3D endPoint) {
137         return reverseRayFromLocation(line, line.abscissa(endPoint));
138     }
139 
140     /** Construct a reverse ray starting at infinity and continuing in the direction of {@code line}
141      * to the given 1D end location.
142      * @param line line for the instance
143      * @param endLocation 1D location of the instance end point
144      * @return a new reverse ray starting infinity and continuing in the direction of {@code line}
145      *      to the given 1D end location
146      * @throws IllegalArgumentException if {@code endLocation} is NaN or infinite
147      */
148     public static ReverseRay3D reverseRayFromLocation(final Line3D line, final double endLocation) {
149         if (!Double.isFinite(endLocation)) {
150             throw new IllegalArgumentException("Invalid reverse ray end location: " + endLocation);
151         }
152 
153         return new ReverseRay3D(line, endLocation);
154     }
155 
156     /** Construct a new line segment from two points. A new line is created for the segment and points in the
157      * direction from {@code startPoint} to {@code endPoint}.
158      * @param startPoint segment start point
159      * @param endPoint segment end point
160      * @param precision precision context to use for floating point comparisons
161      * @return a new line segment instance with the given start and end points
162      * @throws IllegalArgumentException If the vector between {@code startPoint} and {@code endPoint} has zero length,
163      *      as evaluated by the given precision context
164      * @see Lines3D#fromPoints(Vector3D, Vector3D, Precision.DoubleEquivalence)
165      */
166     public static Segment3D segmentFromPoints(final Vector3D startPoint, final Vector3D endPoint,
167             final Precision.DoubleEquivalence precision) {
168         final Line3D line = Lines3D.fromPoints(startPoint, endPoint, precision);
169 
170         // we know that the points lie on the line and are in increasing abscissa order
171         // since they were used to create the line
172         return new Segment3D(line, startPoint, endPoint);
173     }
174 
175     /** Construct a new line segment from a line and a pair of points. The returned segment represents
176      * all points on the line between the projected locations of {@code a} and {@code b}. The points may
177      * be given in any order.
178      * @param line line forming the base of the segment
179      * @param a first point
180      * @param b second point
181      * @return a new line segment representing the points between the projected locations of {@code a}
182      *      and {@code b} on the given line
183      * @throws IllegalArgumentException if either point contains NaN or infinite coordinate values)
184      */
185     public static Segment3D segmentFromPoints(final Line3D line, final Vector3D a, final Vector3D b) {
186         return segmentFromLocations(line, line.abscissa(a), line.abscissa(b));
187     }
188 
189     /** Construct a new line segment from a pair of 1D locations on a line. The returned line
190      * segment consists of all points between the two locations, regardless of the order the
191      * arguments are given.
192      * @param line line forming the base of the segment
193      * @param a first 1D location on the line
194      * @param b second 1D location on the line
195      * @return a new line segment representing the points between {@code a} and {@code b} on
196      *      the given line
197      * @throws IllegalArgumentException if either of the locations is NaN or infinite
198      */
199     public static Segment3D segmentFromLocations(final Line3D line, final double a, final double b) {
200 
201         if (Double.isFinite(a) && Double.isFinite(b)) {
202             final double min = Math.min(a, b);
203             final double max = Math.max(a, b);
204 
205             return new Segment3D(line, min, max);
206         }
207 
208         throw new IllegalArgumentException(
209                 MessageFormat.format("Invalid line segment locations: {0}, {1}",
210                         Double.toString(a), Double.toString(b)));
211     }
212 
213     /** Create a {@link LineConvexSubset3D} spanning the entire line. In other words, the returned
214      * subset is infinite and contains all points on the given line.
215      * @param line the line to span
216      * @return a convex subset spanning the entire line
217      */
218     public static LineConvexSubset3D span(final Line3D line) {
219         return new LineSpanningSubset3D(line);
220     }
221 
222     /** Create a line convex subset from a line and a 1D interval on the line.
223      * @param line the line containing the subset
224      * @param interval 1D interval on the line
225      * @return a line convex subset defined by the given line and interval
226      */
227     public static LineConvexSubset3D subsetFromInterval(final Line3D line, final Interval interval) {
228         return subsetFromInterval(line, interval.getMin(), interval.getMax());
229     }
230 
231     /** Create a line convex subset from a line and a 1D interval on the line.
232      * @param line the line containing the subset
233      * @param a first 1D location on the line
234      * @param b second 1D location on the line
235      * @return a line convex subset defined by the given line and interval
236      */
237     public static LineConvexSubset3D subsetFromInterval(final Line3D line, final double a, final double b) {
238         final double min = Math.min(a, b);
239         final double max = Math.max(a, b);
240 
241         final boolean hasMin = Double.isFinite(min);
242         final boolean hasMax = Double.isFinite(max);
243 
244         if (hasMin) {
245             if (hasMax) {
246                 // has both
247                 return new Segment3D(line, min, max);
248             }
249             // min only
250             return new Ray3D(line, min);
251         } else if (hasMax) {
252             // max only
253             return new ReverseRay3D(line, max);
254         } else if (Double.isInfinite(min) && Double.isInfinite(max) && Double.compare(min, max) < 0) {
255             return new LineSpanningSubset3D(line);
256         }
257 
258         throw new IllegalArgumentException(MessageFormat.format(
259                 "Invalid line convex subset interval: {0}, {1}", Double.toString(a), Double.toString(b)));
260     }
261 
262     /** Create a line convex subset from a line and a 1D interval on the line.
263      * @param line the line containing the subset
264      * @param a first 1D point on the line; must not be null
265      * @param b second 1D point on the line; must not be null
266      * @return a line convex subset defined by the given line and interval
267      */
268     public static LineConvexSubset3D subsetFromInterval(final Line3D line, final Vector1D a, final Vector1D b) {
269         return subsetFromInterval(line, a.getX(), b.getX());
270     }
271 }