Rotation2D.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.twod.rotation;

  18. import org.apache.commons.geometry.euclidean.EuclideanTransform;
  19. import org.apache.commons.geometry.euclidean.internal.Vectors;
  20. import org.apache.commons.geometry.euclidean.twod.AffineTransformMatrix2D;
  21. import org.apache.commons.geometry.euclidean.twod.Vector2D;

  22. /** Class representing a rotation in 2 dimensional Euclidean space. Positive
  23.  * rotations are in a <em>counter-clockwise</em> direction.
  24.  */
  25. public final class Rotation2D implements EuclideanTransform<Vector2D> {

  26.     /** Instance representing a rotation of zero radians. */
  27.     private static final Rotation2D IDENTITY = new Rotation2D(0);

  28.     /** The angle of the rotation in radians. */
  29.     private final double angle;

  30.     /** The cosine of the angle of rotation, cached to avoid repeated computation. */
  31.     private final double cosAngle;

  32.     /** The sine of the angle of rotation, cached to avoid repeated computation. */
  33.     private final double sinAngle;

  34.     /** Create a new instance representing the given angle.
  35.      * @param angle the angle of rotation, in radians
  36.      */
  37.     private Rotation2D(final double angle) {
  38.         this.angle = angle;
  39.         this.cosAngle = Math.cos(angle);
  40.         this.sinAngle = Math.sin(angle);
  41.     }

  42.     /** Get the angle of rotation in radians.
  43.      * @return the angle of rotation in radians
  44.      */
  45.     public double getAngle() {
  46.         return angle;
  47.     }

  48.     /** {@inheritDoc} */
  49.     @Override
  50.     public Rotation2D inverse() {
  51.         return new Rotation2D(-angle);
  52.     }

  53.     /** {@inheritDoc}
  54.      *
  55.      * <p>This method simply returns true since rotations always preserve the orientation
  56.      * of the space.</p>
  57.      */
  58.     @Override
  59.     public boolean preservesOrientation() {
  60.         return true;
  61.     }

  62.     /** {@inheritDoc} */
  63.     @Override
  64.     public Vector2D apply(final Vector2D pt) {
  65.         final double x = pt.getX();
  66.         final double y = pt.getY();

  67.         return Vector2D.of(
  68.                     (x * cosAngle) - (y * sinAngle),
  69.                     (x * sinAngle) + (y * cosAngle)
  70.                 );
  71.     }

  72.     /** {@inheritDoc}
  73.      *
  74.      * <p>This method simply calls {@code apply(vec)} since rotations treat
  75.      * points and vectors similarly.</p>
  76.      * */
  77.     @Override
  78.     public Vector2D applyVector(final Vector2D vec) {
  79.         return apply(vec);
  80.     }

  81.     /** Return an {@link AffineTransformMatrix2D} representing the same rotation
  82.      * as this instance.
  83.      * @return a transform matrix representing the same rotation
  84.      */
  85.     public AffineTransformMatrix2D toMatrix() {
  86.         return AffineTransformMatrix2D.of(
  87.                     cosAngle, -sinAngle, 0.0,
  88.                     sinAngle, cosAngle, 0.0
  89.                 );
  90.     }

  91.     /** {@inheritDoc} */
  92.     @Override
  93.     public int hashCode() {
  94.         return Double.hashCode(angle);
  95.     }

  96.     /** {@inheritDoc} */
  97.     @Override
  98.     public boolean equals(final Object obj) {
  99.         if (this == obj) {
  100.             return true;
  101.         }
  102.         if (!(obj instanceof Rotation2D)) {
  103.             return false;
  104.         }

  105.         final Rotation2D other = (Rotation2D) obj;

  106.         return Double.compare(this.angle, other.angle) == 0;
  107.     }

  108.     /** {@inheritDoc} */
  109.     @Override
  110.     public String toString() {
  111.         final StringBuilder sb = new StringBuilder();
  112.         sb.append(this.getClass().getSimpleName())
  113.             .append("[angle=")
  114.             .append(angle)
  115.             .append(']');

  116.         return sb.toString();
  117.     }

  118.     /** Create a new instance with the given angle of rotation.
  119.      * @param angle the angle of rotation in radians
  120.      * @return a new instance with the given angle of rotation
  121.      */
  122.     public static Rotation2D of(final double angle) {
  123.         return new Rotation2D(angle);
  124.     }

  125.     /** Return an instance representing the identity rotation, ie a rotation
  126.      * of zero radians.
  127.      * @return an instance representing a rotation of zero radians
  128.      */
  129.     public static Rotation2D identity() {
  130.         return IDENTITY;
  131.     }

  132.     /** Create a rotation instance that rotates the vector {@code u} to point in the direction of
  133.      * vector {@code v}.
  134.      * @param u input vector
  135.      * @param v target vector
  136.      * @return a rotation instance that rotates {@code u} to point in the direction of {@code v}
  137.      * @throws IllegalArgumentException if either vector cannot be normalized
  138.      */
  139.     public static Rotation2D createVectorRotation(final Vector2D u, final Vector2D v) {
  140.         // make sure that the vectors are real-valued and of non-zero length; we don't
  141.         // actually need to use the norm value; we just need to check its properties
  142.         Vectors.checkedNorm(u);
  143.         Vectors.checkedNorm(v);

  144.         final double uAzimuth = Math.atan2(u.getY(), u.getX());
  145.         final double vAzimuth = Math.atan2(v.getY(), v.getX());

  146.         return of(vAzimuth - uAzimuth);
  147.     }
  148. }