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.twod;
018
019import java.text.MessageFormat;
020
021import org.apache.commons.geometry.euclidean.oned.Interval;
022import org.apache.commons.numbers.core.Precision;
023
024/** Class containing factory methods for constructing {@link Line} and {@link LineSubset} instances.
025 */
026public final class Lines {
027
028    /** Utility class; no instantiation. */
029    private Lines() {
030    }
031
032    /** Create a line from two points lying on the line. The line points in the direction
033     * from {@code p1} to {@code p2}.
034     * @param p1 first point
035     * @param p2 second point
036     * @param precision precision context used to compare floating point values
037     * @return new line containing {@code p1} and {@code p2} and pointing in the direction
038     *      from {@code p1} to {@code p2}
039     * @throws IllegalArgumentException If the vector between {@code p1} and {@code p2} has zero length,
040     *      as evaluated by the given precision context
041     */
042    public static Line fromPoints(final Vector2D p1, final Vector2D p2, final Precision.DoubleEquivalence precision) {
043        return fromPointAndDirection(p1, p1.vectorTo(p2), precision);
044    }
045
046    /** Create a line from a point and direction.
047     * @param pt point belonging to the line
048     * @param dir the direction of the line
049     * @param precision precision context used to compare floating point values
050     * @return new line containing {@code pt} and pointing in direction {@code dir}
051     * @throws IllegalArgumentException If {@code dir} has zero length, as evaluated by the
052     *      given precision context
053     */
054    public static Line fromPointAndDirection(final Vector2D pt, final Vector2D dir,
055            final Precision.DoubleEquivalence precision) {
056        if (dir.isZero(precision)) {
057            throw new IllegalArgumentException("Line direction cannot be zero");
058        }
059
060        final Vector2D.Unit normalizedDir = dir.normalize();
061        final double originOffset = normalizedDir.signedArea(pt);
062
063        return new Line(normalizedDir, originOffset, precision);
064    }
065
066    /** Create a line from a point lying on the line and an angle relative to the abscissa (x) axis. Note that the
067     * line does not need to intersect the x-axis; the given angle is simply relative to it.
068     * @param pt point belonging to the line
069     * @param angle angle of the line with respect to abscissa (x) axis, in radians
070     * @param precision precision context used to compare floating point values
071     * @return new line containing {@code pt} and forming the given angle with the
072     *      abscissa (x) axis.
073     */
074    public static Line fromPointAndAngle(final Vector2D pt, final double angle,
075            final Precision.DoubleEquivalence precision) {
076        final Vector2D.Unit dir = Vector2D.Unit.from(Math.cos(angle), Math.sin(angle));
077        return fromPointAndDirection(pt, dir, precision);
078    }
079
080    /** Construct a ray from a start point and a direction.
081     * @param startPoint ray start point
082     * @param direction ray direction
083     * @param precision precision context used for floating point comparisons
084     * @return a new ray instance with the given start point and direction
085     * @throws IllegalArgumentException If {@code direction} has zero length, as evaluated by the
086     *      given precision context
087     * @see #fromPointAndDirection(Vector2D, Vector2D, Precision.DoubleEquivalence)
088     */
089    public static Ray rayFromPointAndDirection(final Vector2D startPoint, final Vector2D direction,
090            final Precision.DoubleEquivalence precision) {
091        final Line line = Lines.fromPointAndDirection(startPoint, direction, precision);
092
093        return new Ray(line, startPoint);
094    }
095
096    /** Construct a ray starting at the given point and continuing to infinity in the direction
097     * of {@code line}. The given point is projected onto the line.
098     * @param line line for the ray
099     * @param startPoint start point for the ray
100     * @return a new ray instance starting at the given point and continuing in the direction of
101     *      {@code line}
102     * @throws IllegalArgumentException if any coordinate in {@code startPoint} is NaN or infinite
103     */
104    public static Ray rayFromPoint(final Line line, final Vector2D startPoint) {
105        if (!startPoint.isFinite()) {
106            throw new IllegalArgumentException("Invalid ray start point: " + startPoint);
107        }
108        return new Ray(line, line.project(startPoint));
109    }
110
111    /** Construct a ray starting at the given 1D location on {@code line} and continuing in the
112     * direction of the line to infinity.
113     * @param line line for the ray
114     * @param startLocation 1D location of the ray start point
115     * @return a new ray instance starting at the given 1D location and continuing to infinity
116     *      along {@code line}
117     * @throws IllegalArgumentException if {@code startLocation} is NaN or infinite
118     */
119    public static Ray rayFromLocation(final Line line, final double startLocation) {
120        if (!Double.isFinite(startLocation)) {
121            throw new IllegalArgumentException("Invalid ray start location: " + startLocation);
122        }
123        return new Ray(line, line.toSpace(startLocation));
124    }
125
126    /** Construct a reverse ray from an end point and a line direction.
127     * @param endPoint instance end point
128     * @param lineDirection line direction
129     * @param precision precision context used for floating point comparisons
130     * @return a new instance with the given end point and line direction
131     * @throws IllegalArgumentException If {@code lineDirection} has zero length, as evaluated by the
132     *      given precision context
133     * @see #fromPointAndDirection(Vector2D, Vector2D, Precision.DoubleEquivalence)
134     */
135    public static ReverseRay reverseRayFromPointAndDirection(final Vector2D endPoint, final Vector2D lineDirection,
136            final Precision.DoubleEquivalence precision) {
137        final Line line = Lines.fromPointAndDirection(endPoint, lineDirection, precision);
138
139        return new ReverseRay(line, endPoint);
140    }
141
142    /** Construct a reverse ray starting at infinity and continuing in the direction of {@code line}
143     * to the given end point. The point is projected onto the line.
144     * @param line line for the instance
145     * @param endPoint end point for the instance
146     * @return a new instance starting at infinity and continuing along the line to {@code endPoint}
147     * @throws IllegalArgumentException if any coordinate in {@code endPoint} is NaN or infinite
148     */
149    public static ReverseRay reverseRayFromPoint(final Line line, final Vector2D endPoint) {
150        if (!endPoint.isFinite()) {
151            throw new IllegalArgumentException("Invalid reverse ray end point: " + endPoint);
152        }
153        return new ReverseRay(line, line.project(endPoint));
154    }
155
156    /** Construct a reverse ray starting at infinity and continuing in the direction of {@code line}
157     * to the given 1D end location.
158     * @param line line for the instance
159     * @param endLocation 1D location of the instance end point
160     * @return a new instance starting infinity and continuing in the direction of {@code line}
161     *      to the given 1D end location
162     * @throws IllegalArgumentException if {@code endLocation} is NaN or infinite
163     */
164    public static ReverseRay reverseRayFromLocation(final Line line, final double endLocation) {
165        if (!Double.isFinite(endLocation)) {
166            throw new IllegalArgumentException("Invalid reverse ray end location: " + endLocation);
167        }
168
169        return new ReverseRay(line, line.toSpace(endLocation));
170    }
171
172    /** Construct a new line segment from two points. A new line is created for the segment and points in the
173     * direction from {@code startPoint} to {@code endPoint}.
174     * @param startPoint segment start point
175     * @param endPoint segment end point
176     * @param precision precision context to use for floating point comparisons
177     * @return a new line segment instance with the given start and end points
178     * @throws IllegalArgumentException If the vector between {@code startPoint} and {@code endPoint} has zero length,
179     *      as evaluated by the given precision context
180     */
181    public static Segment segmentFromPoints(final Vector2D startPoint, final Vector2D endPoint,
182            final Precision.DoubleEquivalence precision) {
183        final Line line = Lines.fromPoints(startPoint, endPoint, precision);
184
185        // we know that the points lie on the line and are in increasing abscissa order
186        // since they were used to create the line
187        return new Segment(line, startPoint, endPoint);
188    }
189
190    /** Construct a new line segment from a line and a pair of points. The returned segment represents
191     * all points on the line between the projected locations of {@code a} and {@code b}. The points may
192     * be given in any order.
193     * @param line line forming the base of the segment
194     * @param a first point
195     * @param b second point
196     * @return a new line segment representing the points between the projected locations of {@code a}
197     *      and {@code b} on the given line
198     * @throws IllegalArgumentException if either point contains NaN or infinite coordinate values
199     */
200    public static Segment segmentFromPoints(final Line line, final Vector2D a, final Vector2D b) {
201        return segmentFromLocations(line, line.abscissa(a), line.abscissa(b));
202    }
203
204    /** Construct a new line segment from a pair of 1D locations on a line. The returned line
205     * segment consists of all points between the two locations, regardless of the order the
206     * arguments are given.
207     * @param line line forming the base of the segment
208     * @param a first 1D location on the line
209     * @param b second 1D location on the line
210     * @return a new line segment representing the points between {@code a} and {@code b} on
211     *      the given line
212     * @throws IllegalArgumentException if either of the locations is NaN or infinite
213     */
214    public static Segment segmentFromLocations(final Line line, final double a, final double b) {
215
216        if (Double.isFinite(a) && Double.isFinite(b)) {
217            final double min = Math.min(a, b);
218            final double max = Math.max(a, b);
219
220            return new Segment(line, line.toSpace(min), line.toSpace(max));
221        }
222
223        throw new IllegalArgumentException(
224                MessageFormat.format("Invalid line segment locations: {0}, {1}",
225                        Double.toString(a), Double.toString(b)));
226    }
227
228    /** Create a {@link LineConvexSubset} spanning the entire line. In other words, the returned
229     * subset is infinite and contains all points on the given line.
230     * @param line the line to span
231     * @return a convex subset spanning the entire line
232     */
233    public static LineConvexSubset span(final Line line) {
234        return new LineSpanningSubset(line);
235    }
236
237    /** Create a line subset from a line and a 1D interval on the line. The returned subset
238     * uses the precision context from the line and not any precision contexts referenced by the interval.
239     * @param line the line containing the subset
240     * @param interval 1D interval on the line
241     * @return a convex subset defined by the given line and interval
242     */
243    public static LineConvexSubset subsetFromInterval(final Line line, final Interval interval) {
244        return subsetFromInterval(line, interval.getMin(), interval.getMax());
245    }
246
247    /** Create a line subset from a line and a 1D interval on the line. The double values may be given in any
248     * order and support the use of infinite values. For example, the call
249     * {@code Lines.subsetFromInterval(line, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY)} will return
250     * an instance representing the full span of the line.
251     * @param line the line containing the subset
252     * @param a first 1D location on the line
253     * @param b second 1D location on the line
254     * @return a line subset defined by the given line and interval
255     * @throws IllegalArgumentException if either double value is NaN or both are infinite with the same sign
256     *      (eg, both positive infinity or both negative infinity)
257     */
258    public static LineConvexSubset subsetFromInterval(final Line line, final double a, final double b) {
259        final double min = Math.min(a, b);
260        final double max = Math.max(a, b);
261
262        final boolean hasMin = Double.isFinite(min);
263        final boolean hasMax = Double.isFinite(max);
264
265        if (hasMin) {
266            if (hasMax) {
267                // has both
268                return new Segment(line, line.toSpace(min), line.toSpace(max));
269            }
270            // min only
271            return new Ray(line, line.toSpace(min));
272        } else if (hasMax) {
273            // max only
274            return new ReverseRay(line, line.toSpace(max));
275        } else if (Double.isInfinite(min) && Double.isInfinite(max) && Double.compare(min, max) < 0) {
276            return new LineSpanningSubset(line);
277        }
278
279        throw new IllegalArgumentException(MessageFormat.format(
280                "Invalid line subset interval: {0}, {1}", Double.toString(a), Double.toString(b)));
281    }
282
283    /** Validate that the actual line is equivalent to the expected line, throwing an exception if not.
284     * @param expected the expected line
285     * @param actual the actual line
286     * @throws IllegalArgumentException if the actual line is not equivalent to the expected line
287     */
288    static void validateLinesEquivalent(final Line expected, final Line actual) {
289        if (!expected.eq(actual, expected.getPrecision())) {
290            throw new IllegalArgumentException("Arguments do not represent the same line. Expected " +
291                    expected + " but was " + actual + ".");
292        }
293    }
294}