001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.geometry.euclidean.threed.line;
018
019import java.text.MessageFormat;
020
021import org.apache.commons.geometry.euclidean.oned.Interval;
022import org.apache.commons.geometry.euclidean.oned.Vector1D;
023import org.apache.commons.geometry.euclidean.threed.Vector3D;
024import org.apache.commons.numbers.core.Precision;
025
026/** Class containing factory methods for constructing {@link Line3D} and {@link LineSubset3D} instances.
027 */
028public final class Lines3D {
029
030    /** Utility class; no instantiation. */
031    private Lines3D() {
032    }
033
034    /** Create a new line instance from two points that lie on the line. The line
035     * direction points from the first point to the second point.
036     * @param p1 first point on the line
037     * @param p2 second point on the line
038     * @param precision floating point precision context
039     * @return a new line instance that contains both of the given point and that has
040     *      a direction going from the first point to the second point
041     * @throws IllegalArgumentException if the points lie too close to create a non-zero direction vector
042     */
043    public static Line3D fromPoints(final Vector3D p1, final Vector3D p2,
044            final Precision.DoubleEquivalence precision) {
045        return fromPointAndDirection(p1, p1.vectorTo(p2), precision);
046    }
047
048    /** Create a new line instance from a point and a direction.
049     * @param pt a point lying on the line
050     * @param dir the direction of the line
051     * @param precision floating point precision context
052     * @return a new line instance that contains the given point and points in the
053     *      given direction
054     * @throws IllegalArgumentException if {@code dir} has zero length, as evaluated by the
055     *      given precision context
056     */
057    public static Line3D fromPointAndDirection(final Vector3D pt, final Vector3D dir,
058            final Precision.DoubleEquivalence precision) {
059        if (dir.isZero(precision)) {
060            throw new IllegalArgumentException("Line direction cannot be zero");
061        }
062
063        final Vector3D normDirection = dir.normalize();
064        final Vector3D origin = pt.reject(normDirection);
065
066        return new Line3D(origin, normDirection, precision);
067    }
068
069    /** Construct a ray from a start point and a direction.
070     * @param startPoint ray start point
071     * @param direction ray direction
072     * @param precision precision context used for floating point comparisons
073     * @return a new ray instance with the given start point and direction
074     * @throws IllegalArgumentException If {@code direction} has zero length, as evaluated by the
075     *      given precision context
076     * @see Lines3D#fromPointAndDirection(Vector3D, Vector3D, Precision.DoubleEquivalence)
077     */
078    public static Ray3D rayFromPointAndDirection(final Vector3D startPoint, final Vector3D direction,
079            final Precision.DoubleEquivalence precision) {
080        final Line3D line = Lines3D.fromPointAndDirection(startPoint, direction, precision);
081
082        return new Ray3D(line, startPoint);
083    }
084
085    /** Construct a ray starting at the given point and continuing to infinity in the direction
086     * of {@code line}. The given point is projected onto the line.
087     * @param line line for the ray
088     * @param startPoint start point for the ray
089     * @return a new ray instance starting at the given point and continuing in the direction of
090     *      {@code line}
091     * @throws IllegalArgumentException if any coordinate in {@code startPoint} is NaN or infinite
092     */
093    public static Ray3D rayFromPoint(final Line3D line, final Vector3D startPoint) {
094        return rayFromLocation(line, line.abscissa(startPoint));
095    }
096
097    /** Construct a ray starting at the given 1D location on {@code line} and continuing in the
098     * direction of the line to infinity.
099     * @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}