Plane.java

  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.  *      http://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. package org.apache.commons.math3.geometry.euclidean.threed;

  18. import org.apache.commons.math3.exception.MathArithmeticException;
  19. import org.apache.commons.math3.exception.util.LocalizedFormats;
  20. import org.apache.commons.math3.geometry.Point;
  21. import org.apache.commons.math3.geometry.Vector;
  22. import org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D;
  23. import org.apache.commons.math3.geometry.euclidean.oned.Vector1D;
  24. import org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D;
  25. import org.apache.commons.math3.geometry.euclidean.twod.PolygonsSet;
  26. import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
  27. import org.apache.commons.math3.geometry.partitioning.Embedding;
  28. import org.apache.commons.math3.geometry.partitioning.Hyperplane;
  29. import org.apache.commons.math3.util.FastMath;

  30. /** The class represent planes in a three dimensional space.
  31.  * @since 3.0
  32.  */
  33. public class Plane implements Hyperplane<Euclidean3D>, Embedding<Euclidean3D, Euclidean2D> {

  34.     /** Default value for tolerance. */
  35.     private static final double DEFAULT_TOLERANCE = 1.0e-10;

  36.     /** Offset of the origin with respect to the plane. */
  37.     private double originOffset;

  38.     /** Origin of the plane frame. */
  39.     private Vector3D origin;

  40.     /** First vector of the plane frame (in plane). */
  41.     private Vector3D u;

  42.     /** Second vector of the plane frame (in plane). */
  43.     private Vector3D v;

  44.     /** Third vector of the plane frame (plane normal). */
  45.     private Vector3D w;

  46.     /** Tolerance below which points are considered identical. */
  47.     private final double tolerance;

  48.     /** Build a plane normal to a given direction and containing the origin.
  49.      * @param normal normal direction to the plane
  50.      * @param tolerance tolerance below which points are considered identical
  51.      * @exception MathArithmeticException if the normal norm is too small
  52.      * @since 3.3
  53.      */
  54.     public Plane(final Vector3D normal, final double tolerance)
  55.         throws MathArithmeticException {
  56.         setNormal(normal);
  57.         this.tolerance = tolerance;
  58.         originOffset = 0;
  59.         setFrame();
  60.     }

  61.     /** Build a plane from a point and a normal.
  62.      * @param p point belonging to the plane
  63.      * @param normal normal direction to the plane
  64.      * @param tolerance tolerance below which points are considered identical
  65.      * @exception MathArithmeticException if the normal norm is too small
  66.      * @since 3.3
  67.      */
  68.     public Plane(final Vector3D p, final Vector3D normal, final double tolerance)
  69.         throws MathArithmeticException {
  70.         setNormal(normal);
  71.         this.tolerance = tolerance;
  72.         originOffset = -p.dotProduct(w);
  73.         setFrame();
  74.     }

  75.     /** Build a plane from three points.
  76.      * <p>The plane is oriented in the direction of
  77.      * {@code (p2-p1) ^ (p3-p1)}</p>
  78.      * @param p1 first point belonging to the plane
  79.      * @param p2 second point belonging to the plane
  80.      * @param p3 third point belonging to the plane
  81.      * @param tolerance tolerance below which points are considered identical
  82.      * @exception MathArithmeticException if the points do not constitute a plane
  83.      * @since 3.3
  84.      */
  85.     public Plane(final Vector3D p1, final Vector3D p2, final Vector3D p3, final double tolerance)
  86.         throws MathArithmeticException {
  87.         this(p1, p2.subtract(p1).crossProduct(p3.subtract(p1)), tolerance);
  88.     }

  89.     /** Build a plane normal to a given direction and containing the origin.
  90.      * @param normal normal direction to the plane
  91.      * @exception MathArithmeticException if the normal norm is too small
  92.      * @deprecated as of 3.3, replaced with {@link #Plane(Vector3D, double)}
  93.      */
  94.     @Deprecated
  95.     public Plane(final Vector3D normal) throws MathArithmeticException {
  96.         this(normal, DEFAULT_TOLERANCE);
  97.     }

  98.     /** Build a plane from a point and a normal.
  99.      * @param p point belonging to the plane
  100.      * @param normal normal direction to the plane
  101.      * @exception MathArithmeticException if the normal norm is too small
  102.      * @deprecated as of 3.3, replaced with {@link #Plane(Vector3D, Vector3D, double)}
  103.      */
  104.     @Deprecated
  105.     public Plane(final Vector3D p, final Vector3D normal) throws MathArithmeticException {
  106.         this(p, normal, DEFAULT_TOLERANCE);
  107.     }

  108.     /** Build a plane from three points.
  109.      * <p>The plane is oriented in the direction of
  110.      * {@code (p2-p1) ^ (p3-p1)}</p>
  111.      * @param p1 first point belonging to the plane
  112.      * @param p2 second point belonging to the plane
  113.      * @param p3 third point belonging to the plane
  114.      * @exception MathArithmeticException if the points do not constitute a plane
  115.      * @deprecated as of 3.3, replaced with {@link #Plane(Vector3D, Vector3D, Vector3D, double)}
  116.      */
  117.     @Deprecated
  118.     public Plane(final Vector3D p1, final Vector3D p2, final Vector3D p3)
  119.         throws MathArithmeticException {
  120.         this(p1, p2, p3, DEFAULT_TOLERANCE);
  121.     }

  122.     /** Copy constructor.
  123.      * <p>The instance created is completely independant of the original
  124.      * one. A deep copy is used, none of the underlying object are
  125.      * shared.</p>
  126.      * @param plane plane to copy
  127.      */
  128.     public Plane(final Plane plane) {
  129.         originOffset = plane.originOffset;
  130.         origin       = plane.origin;
  131.         u            = plane.u;
  132.         v            = plane.v;
  133.         w            = plane.w;
  134.         tolerance    = plane.tolerance;
  135.     }

  136.     /** Copy the instance.
  137.      * <p>The instance created is completely independant of the original
  138.      * one. A deep copy is used, none of the underlying objects are
  139.      * shared (except for immutable objects).</p>
  140.      * @return a new hyperplane, copy of the instance
  141.      */
  142.     public Plane copySelf() {
  143.         return new Plane(this);
  144.     }

  145.     /** Reset the instance as if built from a point and a normal.
  146.      * @param p point belonging to the plane
  147.      * @param normal normal direction to the plane
  148.      * @exception MathArithmeticException if the normal norm is too small
  149.      */
  150.     public void reset(final Vector3D p, final Vector3D normal) throws MathArithmeticException {
  151.         setNormal(normal);
  152.         originOffset = -p.dotProduct(w);
  153.         setFrame();
  154.     }

  155.     /** Reset the instance from another one.
  156.      * <p>The updated instance is completely independant of the original
  157.      * one. A deep reset is used none of the underlying object is
  158.      * shared.</p>
  159.      * @param original plane to reset from
  160.      */
  161.     public void reset(final Plane original) {
  162.         originOffset = original.originOffset;
  163.         origin       = original.origin;
  164.         u            = original.u;
  165.         v            = original.v;
  166.         w            = original.w;
  167.     }

  168.     /** Set the normal vactor.
  169.      * @param normal normal direction to the plane (will be copied)
  170.      * @exception MathArithmeticException if the normal norm is too small
  171.      */
  172.     private void setNormal(final Vector3D normal) throws MathArithmeticException {
  173.         final double norm = normal.getNorm();
  174.         if (norm < 1.0e-10) {
  175.             throw new MathArithmeticException(LocalizedFormats.ZERO_NORM);
  176.         }
  177.         w = new Vector3D(1.0 / norm, normal);
  178.     }

  179.     /** Reset the plane frame.
  180.      */
  181.     private void setFrame() {
  182.         origin = new Vector3D(-originOffset, w);
  183.         u = w.orthogonal();
  184.         v = Vector3D.crossProduct(w, u);
  185.     }

  186.     /** Get the origin point of the plane frame.
  187.      * <p>The point returned is the orthogonal projection of the
  188.      * 3D-space origin in the plane.</p>
  189.      * @return the origin point of the plane frame (point closest to the
  190.      * 3D-space origin)
  191.      */
  192.     public Vector3D getOrigin() {
  193.         return origin;
  194.     }

  195.     /** Get the normalized normal vector.
  196.      * <p>The frame defined by ({@link #getU getU}, {@link #getV getV},
  197.      * {@link #getNormal getNormal}) is a rigth-handed orthonormalized
  198.      * frame).</p>
  199.      * @return normalized normal vector
  200.      * @see #getU
  201.      * @see #getV
  202.      */
  203.     public Vector3D getNormal() {
  204.         return w;
  205.     }

  206.     /** Get the plane first 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 first canonical vector
  211.      * @see #getV
  212.      * @see #getNormal
  213.      */
  214.     public Vector3D getU() {
  215.         return u;
  216.     }

  217.     /** Get the plane second canonical vector.
  218.      * <p>The frame defined by ({@link #getU getU}, {@link #getV getV},
  219.      * {@link #getNormal getNormal}) is a rigth-handed orthonormalized
  220.      * frame).</p>
  221.      * @return normalized second canonical vector
  222.      * @see #getU
  223.      * @see #getNormal
  224.      */
  225.     public Vector3D getV() {
  226.         return v;
  227.     }

  228.     /** {@inheritDoc}
  229.      * @since 3.3
  230.      */
  231.     public Point<Euclidean3D> project(Point<Euclidean3D> point) {
  232.         return toSpace(toSubSpace(point));
  233.     }

  234.     /** {@inheritDoc}
  235.      * @since 3.3
  236.      */
  237.     public double getTolerance() {
  238.         return tolerance;
  239.     }

  240.     /** Revert the plane.
  241.      * <p>Replace the instance by a similar plane with opposite orientation.</p>
  242.      * <p>The new plane frame is chosen in such a way that a 3D point that had
  243.      * {@code (x, y)} in-plane coordinates and {@code z} offset with
  244.      * respect to the plane and is unaffected by the change will have
  245.      * {@code (y, x)} in-plane coordinates and {@code -z} offset with
  246.      * respect to the new plane. This means that the {@code u} and {@code v}
  247.      * vectors returned by the {@link #getU} and {@link #getV} methods are exchanged,
  248.      * and the {@code w} vector returned by the {@link #getNormal} method is
  249.      * reversed.</p>
  250.      */
  251.     public void revertSelf() {
  252.         final Vector3D tmp = u;
  253.         u = v;
  254.         v = tmp;
  255.         w = w.negate();
  256.         originOffset = -originOffset;
  257.     }

  258.     /** Transform a space point into a sub-space point.
  259.      * @param vector n-dimension point of the space
  260.      * @return (n-1)-dimension point of the sub-space corresponding to
  261.      * the specified space point
  262.      */
  263.     public Vector2D toSubSpace(Vector<Euclidean3D> vector) {
  264.         return toSubSpace((Point<Euclidean3D>) vector);
  265.     }

  266.     /** Transform a sub-space point into a space point.
  267.      * @param vector (n-1)-dimension point of the sub-space
  268.      * @return n-dimension point of the space corresponding to the
  269.      * specified sub-space point
  270.      */
  271.     public Vector3D toSpace(Vector<Euclidean2D> vector) {
  272.         return toSpace((Point<Euclidean2D>) vector);
  273.     }

  274.     /** Transform a 3D space point into an in-plane point.
  275.      * @param point point of the space (must be a {@link Vector3D
  276.      * Vector3D} instance)
  277.      * @return in-plane point (really a {@link
  278.      * org.apache.commons.math3.geometry.euclidean.twod.Vector2D Vector2D} instance)
  279.      * @see #toSpace
  280.      */
  281.     public Vector2D toSubSpace(final Point<Euclidean3D> point) {
  282.         final Vector3D p3D = (Vector3D) point;
  283.         return new Vector2D(p3D.dotProduct(u), p3D.dotProduct(v));
  284.     }

  285.     /** Transform an in-plane point into a 3D space point.
  286.      * @param point in-plane point (must be a {@link
  287.      * org.apache.commons.math3.geometry.euclidean.twod.Vector2D Vector2D} instance)
  288.      * @return 3D space point (really a {@link Vector3D Vector3D} instance)
  289.      * @see #toSubSpace
  290.      */
  291.     public Vector3D toSpace(final Point<Euclidean2D> point) {
  292.         final Vector2D p2D = (Vector2D) point;
  293.         return new Vector3D(p2D.getX(), u, p2D.getY(), v, -originOffset, w);
  294.     }

  295.     /** Get one point from the 3D-space.
  296.      * @param inPlane desired in-plane coordinates for the point in the
  297.      * plane
  298.      * @param offset desired offset for the point
  299.      * @return one point in the 3D-space, with given coordinates and offset
  300.      * relative to the plane
  301.      */
  302.     public Vector3D getPointAt(final Vector2D inPlane, final double offset) {
  303.         return new Vector3D(inPlane.getX(), u, inPlane.getY(), v, offset - originOffset, w);
  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.     /** Rotate the plane around the specified point.
  318.      * <p>The instance is not modified, a new instance is created.</p>
  319.      * @param center rotation center
  320.      * @param rotation vectorial rotation operator
  321.      * @return a new plane
  322.      */
  323.     public Plane rotate(final Vector3D center, final Rotation rotation) {

  324.         final Vector3D delta = origin.subtract(center);
  325.         final Plane plane = new Plane(center.add(rotation.applyTo(delta)),
  326.                                       rotation.applyTo(w), tolerance);

  327.         // make sure the frame is transformed as desired
  328.         plane.u = rotation.applyTo(u);
  329.         plane.v = rotation.applyTo(v);

  330.         return plane;

  331.     }

  332.     /** Translate the plane by the specified amount.
  333.      * <p>The instance is not modified, a new instance is created.</p>
  334.      * @param translation translation to apply
  335.      * @return a new plane
  336.      */
  337.     public Plane translate(final Vector3D translation) {

  338.         final Plane plane = new Plane(origin.add(translation), w, tolerance);

  339.         // make sure the frame is transformed as desired
  340.         plane.u = u;
  341.         plane.v = v;

  342.         return plane;

  343.     }

  344.     /** Get the intersection of a line with the instance.
  345.      * @param line line intersecting the instance
  346.      * @return intersection point between between the line and the
  347.      * instance (null if the line is parallel to the instance)
  348.      */
  349.     public Vector3D intersection(final Line line) {
  350.         final Vector3D direction = line.getDirection();
  351.         final double   dot       = w.dotProduct(direction);
  352.         if (FastMath.abs(dot) < 1.0e-10) {
  353.             return null;
  354.         }
  355.         final Vector3D point = line.toSpace((Point<Euclidean1D>) Vector1D.ZERO);
  356.         final double   k     = -(originOffset + w.dotProduct(point)) / dot;
  357.         return new Vector3D(1.0, point, k, direction);
  358.     }

  359.     /** Build the line shared by the instance and another plane.
  360.      * @param other other plane
  361.      * @return line at the intersection of the instance and the
  362.      * other plane (really a {@link Line Line} instance)
  363.      */
  364.     public Line intersection(final Plane other) {
  365.         final Vector3D direction = Vector3D.crossProduct(w, other.w);
  366.         if (direction.getNorm() < tolerance) {
  367.             return null;
  368.         }
  369.         final Vector3D point = intersection(this, other, new Plane(direction, tolerance));
  370.         return new Line(point, point.add(direction), tolerance);
  371.     }

  372.     /** Get the intersection point of three planes.
  373.      * @param plane1 first plane1
  374.      * @param plane2 second plane2
  375.      * @param plane3 third plane2
  376.      * @return intersection point of three planes, null if some planes are parallel
  377.      */
  378.     public static Vector3D intersection(final Plane plane1, final Plane plane2, final Plane plane3) {

  379.         // coefficients of the three planes linear equations
  380.         final double a1 = plane1.w.getX();
  381.         final double b1 = plane1.w.getY();
  382.         final double c1 = plane1.w.getZ();
  383.         final double d1 = plane1.originOffset;

  384.         final double a2 = plane2.w.getX();
  385.         final double b2 = plane2.w.getY();
  386.         final double c2 = plane2.w.getZ();
  387.         final double d2 = plane2.originOffset;

  388.         final double a3 = plane3.w.getX();
  389.         final double b3 = plane3.w.getY();
  390.         final double c3 = plane3.w.getZ();
  391.         final double d3 = plane3.originOffset;

  392.         // direct Cramer resolution of the linear system
  393.         // (this is still feasible for a 3x3 system)
  394.         final double a23         = b2 * c3 - b3 * c2;
  395.         final double b23         = c2 * a3 - c3 * a2;
  396.         final double c23         = a2 * b3 - a3 * b2;
  397.         final double determinant = a1 * a23 + b1 * b23 + c1 * c23;
  398.         if (FastMath.abs(determinant) < 1.0e-10) {
  399.             return null;
  400.         }

  401.         final double r = 1.0 / determinant;
  402.         return new Vector3D(
  403.                             (-a23 * d1 - (c1 * b3 - c3 * b1) * d2 - (c2 * b1 - c1 * b2) * d3) * r,
  404.                             (-b23 * d1 - (c3 * a1 - c1 * a3) * d2 - (c1 * a2 - c2 * a1) * d3) * r,
  405.                             (-c23 * d1 - (b1 * a3 - b3 * a1) * d2 - (b2 * a1 - b1 * a2) * d3) * r);

  406.     }

  407.     /** Build a region covering the whole hyperplane.
  408.      * @return a region covering the whole hyperplane
  409.      */
  410.     public SubPlane wholeHyperplane() {
  411.         return new SubPlane(this, new PolygonsSet(tolerance));
  412.     }

  413.     /** Build a region covering the whole space.
  414.      * @return a region containing the instance (really a {@link
  415.      * PolyhedronsSet PolyhedronsSet} instance)
  416.      */
  417.     public PolyhedronsSet wholeSpace() {
  418.         return new PolyhedronsSet(tolerance);
  419.     }

  420.     /** Check if the instance contains a point.
  421.      * @param p point to check
  422.      * @return true if p belongs to the plane
  423.      */
  424.     public boolean contains(final Vector3D p) {
  425.         return FastMath.abs(getOffset(p)) < tolerance;
  426.     }

  427.     /** Get the offset (oriented distance) of a parallel plane.
  428.      * <p>This method should be called only for parallel planes otherwise
  429.      * the result is not meaningful.</p>
  430.      * <p>The offset is 0 if both planes are the same, it is
  431.      * positive if the plane is on the plus side of the instance and
  432.      * negative if it is on the minus side, according to its natural
  433.      * orientation.</p>
  434.      * @param plane plane to check
  435.      * @return offset of the plane
  436.      */
  437.     public double getOffset(final Plane plane) {
  438.         return originOffset + (sameOrientationAs(plane) ? -plane.originOffset : plane.originOffset);
  439.     }

  440.     /** Get the offset (oriented distance) of a vector.
  441.      * @param vector vector to check
  442.      * @return offset of the vector
  443.      */
  444.     public double getOffset(Vector<Euclidean3D> vector) {
  445.         return getOffset((Point<Euclidean3D>) vector);
  446.     }

  447.     /** Get the offset (oriented distance) of a point.
  448.      * <p>The offset is 0 if the point is on the underlying hyperplane,
  449.      * it is positive if the point is on one particular side of the
  450.      * hyperplane, and it is negative if the point is on the other side,
  451.      * according to the hyperplane natural orientation.</p>
  452.      * @param point point to check
  453.      * @return offset of the point
  454.      */
  455.     public double getOffset(final Point<Euclidean3D> point) {
  456.         return ((Vector3D) point).dotProduct(w) + originOffset;
  457.     }

  458.     /** Check if the instance has the same orientation as another hyperplane.
  459.      * @param other other hyperplane to check against the instance
  460.      * @return true if the instance and the other hyperplane have
  461.      * the same orientation
  462.      */
  463.     public boolean sameOrientationAs(final Hyperplane<Euclidean3D> other) {
  464.         return (((Plane) other).w).dotProduct(w) > 0.0;
  465.     }

  466. }