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    *      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
    19  import java.util.Objects;
    20
    21  import org.apache.commons.geometry.core.Transform;
    22  import org.apache.commons.geometry.core.partitioning.EmbeddingHyperplane;
    23  import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
    24  import org.apache.commons.geometry.euclidean.twod.AffineTransformMatrix2D;
    25  import org.apache.commons.geometry.euclidean.twod.Vector2D;
    26  import org.apache.commons.numbers.core.Precision;
    27
    28  /** Extension of the {@link Plane} class that supports embedding of 2D subspaces in the plane.
    29   * This is accomplished by defining two additional vectors, {@link #getU() u} and {@link #getV() v},
    30   * that define the {@code x} and {@code y} axes respectively of the embedded subspace. For completeness,
    31   * an additional vector {@link #getW()} is defined, which is simply an alias for the plane normal.
    32   * Together, the vectors {@code u}, {@code v}, and {@code w} form a right-handed orthonormal basis.
    33   *
    34   * <p>The additional {@code u} and {@code v} vectors are not required to fulfill the contract of
    35   * {@link org.apache.commons.geometry.core.partitioning.Hyperplane Hyperplane}. Therefore, they
    36   * are not considered when using instances of this type purely as a hyperplane. For example, the
    37   * {@link Plane#eq(Plane, Precision.DoubleEquivalence) eq} and
    38   * {@link Plane#similarOrientation(org.apache.commons.geometry.core.partitioning.Hyperplane) similiarOrientation}
    39   * methods do not consider them.</p>
    40   */
    41  public final class EmbeddingPlane extends Plane implements EmbeddingHyperplane<Vector3D, Vector2D> {
    42      /** First normalized vector of the plane frame (in plane). */
    43      private final Vector3D.Unit u;
    44
    45      /** Second normalized vector of the plane frame (in plane). */
    46      private final Vector3D.Unit v;
    47
    48      /** Construct a new instance from an orthonormal set of basis vectors and an origin offset.
    49       * @param u first vector of the basis (in plane)
    50       * @param v second vector of the basis (in plane)
    51       * @param w third vector of the basis (plane normal)
    52       * @param originOffset offset of the origin with respect to the plane.
    53       * @param precision precision context used for floating point comparisons
    54       */
    55      EmbeddingPlane(final Vector3D.Unit u, final Vector3D.Unit v, final Vector3D.Unit w, final double originOffset,
    56                     final Precision.DoubleEquivalence precision) {
    57          super(w, originOffset, precision);
    58
    59          this.u = u;
    60          this.v = v;
    61      }
    62
    63      /** Get the plane first canonical vector.
    64       * <p>
    65       * The frame defined by ({@link #getU u}, {@link #getV v},
    66       * {@link #getW w}) is a right-handed orthonormalized frame).
    67       * </p>
    68       * @return normalized first canonical vector
    69       * @see #getV
    70       * @see #getW
    71       * @see #getNormal
    72       */
    73      public Vector3D.Unit getU() {
    74          return u;
    75      }
    76
    77      /** Get the plane second canonical vector.
    78       * <p>
    79       * The frame defined by ({@link #getU u}, {@link #getV v},
    80       * {@link #getW w}) is a right-handed orthonormalized frame).
    81       * </p>
    82       * @return normalized second canonical vector
    83       * @see #getU
    84       * @see #getW
    85       * @see #getNormal
    86       */
    87      public Vector3D.Unit getV() {
    88          return v;
    89      }
    90
    91      /** Get the plane third canonical vector, ie, the plane normal. This
    92       * method is simply an alias for {@link #getNormal()}.
    93       * <p>
    94       * The frame defined by {@link #getU() u}, {@link #getV() v},
    95       * {@link #getW() w} is a right-handed orthonormalized frame.
    96       * </p>
    97       * @return normalized normal vector
    98       * @see #getU()
    99       * @see #getV()
    100      * @see #getNormal()
    101      */
    102     public Vector3D.Unit getW() {
    103         return getNormal();
    104     }
    105
    106     /** Return the current instance.
    107      */
    108     @Override
    109     public EmbeddingPlane getEmbedding() {
    110         return this;
    111     }
    112
    113     /** Transform a 3D space point into an in-plane point.
    114      * @param point point of the space
    115      * @return in-plane point
    116      * @see #toSpace
    117      */
    118     @Override
    119     public Vector2D toSubspace(final Vector3D point) {
    120         return Vector2D.of(point.dot(u), point.dot(v));
    121     }
    122
    123     /** Transform an in-plane point into a 3D space point.
    124      * @param point in-plane point
    125      * @return 3D space point
    126      * @see #toSubspace(Vector3D)
    127      */
    128     @Override
    129     public Vector3D toSpace(final Vector2D point) {
    130         return Vector3D.Sum.create()
    131                 .addScaled(point.getX(), u)
    132                 .addScaled(point.getY(), v)
    133                 .addScaled(-getOriginOffset(), getNormal()).get();
    134     }
    135
    136     /** Get one point from the 3D-space.
    137      * @param inPlane desired in-plane coordinates for the point in the plane
    138      * @param offset  desired offset for the point
    139      * @return one point in the 3D-space, with given coordinates and offset relative
    140      *         to the plane
    141      */
    142     public Vector3D pointAt(final Vector2D inPlane, final double offset) {
    143         return Vector3D.Sum.create()
    144                 .addScaled(inPlane.getX(), u)
    145                 .addScaled(inPlane.getY(), v)
    146                 .addScaled(offset - getOriginOffset(), getNormal()).get();
    147     }
    148
    149     /** Build a new reversed version of this plane, with opposite orientation.
    150      * <p>
    151      * The new plane frame is chosen in such a way that a 3D point that had
    152      * {@code (x, y)} in-plane coordinates and {@code z} offset with respect to the
    153      * plane and is unaffected by the change will have {@code (y, x)} in-plane
    154      * coordinates and {@code -z} offset with respect to the new plane. This means
    155      * that the {@code u} and {@code v} vectors returned by the {@link #getU} and
    156      * {@link #getV} methods are exchanged, and the {@code w} vector returned by the
    157      * {@link #getNormal} method is reversed.
    158      * </p>
    159      * @return a new reversed plane
    160      */
    161     @Override
    162     public EmbeddingPlane reverse() {
    163         return new EmbeddingPlane(v, u, getNormal().negate(), -getOriginOffset(), getPrecision());
    164     }
    165
    166     /** {@inheritDoc} */
    167     @Override
    168     public EmbeddingPlane transform(final Transform<Vector3D> transform) {
    169         final Vector3D origin = getOrigin();
    170         final Vector3D plusU = origin.add(u);
    171         final Vector3D plusV = origin.add(v);
    172
    173         final Vector3D tOrigin = transform.apply(origin);
    174         final Vector3D tPlusU = transform.apply(plusU);
    175         final Vector3D tPlusV = transform.apply(plusV);
    176
    177         final Vector3D.Unit tU = tOrigin.directionTo(tPlusU);
    178         final Vector3D.Unit tV = tOrigin.directionTo(tPlusV);
    179         final Vector3D.Unit tW = tU.cross(tV).normalize();
    180
    181         final double tOriginOffset = -tOrigin.dot(tW);
    182
    183         return new EmbeddingPlane(tU, tV, tW, tOriginOffset, getPrecision());
    184     }
    185
    186     /** Translate the plane by the specified amount.
    187      * @param translation translation to apply
    188      * @return a new plane
    189      */
    190     @Override
    191     public EmbeddingPlane translate(final Vector3D translation) {
    192         final Vector3D tOrigin = getOrigin().add(translation);
    193
    194         return Planes.fromPointAndPlaneVectors(tOrigin, u, v, getPrecision());
    195     }
    196
    197     /** Rotate the plane around the specified point.
    198      * @param center rotation center
    199      * @param rotation 3-dimensional rotation
    200      * @return a new rotated plane
    201      */
    202     @Override
    203     public EmbeddingPlane rotate(final Vector3D center, final QuaternionRotation rotation) {
    204         final Vector3D delta = getOrigin().subtract(center);
    205         final Vector3D tOrigin = center.add(rotation.apply(delta));
    206         final Vector3D.Unit tU = rotation.apply(u).normalize();
    207         final Vector3D.Unit tV = rotation.apply(v).normalize();
    208
    209         return Planes.fromPointAndPlaneVectors(tOrigin, tU, tV, getPrecision());
    210     }
    211
    212     /** {@inheritDoc} */
    213     @Override
    214     public int hashCode() {
    215         return Objects.hash(getNormal(), getOriginOffset(), u, v, getPrecision());
    216     }
    217
    218     /** {@inheritDoc} */
    219     @Override
    220     public boolean equals(final Object obj) {
    221         if (this == obj) {
    222             return true;
    223         } else if (obj == null || obj.getClass() != EmbeddingPlane.class) {
    224             return false;
    225         }
    226
    227         final EmbeddingPlane other = (EmbeddingPlane) obj;
    228
    229         return Objects.equals(this.getNormal(), other.getNormal()) &&
    230                 Double.compare(this.getOriginOffset(), other.getOriginOffset()) == 0 &&
    231                 Objects.equals(this.u, other.u) &&
    232                 Objects.equals(this.v, other.v) &&
    233                 Objects.equals(this.getPrecision(), other.getPrecision());
    234     }
    235
    236     /** {@inheritDoc} */
    237     @Override
    238     public String toString() {
    239         final StringBuilder sb = new StringBuilder();
    240         sb.append(getClass().getSimpleName())
    241             .append("[origin= ")
    242             .append(getOrigin())
    243             .append(", u= ")
    244             .append(u)
    245             .append(", v= ")
    246             .append(v)
    247             .append(", w= ")
    248             .append(getNormal())
    249             .append(']');
    250
    251         return sb.toString();
    252     }
    253
    254     /** Get an object containing the current plane transformed by the argument along with a
    255      * 2D transform that can be applied to subspace points. The subspace transform transforms
    256      * subspace points such that their 3D location in the transformed plane is the same as their
    257      * 3D location in the original plane after the 3D transform is applied. For example, consider
    258      * the code below:
    259      * <pre>
    260      *      SubspaceTransform st = plane.subspaceTransform(transform);
    261      *
    262      *      Vector2D subPt = Vector2D.of(1, 1);
    263      *
    264      *      Vector3D a = transform.apply(plane.toSpace(subPt)); // transform in 3D space
    265      *      Vector3D b = st.getPlane().toSpace(st.getTransform().apply(subPt)); // transform in 2D space
    266      * </pre>
    267      * At the end of execution, the points {@code a} (which was transformed using the original
    268      * 3D transform) and {@code b} (which was transformed in 2D using the subspace transform)
    269      * are equivalent.
    270      *
    271      * @param transform the transform to apply to this instance
    272      * @return an object containing the transformed plane along with a transform that can be applied
    273      *      to subspace points
    274      * @see #transform(Transform)
    275      */
    276     public SubspaceTransform subspaceTransform(final Transform<Vector3D> transform) {
    277         final Vector3D origin = getOrigin();
    278
    279         final Vector3D tOrigin = transform.apply(origin);
    280         final Vector3D tPlusU = transform.apply(origin.add(u));
    281         final Vector3D tPlusV = transform.apply(origin.add(v));
    282
    283         final EmbeddingPlane tPlane = Planes.fromPointAndPlaneVectors(
    284                 tOrigin,
    285                 tOrigin.vectorTo(tPlusU),
    286                 tOrigin.vectorTo(tPlusV),
    287                 getPrecision());
    288
    289         final Vector2D tSubspaceOrigin = tPlane.toSubspace(tOrigin);
    290         final Vector2D tSubspaceU = tSubspaceOrigin.vectorTo(tPlane.toSubspace(tPlusU));
    291         final Vector2D tSubspaceV = tSubspaceOrigin.vectorTo(tPlane.toSubspace(tPlusV));
    292
    293         final AffineTransformMatrix2D subspaceTransform =
    294                 AffineTransformMatrix2D.fromColumnVectors(tSubspaceU, tSubspaceV, tSubspaceOrigin);
    295
    296         return new SubspaceTransform(tPlane, subspaceTransform);
    297     }
    298
    299     /** Class containing a transformed plane instance along with a subspace (2D) transform. The subspace
    300      * transform produces the equivalent of the 3D transform in 2D.
    301      */
    302     public static final class SubspaceTransform {
    303         /** The transformed plane. */
    304         private final EmbeddingPlane plane;
    305
    306         /** The subspace transform instance. */
    307         private final AffineTransformMatrix2D transform;
    308
    309         /** Simple constructor.
    310          * @param plane the transformed plane
    311          * @param transform 2D transform that can be applied to subspace points
    312          */
    313         public SubspaceTransform(final EmbeddingPlane plane, final AffineTransformMatrix2D transform) {
    314             this.plane = plane;
    315             this.transform = transform;
    316         }
    317
    318         /** Get the transformed plane instance.
    319          * @return the transformed plane instance
    320          */
    321         public EmbeddingPlane getPlane() {
    322             return plane;
    323         }
    324
    325         /** Get the 2D transform that can be applied to subspace points. This transform can be used
    326          * to perform the equivalent of the 3D transform in 2D space.
    327          * @return the subspace transform instance
    328          */
    329         public AffineTransformMatrix2D getTransform() {
    330             return transform;
    331         }
    332     }
    333 }