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