View Javadoc
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    *      https://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  
18  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  package org.hipparchus.geometry.euclidean.threed;
23  
24  import org.hipparchus.exception.LocalizedCoreFormats;
25  import org.hipparchus.exception.MathIllegalArgumentException;
26  import org.hipparchus.geometry.Point;
27  import org.hipparchus.geometry.Vector;
28  import org.hipparchus.geometry.euclidean.oned.Euclidean1D;
29  import org.hipparchus.geometry.euclidean.oned.IntervalsSet;
30  import org.hipparchus.geometry.euclidean.oned.Vector1D;
31  import org.hipparchus.geometry.partitioning.Embedding;
32  import org.hipparchus.util.FastMath;
33  import org.hipparchus.util.Precision;
34  
35  /** The class represent lines in a three dimensional space.
36  
37   * <p>Each oriented line is intrinsically associated with an abscissa
38   * which is a coordinate on the line. The point at abscissa 0 is the
39   * orthogonal projection of the origin on the line, another equivalent
40   * way to express this is to say that it is the point of the line
41   * which is closest to the origin. Abscissa increases in the line
42   * direction.</p>
43   *
44   * @see #fromDirection(Vector3D, Vector3D, double)
45   * @see #Line(Vector3D, Vector3D, double)
46   */
47  public class Line implements Embedding<Euclidean3D, Euclidean1D> {
48  
49      /** Line direction. */
50      private Vector3D direction;
51  
52      /** Line point closest to the origin. */
53      private Vector3D zero;
54  
55      /** Tolerance below which points are considered identical. */
56      private final double tolerance;
57  
58      /** Build a line from two points.
59       * @param p1 first point belonging to the line (this can be any point)
60       * @param p2 second point belonging to the line (this can be any point, different from p1)
61       * @param tolerance tolerance below which points are considered identical
62       * @exception MathIllegalArgumentException if the points are equal
63       * @see #fromDirection(Vector3D, Vector3D, double)
64       */
65      public Line(final Vector3D p1, final Vector3D p2, final double tolerance)
66          throws MathIllegalArgumentException {
67          this(tolerance);
68          reset(p1, p2);
69      }
70  
71      /** Copy constructor.
72       * <p>The created instance is completely independent from the
73       * original instance, it is a deep copy.</p>
74       * @param line line to copy
75       */
76      public Line(final Line line) {
77          this(line.tolerance);
78          this.direction = line.direction;
79          this.zero      = line.zero;
80      }
81  
82      /**
83       * Private constructor. Just sets the tolerance.
84       *
85       * @param tolerance below which points are considered identical.
86       */
87      private Line(final double tolerance) {
88          this.tolerance = tolerance;
89      }
90  
91      /**
92       * Create a line from a point and a direction. Line = {@code point} + t * {@code
93       * direction}, where t is any real number.
94       *
95       * @param point     on the line. Can be any point.
96       * @param direction of the line. Must not be the zero vector.
97       * @param tolerance below which points are considered identical.
98       * @return a new Line with the given point and direction.
99       * @throws MathIllegalArgumentException if {@code direction} is the zero vector.
100      * @see #Line(Vector3D, Vector3D, double)
101      */
102     public static Line fromDirection(final Vector3D point,
103                                      final Vector3D direction,
104                                      final double tolerance) {
105         final Line line = new Line(tolerance);
106         line.resetWithDirection(point, direction);
107         return line;
108     }
109 
110     /** Reset the instance as if built from two points.
111      * @param p1 first point belonging to the line (this can be any point)
112      * @param p2 second point belonging to the line (this can be any point, different from p1)
113      * @exception MathIllegalArgumentException if the points are equal
114      */
115     public void reset(final Vector3D p1, final Vector3D p2) throws MathIllegalArgumentException {
116         resetWithDirection(p1, p2.subtract(p1));
117     }
118 
119     /**
120      * Reset the instance as if built from a point and direction.
121      *
122      * @param p1    point belonging to the line (this can be any point).
123      * @param delta direction of the line.
124      * @throws MathIllegalArgumentException if {@code delta} is the zero vector.
125      */
126     private void resetWithDirection(final Vector3D p1, final Vector3D delta) {
127         final double norm2 = delta.getNormSq();
128         if (norm2 == 0.0) {
129             throw new MathIllegalArgumentException(LocalizedCoreFormats.ZERO_NORM);
130         }
131         this.direction = new Vector3D(1.0 / FastMath.sqrt(norm2), delta);
132         zero = new Vector3D(1.0, p1, -p1.dotProduct(delta) / norm2, delta);
133     }
134 
135     /** Get the tolerance below which points are considered identical.
136      * @return tolerance below which points are considered identical
137      */
138     public double getTolerance() {
139         return tolerance;
140     }
141 
142     /** Get a line with reversed direction.
143      * @return a new instance, with reversed direction
144      */
145     public Line revert() {
146         final Line reverted = new Line(this);
147         reverted.direction = reverted.direction.negate();
148         return reverted;
149     }
150 
151     /** Get the normalized direction vector.
152      * @return normalized direction vector
153      */
154     public Vector3D getDirection() {
155         return direction;
156     }
157 
158     /** Get the line point closest to the origin.
159      * @return line point closest to the origin
160      */
161     public Vector3D getOrigin() {
162         return zero;
163     }
164 
165     /** Get the abscissa of a point with respect to the line.
166      * <p>The abscissa is 0 if the projection of the point and the
167      * projection of the frame origin on the line are the same
168      * point.</p>
169      * @param point point to check
170      * @return abscissa of the point
171      */
172     public double getAbscissa(final Vector3D point) {
173         return point.subtract(zero).dotProduct(direction);
174     }
175 
176     /** Get one point from the line.
177      * @param abscissa desired abscissa for the point
178      * @return one point belonging to the line, at specified abscissa
179      */
180     public Vector3D pointAt(final double abscissa) {
181         return new Vector3D(1.0, zero, abscissa, direction);
182     }
183 
184     /** Transform a space point into a sub-space point.
185      * @param vector n-dimension point of the space
186      * @return (n-1)-dimension point of the sub-space corresponding to
187      * the specified space point
188      */
189     public Vector1D toSubSpace(Vector<Euclidean3D, Vector3D> vector) {
190         return toSubSpace((Point<Euclidean3D>) vector);
191     }
192 
193     /** Transform a sub-space point into a space point.
194      * @param vector (n-1)-dimension point of the sub-space
195      * @return n-dimension point of the space corresponding to the
196      * specified sub-space point
197      */
198     public Vector3D toSpace(Vector<Euclidean1D, Vector1D> vector) {
199         return toSpace((Point<Euclidean1D>) vector);
200     }
201 
202     /** {@inheritDoc}
203      * @see #getAbscissa(Vector3D)
204      */
205     @Override
206     public Vector1D toSubSpace(final Point<Euclidean3D> point) {
207         return new Vector1D(getAbscissa((Vector3D) point));
208     }
209 
210     /** {@inheritDoc}
211      * @see #pointAt(double)
212      */
213     @Override
214     public Vector3D toSpace(final Point<Euclidean1D> point) {
215         return pointAt(((Vector1D) point).getX());
216     }
217 
218     /** Check if the instance is similar to another line.
219      * <p>Lines are considered similar if they contain the same
220      * points. This does not mean they are equal since they can have
221      * opposite directions.</p>
222      * @param line line to which instance should be compared
223      * @return true if the lines are similar
224      */
225     public boolean isSimilarTo(final Line line) {
226         final double angle = Vector3D.angle(direction, line.direction);
227         return ((angle < tolerance) || (angle > (FastMath.PI - tolerance))) && contains(line.zero);
228     }
229 
230     /** Check if the instance contains a point.
231      * @param p point to check
232      * @return true if p belongs to the line
233      */
234     public boolean contains(final Vector3D p) {
235         return distance(p) < tolerance;
236     }
237 
238     /** Compute the distance between the instance and a point.
239      * @param p to check
240      * @return distance between the instance and the point
241      */
242     public double distance(final Vector3D p) {
243         final Vector3D d = p.subtract(zero);
244         final Vector3D n = new Vector3D(1.0, d, -d.dotProduct(direction), direction);
245         return n.getNorm();
246     }
247 
248     /** Compute the shortest distance between the instance and another line.
249      * @param line line to check against the instance
250      * @return shortest distance between the instance and the line
251      */
252     public double distance(final Line line) {
253 
254         final Vector3D normal = Vector3D.crossProduct(direction, line.direction);
255         final double n = normal.getNorm();
256         if (n < Precision.SAFE_MIN) {
257             // lines are parallel
258             return distance(line.zero);
259         }
260 
261         // signed separation of the two parallel planes that contains the lines
262         final double offset = line.zero.subtract(zero).dotProduct(normal) / n;
263 
264         return FastMath.abs(offset);
265 
266     }
267 
268     /** Compute the point of the instance closest to another line.
269      * @param line line to check against the instance
270      * @return point of the instance closest to another line
271      */
272     public Vector3D closestPoint(final Line line) {
273 
274         final double cos = direction.dotProduct(line.direction);
275         final double n = 1 - cos * cos;
276         if (n < Precision.EPSILON) {
277             // the lines are parallel
278             return zero;
279         }
280 
281         final Vector3D delta0 = line.zero.subtract(zero);
282         final double a        = delta0.dotProduct(direction);
283         final double b        = delta0.dotProduct(line.direction);
284 
285         return new Vector3D(1, zero, (a - b * cos) / n, direction);
286 
287     }
288 
289     /** Get the intersection point of the instance and another line.
290      * @param line other line
291      * @return intersection point of the instance and the other line
292      * or null if there are no intersection points
293      */
294     public Vector3D intersection(final Line line) {
295         final Vector3D closest = closestPoint(line);
296         return line.contains(closest) ? closest : null;
297     }
298 
299     /** Build a sub-line covering the whole line.
300      * @return a sub-line covering the whole line
301      */
302     public SubLine wholeLine() {
303         return new SubLine(this, new IntervalsSet(tolerance));
304     }
305 
306 }