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.math3.geometry.euclidean.threed;
018
019import org.apache.commons.math3.exception.MathIllegalArgumentException;
020import org.apache.commons.math3.exception.util.LocalizedFormats;
021import org.apache.commons.math3.geometry.Point;
022import org.apache.commons.math3.geometry.Vector;
023import org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D;
024import org.apache.commons.math3.geometry.euclidean.oned.IntervalsSet;
025import org.apache.commons.math3.geometry.euclidean.oned.Vector1D;
026import org.apache.commons.math3.geometry.partitioning.Embedding;
027import org.apache.commons.math3.util.FastMath;
028import org.apache.commons.math3.util.Precision;
029
030/** The class represent lines in a three dimensional space.
031
032 * <p>Each oriented line is intrinsically associated with an abscissa
033 * which is a coordinate on the line. The point at abscissa 0 is the
034 * orthogonal projection of the origin on the line, another equivalent
035 * way to express this is to say that it is the point of the line
036 * which is closest to the origin. Abscissa increases in the line
037 * direction.</p>
038
039 * @since 3.0
040 */
041public class Line implements Embedding<Euclidean3D, Euclidean1D> {
042
043    /** Default value for tolerance. */
044    private static final double DEFAULT_TOLERANCE = 1.0e-10;
045
046    /** Line direction. */
047    private Vector3D direction;
048
049    /** Line point closest to the origin. */
050    private Vector3D zero;
051
052    /** Tolerance below which points are considered identical. */
053    private final double tolerance;
054
055    /** Build a line from two points.
056     * @param p1 first point belonging to the line (this can be any point)
057     * @param p2 second point belonging to the line (this can be any point, different from p1)
058     * @param tolerance tolerance below which points are considered identical
059     * @exception MathIllegalArgumentException if the points are equal
060     * @since 3.3
061     */
062    public Line(final Vector3D p1, final Vector3D p2, final double tolerance)
063        throws MathIllegalArgumentException {
064        reset(p1, p2);
065        this.tolerance = tolerance;
066    }
067
068    /** Copy constructor.
069     * <p>The created instance is completely independent from the
070     * original instance, it is a deep copy.</p>
071     * @param line line to copy
072     */
073    public Line(final Line line) {
074        this.direction = line.direction;
075        this.zero      = line.zero;
076        this.tolerance = line.tolerance;
077    }
078
079    /** Build a line from two points.
080     * @param p1 first point belonging to the line (this can be any point)
081     * @param p2 second point belonging to the line (this can be any point, different from p1)
082     * @exception MathIllegalArgumentException if the points are equal
083     * @deprecated as of 3.3, replaced with {@link #Line(Vector3D, Vector3D, double)}
084     */
085    @Deprecated
086    public Line(final Vector3D p1, final Vector3D p2) throws MathIllegalArgumentException {
087        this(p1, p2, DEFAULT_TOLERANCE);
088    }
089
090    /** Reset the instance as if built from two points.
091     * @param p1 first point belonging to the line (this can be any point)
092     * @param p2 second point belonging to the line (this can be any point, different from p1)
093     * @exception MathIllegalArgumentException if the points are equal
094     */
095    public void reset(final Vector3D p1, final Vector3D p2) throws MathIllegalArgumentException {
096        final Vector3D delta = p2.subtract(p1);
097        final double norm2 = delta.getNormSq();
098        if (norm2 == 0.0) {
099            throw new MathIllegalArgumentException(LocalizedFormats.ZERO_NORM);
100        }
101        this.direction = new Vector3D(1.0 / FastMath.sqrt(norm2), delta);
102        zero = new Vector3D(1.0, p1, -p1.dotProduct(delta) / norm2, delta);
103    }
104
105    /** Get the tolerance below which points are considered identical.
106     * @return tolerance below which points are considered identical
107     * @since 3.3
108     */
109    public double getTolerance() {
110        return tolerance;
111    }
112
113    /** Get a line with reversed direction.
114     * @return a new instance, with reversed direction
115     */
116    public Line revert() {
117        final Line reverted = new Line(this);
118        reverted.direction = reverted.direction.negate();
119        return reverted;
120    }
121
122    /** Get the normalized direction vector.
123     * @return normalized direction vector
124     */
125    public Vector3D getDirection() {
126        return direction;
127    }
128
129    /** Get the line point closest to the origin.
130     * @return line point closest to the origin
131     */
132    public Vector3D getOrigin() {
133        return zero;
134    }
135
136    /** Get the abscissa of a point with respect to the line.
137     * <p>The abscissa is 0 if the projection of the point and the
138     * projection of the frame origin on the line are the same
139     * point.</p>
140     * @param point point to check
141     * @return abscissa of the point
142     */
143    public double getAbscissa(final Vector3D point) {
144        return point.subtract(zero).dotProduct(direction);
145    }
146
147    /** Get one point from the line.
148     * @param abscissa desired abscissa for the point
149     * @return one point belonging to the line, at specified abscissa
150     */
151    public Vector3D pointAt(final double abscissa) {
152        return new Vector3D(1.0, zero, abscissa, direction);
153    }
154
155    /** Transform a space point into a sub-space point.
156     * @param vector n-dimension point of the space
157     * @return (n-1)-dimension point of the sub-space corresponding to
158     * the specified space point
159     */
160    public Vector1D toSubSpace(Vector<Euclidean3D> vector) {
161        return toSubSpace((Point<Euclidean3D>) vector);
162    }
163
164    /** Transform a sub-space point into a space point.
165     * @param vector (n-1)-dimension point of the sub-space
166     * @return n-dimension point of the space corresponding to the
167     * specified sub-space point
168     */
169    public Vector3D toSpace(Vector<Euclidean1D> vector) {
170        return toSpace((Point<Euclidean1D>) vector);
171    }
172
173    /** {@inheritDoc}
174     * @see #getAbscissa(Vector3D)
175     */
176    public Vector1D toSubSpace(final Point<Euclidean3D> point) {
177        return new Vector1D(getAbscissa((Vector3D) point));
178    }
179
180    /** {@inheritDoc}
181     * @see #pointAt(double)
182     */
183    public Vector3D toSpace(final Point<Euclidean1D> point) {
184        return pointAt(((Vector1D) point).getX());
185    }
186
187    /** Check if the instance is similar to another line.
188     * <p>Lines are considered similar if they contain the same
189     * points. This does not mean they are equal since they can have
190     * opposite directions.</p>
191     * @param line line to which instance should be compared
192     * @return true if the lines are similar
193     */
194    public boolean isSimilarTo(final Line line) {
195        final double angle = Vector3D.angle(direction, line.direction);
196        return ((angle < tolerance) || (angle > (FastMath.PI - tolerance))) && contains(line.zero);
197    }
198
199    /** Check if the instance contains a point.
200     * @param p point to check
201     * @return true if p belongs to the line
202     */
203    public boolean contains(final Vector3D p) {
204        return distance(p) < tolerance;
205    }
206
207    /** Compute the distance between the instance and a point.
208     * @param p to check
209     * @return distance between the instance and the point
210     */
211    public double distance(final Vector3D p) {
212        final Vector3D d = p.subtract(zero);
213        final Vector3D n = new Vector3D(1.0, d, -d.dotProduct(direction), direction);
214        return n.getNorm();
215    }
216
217    /** Compute the shortest distance between the instance and another line.
218     * @param line line to check against the instance
219     * @return shortest distance between the instance and the line
220     */
221    public double distance(final Line line) {
222
223        final Vector3D normal = Vector3D.crossProduct(direction, line.direction);
224        final double n = normal.getNorm();
225        if (n < Precision.SAFE_MIN) {
226            // lines are parallel
227            return distance(line.zero);
228        }
229
230        // signed separation of the two parallel planes that contains the lines
231        final double offset = line.zero.subtract(zero).dotProduct(normal) / n;
232
233        return FastMath.abs(offset);
234
235    }
236
237    /** Compute the point of the instance closest to another line.
238     * @param line line to check against the instance
239     * @return point of the instance closest to another line
240     */
241    public Vector3D closestPoint(final Line line) {
242
243        final double cos = direction.dotProduct(line.direction);
244        final double n = 1 - cos * cos;
245        if (n < Precision.EPSILON) {
246            // the lines are parallel
247            return zero;
248        }
249
250        final Vector3D delta0 = line.zero.subtract(zero);
251        final double a        = delta0.dotProduct(direction);
252        final double b        = delta0.dotProduct(line.direction);
253
254        return new Vector3D(1, zero, (a - b * cos) / n, direction);
255
256    }
257
258    /** Get the intersection point of the instance and another line.
259     * @param line other line
260     * @return intersection point of the instance and the other line
261     * or null if there are no intersection points
262     */
263    public Vector3D intersection(final Line line) {
264        final Vector3D closest = closestPoint(line);
265        return line.contains(closest) ? closest : null;
266    }
267
268    /** Build a sub-line covering the whole line.
269     * @return a sub-line covering the whole line
270     */
271    public SubLine wholeLine() {
272        return new SubLine(this, new IntervalsSet(tolerance));
273    }
274
275}