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; 020import java.util.Objects; 021 022import org.apache.commons.geometry.core.Embedding; 023import org.apache.commons.geometry.core.Transform; 024import org.apache.commons.geometry.euclidean.oned.AffineTransformMatrix1D; 025import org.apache.commons.geometry.euclidean.oned.Vector1D; 026import org.apache.commons.geometry.euclidean.threed.Vector3D; 027import org.apache.commons.numbers.core.Precision; 028 029/** Class representing a line in 3D space. 030 * 031 * <p>Instances of this class are guaranteed to be immutable.</p> 032 * @see Lines3D 033 */ 034public final class Line3D implements Embedding<Vector3D, Vector1D> { 035 036 /** Format string for creating line string representations. */ 037 static final String TO_STRING_FORMAT = "{0}[origin= {1}, direction= {2}]"; 038 039 /** Line point closest to the origin. */ 040 private final Vector3D origin; 041 042 /** Line direction. */ 043 private final Vector3D direction; 044 045 /** Precision context used to compare floating point numbers. */ 046 private final Precision.DoubleEquivalence precision; 047 048 /** Simple constructor. 049 * @param origin the origin of the line, meaning the point on the line closest to the origin of the 050 * 3D space 051 * @param direction the direction of the line 052 * @param precision precision context used to compare floating point numbers 053 */ 054 Line3D(final Vector3D origin, final Vector3D direction, final Precision.DoubleEquivalence precision) { 055 this.origin = origin; 056 this.direction = direction; 057 this.precision = precision; 058 } 059 060 /** Get the line point closest to the origin. 061 * @return line point closest to the origin 062 */ 063 public Vector3D getOrigin() { 064 return origin; 065 } 066 067 /** Get the normalized direction vector. 068 * @return normalized direction vector 069 */ 070 public Vector3D getDirection() { 071 return direction; 072 } 073 074 /** Get the object used to determine floating point equality for this instance. 075 * @return the floating point precision context for the instance 076 */ 077 public Precision.DoubleEquivalence getPrecision() { 078 return precision; 079 } 080 081 /** Return a line containing the same points as this instance but pointing 082 * in the opposite direction. 083 * @return an instance containing the same points but pointing in the opposite 084 * direction 085 */ 086 public Line3D reverse() { 087 return new Line3D(origin, direction.negate(), precision); 088 } 089 090 /** Transform this instance. 091 * @param transform object used to transform the instance 092 * @return a transformed instance 093 */ 094 public Line3D transform(final Transform<Vector3D> transform) { 095 final Vector3D p1 = transform.apply(origin); 096 final Vector3D p2 = transform.apply(origin.add(direction)); 097 098 return Lines3D.fromPoints(p1, p2, precision); 099 } 100 101 /** Get an object containing the current line transformed by the argument along with a 102 * 1D transform that can be applied to subspace points. The subspace transform transforms 103 * subspace points such that their 3D location in the transformed line is the same as their 104 * 3D location in the original line after the 3D transform is applied. For example, consider 105 * the code below: 106 * <pre> 107 * SubspaceTransform st = line.subspaceTransform(transform); 108 * 109 * Vector1D subPt = Vector1D.of(1); 110 * 111 * Vector3D a = transform.apply(line.toSpace(subPt)); // transform in 3D space 112 * Vector3D b = st.getLine().toSpace(st.getTransform().apply(subPt)); // transform in 1D space 113 * </pre> 114 * At the end of execution, the points {@code a} (which was transformed using the original 115 * 3D transform) and {@code b} (which was transformed in 1D using the subspace transform) 116 * are equivalent. 117 * 118 * @param transform the transform to apply to this instance 119 * @return an object containing the transformed line along with a transform that can be applied 120 * to subspace points 121 * @see #transform(Transform) 122 */ 123 public SubspaceTransform subspaceTransform(final Transform<Vector3D> transform) { 124 final Vector3D p1 = transform.apply(origin); 125 final Vector3D p2 = transform.apply(origin.add(direction)); 126 127 final Line3D tLine = Lines3D.fromPoints(p1, p2, precision); 128 129 final Vector1D tSubspaceOrigin = tLine.toSubspace(p1); 130 final Vector1D tSubspaceDirection = tSubspaceOrigin.vectorTo(tLine.toSubspace(p2)); 131 132 final double translation = tSubspaceOrigin.getX(); 133 final double scale = tSubspaceDirection.getX(); 134 135 final AffineTransformMatrix1D subspaceTransform = AffineTransformMatrix1D.of(scale, translation); 136 137 return new SubspaceTransform(tLine, subspaceTransform); 138 } 139 140 /** Get the abscissa of the given point on the line. The abscissa represents 141 * the distance the projection of the point on the line is from the line's 142 * origin point (the point on the line closest to the origin of the 143 * 2D space). Abscissa values increase in the direction of the line. This method 144 * is exactly equivalent to {@link #toSubspace(Vector3D)} except that this method 145 * returns a double instead of a {@link Vector1D}. 146 * @param pt point to compute the abscissa for 147 * @return abscissa value of the point 148 * @see #toSubspace(Vector3D) 149 */ 150 public double abscissa(final Vector3D pt) { 151 return pt.subtract(origin).dot(direction); 152 } 153 154 /** Get one point from the line. 155 * @param abscissa desired abscissa for the point 156 * @return one point belonging to the line, at specified abscissa 157 */ 158 public Vector3D pointAt(final double abscissa) { 159 return Vector3D.Sum.of(origin) 160 .addScaled(abscissa, direction) 161 .get(); 162 } 163 164 /** {@inheritDoc} */ 165 @Override 166 public Vector1D toSubspace(final Vector3D pt) { 167 return Vector1D.of(abscissa(pt)); 168 } 169 170 /** {@inheritDoc} */ 171 @Override 172 public Vector3D toSpace(final Vector1D pt) { 173 return toSpace(pt.getX()); 174 } 175 176 /** Get the 3 dimensional point at the given abscissa position 177 * on the line. 178 * @param abscissa location on the line 179 * @return the 3 dimensional point at the given abscissa position 180 * on the line 181 */ 182 public Vector3D toSpace(final double abscissa) { 183 return pointAt(abscissa); 184 } 185 186 /** Check if the instance is similar to another line. 187 * <p>Lines are considered similar if they contain the same 188 * points. This does not mean they are equal since they can have 189 * opposite directions.</p> 190 * @param line line to which instance should be compared 191 * @return true if the lines are similar 192 */ 193 public boolean isSimilarTo(final Line3D line) { 194 final double angle = direction.angle(line.direction); 195 return (precision.eqZero(angle) || precision.eq(Math.abs(angle), Math.PI)) && 196 contains(line.origin); 197 } 198 199 /** Check if the instance contains a point. 200 * @param pt point to check 201 * @return true if p belongs to the line 202 */ 203 public boolean contains(final Vector3D pt) { 204 return precision.eqZero(distance(pt)); 205 } 206 207 /** Compute the distance between the instance and a point. 208 * @param pt to check 209 * @return distance between the instance and the point 210 */ 211 public double distance(final Vector3D pt) { 212 final Vector3D delta = pt.subtract(origin); 213 final Vector3D orthogonal = delta.reject(direction); 214 215 return orthogonal.norm(); 216 } 217 218 /** Compute the shortest distance between the instance and another line. 219 * @param line line to check against the instance 220 * @return shortest distance between the instance and the line 221 */ 222 public double distance(final Line3D line) { 223 224 final Vector3D normal = direction.cross(line.direction); 225 final double norm = normal.norm(); 226 227 if (precision.eqZero(norm)) { 228 // the lines are parallel 229 return distance(line.origin); 230 } 231 232 // signed separation of the two parallel planes that contains the lines 233 final double offset = line.origin.subtract(origin).dot(normal) / norm; 234 235 return Math.abs(offset); 236 } 237 238 /** Compute the point of the instance closest to another line. 239 * @param line line to check against the instance 240 * @return point of the instance closest to another line 241 */ 242 public Vector3D closest(final Line3D line) { 243 244 final double cos = direction.dot(line.direction); 245 final double n = 1 - cos * cos; 246 247 if (precision.eqZero(n)) { 248 // the lines are parallel 249 return origin; 250 } 251 252 final Vector3D delta = line.origin.subtract(origin); 253 final double a = delta.dot(direction); 254 final double b = delta.dot(line.direction); 255 256 return Vector3D.Sum.of(origin) 257 .addScaled((a - (b * cos)) / n, direction) 258 .get(); 259 } 260 261 /** Get the intersection point of the instance and another line. 262 * @param line other line 263 * @return intersection point of the instance and the other line 264 * or null if there are no intersection points 265 */ 266 public Vector3D intersection(final Line3D line) { 267 final Vector3D closestPt = closest(line); 268 return line.contains(closestPt) ? closestPt : null; 269 } 270 271 /** Return a new infinite line subset representing the entire line. 272 * @return a new infinite line subset representing the entire line 273 * @see Lines3D#span(Line3D) 274 */ 275 public LineConvexSubset3D span() { 276 return Lines3D.span(this); 277 } 278 279 /** Create a new line segment from the given 1D interval. The returned line 280 * segment consists of all points between the two locations, regardless of the order the 281 * arguments are given. 282 * @param a first 1D location for the interval 283 * @param b second 1D location for the interval 284 * @return a new line segment on this line 285 * @throws IllegalArgumentException if either of the locations is NaN or infinite 286 * @see Lines3D#segmentFromLocations(Line3D, double, double) 287 */ 288 public Segment3D segment(final double a, final double b) { 289 return Lines3D.segmentFromLocations(this, a, b); 290 } 291 292 /** Create a new line segment from two points. The returned segment represents all points on this line 293 * between the projected locations of {@code a} and {@code b}. The points may be given in any order. 294 * @param a first point 295 * @param b second point 296 * @return a new line segment on this line 297 * @throws IllegalArgumentException if either point contains NaN or infinite coordinate values 298 * @see Lines3D#segmentFromPoints(Line3D, Vector3D, Vector3D) 299 */ 300 public Segment3D segment(final Vector3D a, final Vector3D b) { 301 return Lines3D.segmentFromPoints(this, a, b); 302 } 303 304 /** Create a new line convex subset that starts at infinity and continues along 305 * the line up to the projection of the given end point. 306 * @param endPoint point defining the end point of the line subset; the end point 307 * is equal to the projection of this point onto the line 308 * @return a new, half-open line subset that ends at the given point 309 * @throws IllegalArgumentException if any coordinate in {@code endPoint} is NaN or infinite 310 * @see Lines3D#reverseRayFromPoint(Line3D, Vector3D) 311 */ 312 public ReverseRay3D reverseRayTo(final Vector3D endPoint) { 313 return Lines3D.reverseRayFromPoint(this, endPoint); 314 } 315 316 /** Create a new line convex subset that starts at infinity and continues along 317 * the line up to the given 1D location. 318 * @param endLocation the 1D location of the end of the half-line 319 * @return a new, half-open line subset that ends at the given 1D location 320 * @throws IllegalArgumentException if {@code endLocation} is NaN or infinite 321 * @see Lines3D#reverseRayFromLocation(Line3D, double) 322 */ 323 public ReverseRay3D reverseRayTo(final double endLocation) { 324 return Lines3D.reverseRayFromLocation(this, endLocation); 325 } 326 327 /** Create a new ray instance that starts at the projection of the given point 328 * and continues in the direction of the line to infinity. 329 * @param startPoint point defining the start point of the ray; the start point 330 * is equal to the projection of this point onto the line 331 * @return a ray starting at the projected point and extending along this line 332 * to infinity 333 * @throws IllegalArgumentException if any coordinate in {@code startPoint} is NaN or infinite 334 * @see Lines3D#rayFromPoint(Line3D, Vector3D) 335 */ 336 public Ray3D rayFrom(final Vector3D startPoint) { 337 return Lines3D.rayFromPoint(this, startPoint); 338 } 339 340 /** Create a new ray instance that starts at the given 1D location and continues in 341 * the direction of the line to infinity. 342 * @param startLocation 1D location defining the start point of the ray 343 * @return a ray starting at the given 1D location and extending along this line 344 * to infinity 345 * @throws IllegalArgumentException if {@code startLocation} is NaN or infinite 346 * @see Lines3D#rayFromLocation(Line3D, double) 347 */ 348 public Ray3D rayFrom(final double startLocation) { 349 return Lines3D.rayFromLocation(this, startLocation); 350 } 351 352 /** Return true if this instance should be considered equivalent to the argument, using the 353 * given precision context for comparison. Instances are considered equivalent if they have 354 * equivalent {@code origin}s and {@code direction}s. 355 * @param other the point to compare with 356 * @param ctx precision context to use for the comparison 357 * @return true if this instance should be considered equivalent to the argument 358 * @see Vector3D#eq(Vector3D, Precision.DoubleEquivalence) 359 */ 360 public boolean eq(final Line3D other, final Precision.DoubleEquivalence ctx) { 361 return getOrigin().eq(other.getOrigin(), ctx) && 362 getDirection().eq(other.getDirection(), ctx); 363 } 364 365 /** {@inheritDoc} */ 366 @Override 367 public int hashCode() { 368 return Objects.hash(origin, direction, precision); 369 } 370 371 /** {@inheritDoc} */ 372 @Override 373 public boolean equals(final Object obj) { 374 if (this == obj) { 375 return true; 376 } 377 if (!(obj instanceof Line3D)) { 378 return false; 379 } 380 final Line3D other = (Line3D) obj; 381 return this.origin.equals(other.origin) && 382 this.direction.equals(other.direction) && 383 this.precision.equals(other.precision); 384 } 385 386 /** {@inheritDoc} */ 387 @Override 388 public String toString() { 389 return MessageFormat.format(TO_STRING_FORMAT, 390 getClass().getSimpleName(), 391 getOrigin(), 392 getDirection()); 393 } 394 395 /** Class containing a transformed line instance along with a subspace (1D) transform. The subspace 396 * transform produces the equivalent of the 3D transform in 1D. 397 */ 398 public static final class SubspaceTransform { 399 /** The transformed line. */ 400 private final Line3D line; 401 402 /** The subspace transform instance. */ 403 private final AffineTransformMatrix1D transform; 404 405 /** Simple constructor. 406 * @param line the transformed line 407 * @param transform 1D transform that can be applied to subspace points 408 */ 409 public SubspaceTransform(final Line3D line, final AffineTransformMatrix1D transform) { 410 this.line = line; 411 this.transform = transform; 412 } 413 414 /** Get the transformed line instance. 415 * @return the transformed line instance 416 */ 417 public Line3D getLine() { 418 return line; 419 } 420 421 /** Get the 1D transform that can be applied to subspace points. This transform can be used 422 * to perform the equivalent of the 3D transform in 1D space. 423 * @return the subspace transform instance 424 */ 425 public AffineTransformMatrix1D getTransform() { 426 return transform; 427 } 428 } 429}