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 }