EmbeddingPlane.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.geometry.euclidean.threed;

  18. import java.util.Objects;

  19. import org.apache.commons.geometry.core.Transform;
  20. import org.apache.commons.geometry.core.partitioning.EmbeddingHyperplane;
  21. import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
  22. import org.apache.commons.geometry.euclidean.twod.AffineTransformMatrix2D;
  23. import org.apache.commons.geometry.euclidean.twod.Vector2D;
  24. import org.apache.commons.numbers.core.Precision;

  25. /** Extension of the {@link Plane} class that supports embedding of 2D subspaces in the plane.
  26.  * This is accomplished by defining two additional vectors, {@link #getU() u} and {@link #getV() v},
  27.  * that define the {@code x} and {@code y} axes respectively of the embedded subspace. For completeness,
  28.  * an additional vector {@link #getW()} is defined, which is simply an alias for the plane normal.
  29.  * Together, the vectors {@code u}, {@code v}, and {@code w} form a right-handed orthonormal basis.
  30.  *
  31.  * <p>The additional {@code u} and {@code v} vectors are not required to fulfill the contract of
  32.  * {@link org.apache.commons.geometry.core.partitioning.Hyperplane Hyperplane}. Therefore, they
  33.  * are not considered when using instances of this type purely as a hyperplane. For example, the
  34.  * {@link Plane#eq(Plane, Precision.DoubleEquivalence) eq} and
  35.  * {@link Plane#similarOrientation(org.apache.commons.geometry.core.partitioning.Hyperplane) similiarOrientation}
  36.  * methods do not consider them.</p>
  37.  */
  38. public final class EmbeddingPlane extends Plane implements EmbeddingHyperplane<Vector3D, Vector2D> {
  39.     /** First normalized vector of the plane frame (in plane). */
  40.     private final Vector3D.Unit u;

  41.     /** Second normalized vector of the plane frame (in plane). */
  42.     private final Vector3D.Unit v;

  43.     /** Construct a new instance from an orthonormal set of basis vectors and an origin offset.
  44.      * @param u first vector of the basis (in plane)
  45.      * @param v second vector of the basis (in plane)
  46.      * @param w third vector of the basis (plane normal)
  47.      * @param originOffset offset of the origin with respect to the plane.
  48.      * @param precision precision context used for floating point comparisons
  49.      */
  50.     EmbeddingPlane(final Vector3D.Unit u, final Vector3D.Unit v, final Vector3D.Unit w, final double originOffset,
  51.                    final Precision.DoubleEquivalence precision) {
  52.         super(w, originOffset, precision);

  53.         this.u = u;
  54.         this.v = v;
  55.     }

  56.     /** Get the plane first canonical vector.
  57.      * <p>
  58.      * The frame defined by ({@link #getU u}, {@link #getV v},
  59.      * {@link #getW w}) is a right-handed orthonormalized frame).
  60.      * </p>
  61.      * @return normalized first canonical vector
  62.      * @see #getV
  63.      * @see #getW
  64.      * @see #getNormal
  65.      */
  66.     public Vector3D.Unit getU() {
  67.         return u;
  68.     }

  69.     /** Get the plane second canonical vector.
  70.      * <p>
  71.      * The frame defined by ({@link #getU u}, {@link #getV v},
  72.      * {@link #getW w}) is a right-handed orthonormalized frame).
  73.      * </p>
  74.      * @return normalized second canonical vector
  75.      * @see #getU
  76.      * @see #getW
  77.      * @see #getNormal
  78.      */
  79.     public Vector3D.Unit getV() {
  80.         return v;
  81.     }

  82.     /** Get the plane third canonical vector, ie, the plane normal. This
  83.      * method is simply an alias for {@link #getNormal()}.
  84.      * <p>
  85.      * The frame defined by {@link #getU() u}, {@link #getV() v},
  86.      * {@link #getW() w} is a right-handed orthonormalized frame.
  87.      * </p>
  88.      * @return normalized normal vector
  89.      * @see #getU()
  90.      * @see #getV()
  91.      * @see #getNormal()
  92.      */
  93.     public Vector3D.Unit getW() {
  94.         return getNormal();
  95.     }

  96.     /** Return the current instance.
  97.      */
  98.     @Override
  99.     public EmbeddingPlane getEmbedding() {
  100.         return this;
  101.     }

  102.     /** Transform a 3D space point into an in-plane point.
  103.      * @param point point of the space
  104.      * @return in-plane point
  105.      * @see #toSpace
  106.      */
  107.     @Override
  108.     public Vector2D toSubspace(final Vector3D point) {
  109.         return Vector2D.of(point.dot(u), point.dot(v));
  110.     }

  111.     /** Transform an in-plane point into a 3D space point.
  112.      * @param point in-plane point
  113.      * @return 3D space point
  114.      * @see #toSubspace(Vector3D)
  115.      */
  116.     @Override
  117.     public Vector3D toSpace(final Vector2D point) {
  118.         return Vector3D.Sum.create()
  119.                 .addScaled(point.getX(), u)
  120.                 .addScaled(point.getY(), v)
  121.                 .addScaled(-getOriginOffset(), getNormal()).get();
  122.     }

  123.     /** Get one point from the 3D-space.
  124.      * @param inPlane desired in-plane coordinates for the point in the plane
  125.      * @param offset  desired offset for the point
  126.      * @return one point in the 3D-space, with given coordinates and offset relative
  127.      *         to the plane
  128.      */
  129.     public Vector3D pointAt(final Vector2D inPlane, final double offset) {
  130.         return Vector3D.Sum.create()
  131.                 .addScaled(inPlane.getX(), u)
  132.                 .addScaled(inPlane.getY(), v)
  133.                 .addScaled(offset - getOriginOffset(), getNormal()).get();
  134.     }

  135.     /** Build a new reversed version of this plane, with opposite orientation.
  136.      * <p>
  137.      * The new plane frame is chosen in such a way that a 3D point that had
  138.      * {@code (x, y)} in-plane coordinates and {@code z} offset with respect to the
  139.      * plane and is unaffected by the change will have {@code (y, x)} in-plane
  140.      * coordinates and {@code -z} offset with respect to the new plane. This means
  141.      * that the {@code u} and {@code v} vectors returned by the {@link #getU} and
  142.      * {@link #getV} methods are exchanged, and the {@code w} vector returned by the
  143.      * {@link #getNormal} method is reversed.
  144.      * </p>
  145.      * @return a new reversed plane
  146.      */
  147.     @Override
  148.     public EmbeddingPlane reverse() {
  149.         return new EmbeddingPlane(v, u, getNormal().negate(), -getOriginOffset(), getPrecision());
  150.     }

  151.     /** {@inheritDoc} */
  152.     @Override
  153.     public EmbeddingPlane transform(final Transform<Vector3D> transform) {
  154.         final Vector3D origin = getOrigin();
  155.         final Vector3D plusU = origin.add(u);
  156.         final Vector3D plusV = origin.add(v);

  157.         final Vector3D tOrigin = transform.apply(origin);
  158.         final Vector3D tPlusU = transform.apply(plusU);
  159.         final Vector3D tPlusV = transform.apply(plusV);

  160.         final Vector3D.Unit tU = tOrigin.directionTo(tPlusU);
  161.         final Vector3D.Unit tV = tOrigin.directionTo(tPlusV);
  162.         final Vector3D.Unit tW = tU.cross(tV).normalize();

  163.         final double tOriginOffset = -tOrigin.dot(tW);

  164.         return new EmbeddingPlane(tU, tV, tW, tOriginOffset, getPrecision());
  165.     }

  166.     /** Translate the plane by the specified amount.
  167.      * @param translation translation to apply
  168.      * @return a new plane
  169.      */
  170.     @Override
  171.     public EmbeddingPlane translate(final Vector3D translation) {
  172.         final Vector3D tOrigin = getOrigin().add(translation);

  173.         return Planes.fromPointAndPlaneVectors(tOrigin, u, v, getPrecision());
  174.     }

  175.     /** Rotate the plane around the specified point.
  176.      * @param center rotation center
  177.      * @param rotation 3-dimensional rotation
  178.      * @return a new rotated plane
  179.      */
  180.     @Override
  181.     public EmbeddingPlane rotate(final Vector3D center, final QuaternionRotation rotation) {
  182.         final Vector3D delta = getOrigin().subtract(center);
  183.         final Vector3D tOrigin = center.add(rotation.apply(delta));
  184.         final Vector3D.Unit tU = rotation.apply(u).normalize();
  185.         final Vector3D.Unit tV = rotation.apply(v).normalize();

  186.         return Planes.fromPointAndPlaneVectors(tOrigin, tU, tV, getPrecision());
  187.     }

  188.     /** {@inheritDoc} */
  189.     @Override
  190.     public int hashCode() {
  191.         return Objects.hash(getNormal(), getOriginOffset(), u, v, getPrecision());
  192.     }

  193.     /** {@inheritDoc} */
  194.     @Override
  195.     public boolean equals(final Object obj) {
  196.         if (this == obj) {
  197.             return true;
  198.         } else if (obj == null || obj.getClass() != EmbeddingPlane.class) {
  199.             return false;
  200.         }

  201.         final EmbeddingPlane other = (EmbeddingPlane) obj;

  202.         return Objects.equals(this.getNormal(), other.getNormal()) &&
  203.                 Double.compare(this.getOriginOffset(), other.getOriginOffset()) == 0 &&
  204.                 Objects.equals(this.u, other.u) &&
  205.                 Objects.equals(this.v, other.v) &&
  206.                 Objects.equals(this.getPrecision(), other.getPrecision());
  207.     }

  208.     /** {@inheritDoc} */
  209.     @Override
  210.     public String toString() {
  211.         final StringBuilder sb = new StringBuilder();
  212.         sb.append(getClass().getSimpleName())
  213.             .append("[origin= ")
  214.             .append(getOrigin())
  215.             .append(", u= ")
  216.             .append(u)
  217.             .append(", v= ")
  218.             .append(v)
  219.             .append(", w= ")
  220.             .append(getNormal())
  221.             .append(']');

  222.         return sb.toString();
  223.     }

  224.     /** Get an object containing the current plane transformed by the argument along with a
  225.      * 2D transform that can be applied to subspace points. The subspace transform transforms
  226.      * subspace points such that their 3D location in the transformed plane is the same as their
  227.      * 3D location in the original plane after the 3D transform is applied. For example, consider
  228.      * the code below:
  229.      * <pre>
  230.      *      SubspaceTransform st = plane.subspaceTransform(transform);
  231.      *
  232.      *      Vector2D subPt = Vector2D.of(1, 1);
  233.      *
  234.      *      Vector3D a = transform.apply(plane.toSpace(subPt)); // transform in 3D space
  235.      *      Vector3D b = st.getPlane().toSpace(st.getTransform().apply(subPt)); // transform in 2D space
  236.      * </pre>
  237.      * At the end of execution, the points {@code a} (which was transformed using the original
  238.      * 3D transform) and {@code b} (which was transformed in 2D using the subspace transform)
  239.      * are equivalent.
  240.      *
  241.      * @param transform the transform to apply to this instance
  242.      * @return an object containing the transformed plane along with a transform that can be applied
  243.      *      to subspace points
  244.      * @see #transform(Transform)
  245.      */
  246.     public SubspaceTransform subspaceTransform(final Transform<Vector3D> transform) {
  247.         final Vector3D origin = getOrigin();

  248.         final Vector3D tOrigin = transform.apply(origin);
  249.         final Vector3D tPlusU = transform.apply(origin.add(u));
  250.         final Vector3D tPlusV = transform.apply(origin.add(v));

  251.         final EmbeddingPlane tPlane = Planes.fromPointAndPlaneVectors(
  252.                 tOrigin,
  253.                 tOrigin.vectorTo(tPlusU),
  254.                 tOrigin.vectorTo(tPlusV),
  255.                 getPrecision());

  256.         final Vector2D tSubspaceOrigin = tPlane.toSubspace(tOrigin);
  257.         final Vector2D tSubspaceU = tSubspaceOrigin.vectorTo(tPlane.toSubspace(tPlusU));
  258.         final Vector2D tSubspaceV = tSubspaceOrigin.vectorTo(tPlane.toSubspace(tPlusV));

  259.         final AffineTransformMatrix2D subspaceTransform =
  260.                 AffineTransformMatrix2D.fromColumnVectors(tSubspaceU, tSubspaceV, tSubspaceOrigin);

  261.         return new SubspaceTransform(tPlane, subspaceTransform);
  262.     }

  263.     /** Class containing a transformed plane instance along with a subspace (2D) transform. The subspace
  264.      * transform produces the equivalent of the 3D transform in 2D.
  265.      */
  266.     public static final class SubspaceTransform {
  267.         /** The transformed plane. */
  268.         private final EmbeddingPlane plane;

  269.         /** The subspace transform instance. */
  270.         private final AffineTransformMatrix2D transform;

  271.         /** Simple constructor.
  272.          * @param plane the transformed plane
  273.          * @param transform 2D transform that can be applied to subspace points
  274.          */
  275.         public SubspaceTransform(final EmbeddingPlane plane, final AffineTransformMatrix2D transform) {
  276.             this.plane = plane;
  277.             this.transform = transform;
  278.         }

  279.         /** Get the transformed plane instance.
  280.          * @return the transformed plane instance
  281.          */
  282.         public EmbeddingPlane getPlane() {
  283.             return plane;
  284.         }

  285.         /** Get the 2D transform that can be applied to subspace points. This transform can be used
  286.          * to perform the equivalent of the 3D transform in 2D space.
  287.          * @return the subspace transform instance
  288.          */
  289.         public AffineTransformMatrix2D getTransform() {
  290.             return transform;
  291.         }
  292.     }
  293. }