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 }