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.twod;
18
19 import java.text.MessageFormat;
20
21 import org.apache.commons.geometry.euclidean.oned.Interval;
22 import org.apache.commons.numbers.core.Precision;
23
24 /** Class containing factory methods for constructing {@link Line} and {@link LineSubset} instances.
25 */
26 public final class Lines {
27
28 /** Utility class; no instantiation. */
29 private Lines() {
30 }
31
32 /** Create a line from two points lying on the line. The line points in the direction
33 * from {@code p1} to {@code p2}.
34 * @param p1 first point
35 * @param p2 second point
36 * @param precision precision context used to compare floating point values
37 * @return new line containing {@code p1} and {@code p2} and pointing in the direction
38 * from {@code p1} to {@code p2}
39 * @throws IllegalArgumentException If the vector between {@code p1} and {@code p2} has zero length,
40 * as evaluated by the given precision context
41 */
42 public static Line fromPoints(final Vector2D p1, final Vector2D p2, final Precision.DoubleEquivalence precision) {
43 return fromPointAndDirection(p1, p1.vectorTo(p2), precision);
44 }
45
46 /** Create a line from a point and direction.
47 * @param pt point belonging to the line
48 * @param dir the direction of the line
49 * @param precision precision context used to compare floating point values
50 * @return new line containing {@code pt} and pointing in direction {@code dir}
51 * @throws IllegalArgumentException If {@code dir} has zero length, as evaluated by the
52 * given precision context
53 */
54 public static Line fromPointAndDirection(final Vector2D pt, final Vector2D dir,
55 final Precision.DoubleEquivalence precision) {
56 if (dir.isZero(precision)) {
57 throw new IllegalArgumentException("Line direction cannot be zero");
58 }
59
60 final Vector2D.Unit normalizedDir = dir.normalize();
61 final double originOffset = normalizedDir.signedArea(pt);
62
63 return new Line(normalizedDir, originOffset, precision);
64 }
65
66 /** Create a line from a point lying on the line and an angle relative to the abscissa (x) axis. Note that the
67 * line does not need to intersect the x-axis; the given angle is simply relative to it.
68 * @param pt point belonging to the line
69 * @param angle angle of the line with respect to abscissa (x) axis, in radians
70 * @param precision precision context used to compare floating point values
71 * @return new line containing {@code pt} and forming the given angle with the
72 * abscissa (x) axis.
73 */
74 public static Line fromPointAndAngle(final Vector2D pt, final double angle,
75 final Precision.DoubleEquivalence precision) {
76 final Vector2D.Unit dir = Vector2D.Unit.from(Math.cos(angle), Math.sin(angle));
77 return fromPointAndDirection(pt, dir, precision);
78 }
79
80 /** Construct a ray from a start point and a direction.
81 * @param startPoint ray start point
82 * @param direction ray direction
83 * @param precision precision context used for floating point comparisons
84 * @return a new ray instance with the given start point and direction
85 * @throws IllegalArgumentException If {@code direction} has zero length, as evaluated by the
86 * given precision context
87 * @see #fromPointAndDirection(Vector2D, Vector2D, Precision.DoubleEquivalence)
88 */
89 public static Ray rayFromPointAndDirection(final Vector2D startPoint, final Vector2D direction,
90 final Precision.DoubleEquivalence precision) {
91 final Line line = Lines.fromPointAndDirection(startPoint, direction, precision);
92
93 return new Ray(line, startPoint);
94 }
95
96 /** Construct a ray starting at the given point and continuing to infinity in the direction
97 * of {@code line}. The given point is projected onto the line.
98 * @param line line for the ray
99 * @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 }