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.MathRuntimeException;
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.Vector1D;
30  import org.hipparchus.geometry.euclidean.twod.Euclidean2D;
31  import org.hipparchus.geometry.euclidean.twod.PolygonsSet;
32  import org.hipparchus.geometry.euclidean.twod.Vector2D;
33  import org.hipparchus.geometry.partitioning.Embedding;
34  import org.hipparchus.geometry.partitioning.Hyperplane;
35  import org.hipparchus.geometry.partitioning.RegionFactory;
36  import org.hipparchus.util.FastMath;
37  
38  /** The class represent planes in a three dimensional space.
39   */
40  public class Plane implements Hyperplane<Euclidean3D>, Embedding<Euclidean3D, Euclidean2D> {
41  
42      /** Offset of the origin with respect to the plane. */
43      private double originOffset;
44  
45      /** Origin of the plane frame. */
46      private Vector3D origin;
47  
48      /** First vector of the plane frame (in plane). */
49      private Vector3D u;
50  
51      /** Second vector of the plane frame (in plane). */
52      private Vector3D v;
53  
54      /** Third vector of the plane frame (plane normal). */
55      private Vector3D w;
56  
57      /** Tolerance below which points are considered identical. */
58      private final double tolerance;
59  
60      /** Build a plane normal to a given direction and containing the origin.
61       * @param normal normal direction to the plane
62       * @param tolerance tolerance below which points are considered identical
63       * @exception MathRuntimeException if the normal norm is too small
64       */
65      public Plane(final Vector3D normal, final double tolerance)
66          throws MathRuntimeException {
67          setNormal(normal);
68          this.tolerance = tolerance;
69          originOffset = 0;
70          setFrame();
71      }
72  
73      /** Build a plane from a point and a normal.
74       * @param p point belonging to the plane
75       * @param normal normal direction to the plane
76       * @param tolerance tolerance below which points are considered identical
77       * @exception MathRuntimeException if the normal norm is too small
78       */
79      public Plane(final Vector3D p, final Vector3D normal, final double tolerance)
80          throws MathRuntimeException {
81          setNormal(normal);
82          this.tolerance = tolerance;
83          originOffset = -p.dotProduct(w);
84          setFrame();
85      }
86  
87      /** Build a plane from three points.
88       * <p>The plane is oriented in the direction of
89       * {@code (p2-p1) ^ (p3-p1)}</p>
90       * @param p1 first point belonging to the plane
91       * @param p2 second point belonging to the plane
92       * @param p3 third point belonging to the plane
93       * @param tolerance tolerance below which points are considered identical
94       * @exception MathRuntimeException if the points do not constitute a plane
95       */
96      public Plane(final Vector3D p1, final Vector3D p2, final Vector3D p3, final double tolerance)
97          throws MathRuntimeException {
98          this(p1, p2.subtract(p1).crossProduct(p3.subtract(p1)), tolerance);
99      }
100 
101     /** Copy constructor.
102      * <p>The instance created is completely independent of the original
103      * one. A deep copy is used, none of the underlying object are
104      * shared.</p>
105      * @param plane plane to copy
106      */
107     public Plane(final Plane plane) {
108         originOffset = plane.originOffset;
109         origin       = plane.origin;
110         u            = plane.u;
111         v            = plane.v;
112         w            = plane.w;
113         tolerance    = plane.tolerance;
114     }
115 
116     /** Copy the instance.
117      * <p>The instance created is completely independant of the original
118      * one. A deep copy is used, none of the underlying objects are
119      * shared (except for immutable objects).</p>
120      * @return a new hyperplane, copy of the instance
121      */
122     @Override
123     public Plane copySelf() {
124         return new Plane(this);
125     }
126 
127     /** Reset the instance as if built from a point and a normal.
128      * @param p point belonging to the plane
129      * @param normal normal direction to the plane
130      * @exception MathRuntimeException if the normal norm is too small
131      */
132     public void reset(final Vector3D p, final Vector3D normal) throws MathRuntimeException {
133         setNormal(normal);
134         originOffset = -p.dotProduct(w);
135         setFrame();
136     }
137 
138     /** Reset the instance from another one.
139      * <p>The updated instance is completely independant of the original
140      * one. A deep reset is used none of the underlying object is
141      * shared.</p>
142      * @param original plane to reset from
143      */
144     public void reset(final Plane original) {
145         originOffset = original.originOffset;
146         origin       = original.origin;
147         u            = original.u;
148         v            = original.v;
149         w            = original.w;
150     }
151 
152     /** Set the normal vactor.
153      * @param normal normal direction to the plane (will be copied)
154      * @exception MathRuntimeException if the normal norm is too small
155      */
156     private void setNormal(final Vector3D normal) throws MathRuntimeException {
157         final double norm = normal.getNorm();
158         if (norm < 1.0e-10) {
159             throw new MathRuntimeException(LocalizedCoreFormats.ZERO_NORM);
160         }
161         w = new Vector3D(1.0 / norm, normal);
162     }
163 
164     /** Reset the plane frame.
165      */
166     private void setFrame() {
167         origin = new Vector3D(-originOffset, w);
168         u = w.orthogonal();
169         v = Vector3D.crossProduct(w, u);
170     }
171 
172     /** Get the origin point of the plane frame.
173      * <p>The point returned is the orthogonal projection of the
174      * 3D-space origin in the plane.</p>
175      * @return the origin point of the plane frame (point closest to the
176      * 3D-space origin)
177      */
178     public Vector3D getOrigin() {
179         return origin;
180     }
181 
182     /** Get the normalized normal vector.
183      * <p>The frame defined by ({@link #getU getU}, {@link #getV getV},
184      * {@link #getNormal getNormal}) is a rigth-handed orthonormalized
185      * frame).</p>
186      * @return normalized normal vector
187      * @see #getU
188      * @see #getV
189      */
190     public Vector3D getNormal() {
191         return w;
192     }
193 
194     /** Get the plane first canonical vector.
195      * <p>The frame defined by ({@link #getU getU}, {@link #getV getV},
196      * {@link #getNormal getNormal}) is a rigth-handed orthonormalized
197      * frame).</p>
198      * @return normalized first canonical vector
199      * @see #getV
200      * @see #getNormal
201      */
202     public Vector3D getU() {
203         return u;
204     }
205 
206     /** Get the plane second canonical vector.
207      * <p>The frame defined by ({@link #getU getU}, {@link #getV getV},
208      * {@link #getNormal getNormal}) is a rigth-handed orthonormalized
209      * frame).</p>
210      * @return normalized second canonical vector
211      * @see #getU
212      * @see #getNormal
213      */
214     public Vector3D getV() {
215         return v;
216     }
217 
218     /** {@inheritDoc}
219      */
220     @Override
221     public Point<Euclidean3D> project(Point<Euclidean3D> point) {
222         return toSpace(toSubSpace(point));
223     }
224 
225     /** {@inheritDoc}
226      */
227     @Override
228     public double getTolerance() {
229         return tolerance;
230     }
231 
232     /** Revert the plane.
233      * <p>Replace the instance by a similar plane with opposite orientation.</p>
234      * <p>The new plane frame is chosen in such a way that a 3D point that had
235      * {@code (x, y)} in-plane coordinates and {@code z} offset with
236      * respect to the plane and is unaffected by the change will have
237      * {@code (y, x)} in-plane coordinates and {@code -z} offset with
238      * respect to the new plane. This means that the {@code u} and {@code v}
239      * vectors returned by the {@link #getU} and {@link #getV} methods are exchanged,
240      * and the {@code w} vector returned by the {@link #getNormal} method is
241      * reversed.</p>
242      */
243     public void revertSelf() {
244         final Vector3D tmp = u;
245         u = v;
246         v = tmp;
247         w = w.negate();
248         originOffset = -originOffset;
249     }
250 
251     /** Transform a space point into a sub-space point.
252      * @param vector n-dimension point of the space
253      * @return (n-1)-dimension point of the sub-space corresponding to
254      * the specified space point
255      */
256     public Vector2D toSubSpace(Vector<Euclidean3D, Vector3D> vector) {
257         return toSubSpace((Point<Euclidean3D>) vector);
258     }
259 
260     /** Transform a sub-space point into a space point.
261      * @param vector (n-1)-dimension point of the sub-space
262      * @return n-dimension point of the space corresponding to the
263      * specified sub-space point
264      */
265     public Vector3D toSpace(Vector<Euclidean2D, Vector2D> vector) {
266         return toSpace((Point<Euclidean2D>) vector);
267     }
268 
269     /** Transform a 3D space point into an in-plane point.
270      * @param point point of the space (must be a {@link Vector3D
271      * Vector3D} instance)
272      * @return in-plane point (really a {@link
273      * org.hipparchus.geometry.euclidean.twod.Vector2D Vector2D} instance)
274      * @see #toSpace
275      */
276     @Override
277     public Vector2D toSubSpace(final Point<Euclidean3D> point) {
278         final Vector3D p3D = (Vector3D) point;
279         return new Vector2D(p3D.dotProduct(u), p3D.dotProduct(v));
280     }
281 
282     /** Transform an in-plane point into a 3D space point.
283      * @param point in-plane point (must be a {@link
284      * org.hipparchus.geometry.euclidean.twod.Vector2D Vector2D} instance)
285      * @return 3D space point (really a {@link Vector3D Vector3D} instance)
286      * @see #toSubSpace
287      */
288     @Override
289     public Vector3D toSpace(final Point<Euclidean2D> point) {
290         final Vector2D p2D = (Vector2D) point;
291         return new Vector3D(p2D.getX(), u, p2D.getY(), v, -originOffset, w);
292     }
293 
294     /** Get one point from the 3D-space.
295      * @param inPlane desired in-plane coordinates for the point in the
296      * plane
297      * @param offset desired offset for the point
298      * @return one point in the 3D-space, with given coordinates and offset
299      * relative to the plane
300      */
301     public Vector3D getPointAt(final Vector2D inPlane, final double offset) {
302         return new Vector3D(inPlane.getX(), u, inPlane.getY(), v, offset - originOffset, w);
303     }
304 
305     /** Check if the instance is similar to another plane.
306      * <p>Planes are considered similar if they contain the same
307      * points. This does not mean they are equal since they can have
308      * opposite normals.</p>
309      * @param plane plane to which the instance is compared
310      * @return true if the planes are similar
311      */
312     public boolean isSimilarTo(final Plane plane) {
313         final double angle = Vector3D.angle(w, plane.w);
314         return ((angle < 1.0e-10) && (FastMath.abs(originOffset - plane.originOffset) < tolerance)) ||
315                ((angle > (FastMath.PI - 1.0e-10)) && (FastMath.abs(originOffset + plane.originOffset) < tolerance));
316     }
317 
318     /** Rotate the plane around the specified point.
319      * <p>The instance is not modified, a new instance is created.</p>
320      * @param center rotation center
321      * @param rotation vectorial rotation operator
322      * @return a new plane
323      */
324     public Plane rotate(final Vector3D center, final Rotation rotation) {
325 
326         final Vector3D delta = origin.subtract(center);
327         final Plane plane = new Plane(center.add(rotation.applyTo(delta)),
328                                       rotation.applyTo(w), tolerance);
329 
330         // make sure the frame is transformed as desired
331         plane.u = rotation.applyTo(u);
332         plane.v = rotation.applyTo(v);
333 
334         return plane;
335 
336     }
337 
338     /** Translate the plane by the specified amount.
339      * <p>The instance is not modified, a new instance is created.</p>
340      * @param translation translation to apply
341      * @return a new plane
342      */
343     public Plane translate(final Vector3D translation) {
344 
345         final Plane plane = new Plane(origin.add(translation), w, tolerance);
346 
347         // make sure the frame is transformed as desired
348         plane.u = u;
349         plane.v = v;
350 
351         return plane;
352 
353     }
354 
355     /** Get the intersection of a line with the instance.
356      * @param line line intersecting the instance
357      * @return intersection point between between the line and the
358      * instance (null if the line is parallel to the instance)
359      */
360     public Vector3D intersection(final Line line) {
361         final Vector3D direction = line.getDirection();
362         final double   dot       = w.dotProduct(direction);
363         if (FastMath.abs(dot) < 1.0e-10) {
364             return null;
365         }
366         final Vector3D point = line.toSpace((Point<Euclidean1D>) Vector1D.ZERO);
367         final double   k     = -(originOffset + w.dotProduct(point)) / dot;
368         return new Vector3D(1.0, point, k, direction);
369     }
370 
371     /** Build the line shared by the instance and another plane.
372      * @param other other plane
373      * @return line at the intersection of the instance and the
374      * other plane (really a {@link Line Line} instance)
375      */
376     public Line intersection(final Plane other) {
377         final Vector3D direction = Vector3D.crossProduct(w, other.w);
378         if (direction.getNorm() < tolerance) {
379             return null;
380         }
381         final Vector3D point = intersection(this, other, new Plane(direction, tolerance));
382         return new Line(point, point.add(direction), tolerance);
383     }
384 
385     /** Get the intersection point of three planes.
386      * @param plane1 first plane1
387      * @param plane2 second plane2
388      * @param plane3 third plane2
389      * @return intersection point of three planes, null if some planes are parallel
390      */
391     public static Vector3D intersection(final Plane plane1, final Plane plane2, final Plane plane3) {
392 
393         // coefficients of the three planes linear equations
394         final double a1 = plane1.w.getX();
395         final double b1 = plane1.w.getY();
396         final double c1 = plane1.w.getZ();
397         final double d1 = plane1.originOffset;
398 
399         final double a2 = plane2.w.getX();
400         final double b2 = plane2.w.getY();
401         final double c2 = plane2.w.getZ();
402         final double d2 = plane2.originOffset;
403 
404         final double a3 = plane3.w.getX();
405         final double b3 = plane3.w.getY();
406         final double c3 = plane3.w.getZ();
407         final double d3 = plane3.originOffset;
408 
409         // direct Cramer resolution of the linear system
410         // (this is still feasible for a 3x3 system)
411         final double a23         = b2 * c3 - b3 * c2;
412         final double b23         = c2 * a3 - c3 * a2;
413         final double c23         = a2 * b3 - a3 * b2;
414         final double determinant = a1 * a23 + b1 * b23 + c1 * c23;
415         if (FastMath.abs(determinant) < 1.0e-10) {
416             return null;
417         }
418 
419         final double r = 1.0 / determinant;
420         return new Vector3D(
421                             (-a23 * d1 - (c1 * b3 - c3 * b1) * d2 - (c2 * b1 - c1 * b2) * d3) * r,
422                             (-b23 * d1 - (c3 * a1 - c1 * a3) * d2 - (c1 * a2 - c2 * a1) * d3) * r,
423                             (-c23 * d1 - (b1 * a3 - b3 * a1) * d2 - (b2 * a1 - b1 * a2) * d3) * r);
424 
425     }
426 
427     /** Build a region covering the whole hyperplane.
428      * @return a region covering the whole hyperplane
429      */
430     @Override
431     public SubPlane wholeHyperplane() {
432         return new SubPlane(this, new PolygonsSet(tolerance));
433     }
434 
435     /** {@inheritDoc} */
436     @Override
437     public SubPlane emptyHyperplane() {
438         return new SubPlane(this, new RegionFactory<Euclidean2D>().getComplement(new PolygonsSet(tolerance)));
439     }
440 
441     /** Build a region covering the whole space.
442      * @return a region containing the instance (really a {@link
443      * PolyhedronsSet PolyhedronsSet} instance)
444      */
445     @Override
446     public PolyhedronsSet wholeSpace() {
447         return new PolyhedronsSet(tolerance);
448     }
449 
450     /** Check if the instance contains a point.
451      * @param p point to check
452      * @return true if p belongs to the plane
453      */
454     public boolean contains(final Vector3D p) {
455         return FastMath.abs(getOffset(p)) < tolerance;
456     }
457 
458     /** Get the offset (oriented distance) of a parallel plane.
459      * <p>This method should be called only for parallel planes otherwise
460      * the result is not meaningful.</p>
461      * <p>The offset is 0 if both planes are the same, it is
462      * positive if the plane is on the plus side of the instance and
463      * negative if it is on the minus side, according to its natural
464      * orientation.</p>
465      * @param plane plane to check
466      * @return offset of the plane
467      */
468     public double getOffset(final Plane plane) {
469         return originOffset + (sameOrientationAs(plane) ? -plane.originOffset : plane.originOffset);
470     }
471 
472     /** Get the offset (oriented distance) of a vector.
473      * @param vector vector to check
474      * @return offset of the vector
475      */
476     public double getOffset(Vector<Euclidean3D, Vector3D> vector) {
477         return getOffset((Point<Euclidean3D>) vector);
478     }
479 
480     /** Get the offset (oriented distance) of a point.
481      * <p>The offset is 0 if the point is on the underlying hyperplane,
482      * it is positive if the point is on one particular side of the
483      * hyperplane, and it is negative if the point is on the other side,
484      * according to the hyperplane natural orientation.</p>
485      * @param point point to check
486      * @return offset of the point
487      */
488     @Override
489     public double getOffset(final Point<Euclidean3D> point) {
490         return ((Vector3D) point).dotProduct(w) + originOffset;
491     }
492 
493     /** Check if the instance has the same orientation as another hyperplane.
494      * @param other other hyperplane to check against the instance
495      * @return true if the instance and the other hyperplane have
496      * the same orientation
497      */
498     @Override
499     public boolean sameOrientationAs(final Hyperplane<Euclidean3D> other) {
500         return (((Plane) other).w).dotProduct(w) > 0.0;
501     }
502 
503 }