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.twod.rotation;
18  
19  import org.apache.commons.geometry.euclidean.EuclideanTransform;
20  import org.apache.commons.geometry.euclidean.internal.Vectors;
21  import org.apache.commons.geometry.euclidean.twod.AffineTransformMatrix2D;
22  import org.apache.commons.geometry.euclidean.twod.Vector2D;
23  
24  /** Class representing a rotation in 2 dimensional Euclidean space. Positive
25   * rotations are in a <em>counter-clockwise</em> direction.
26   */
27  public final class Rotation2D implements EuclideanTransform<Vector2D> {
28  
29      /** Instance representing a rotation of zero radians. */
30      private static final Rotation2D IDENTITY = new Rotation2D(0);
31  
32      /** The angle of the rotation in radians. */
33      private final double angle;
34  
35      /** The cosine of the angle of rotation, cached to avoid repeated computation. */
36      private final double cosAngle;
37  
38      /** The sine of the angle of rotation, cached to avoid repeated computation. */
39      private final double sinAngle;
40  
41      /** Create a new instance representing the given angle.
42       * @param angle the angle of rotation, in radians
43       */
44      private Rotation2D(final double angle) {
45          this.angle = angle;
46          this.cosAngle = Math.cos(angle);
47          this.sinAngle = Math.sin(angle);
48      }
49  
50      /** Get the angle of rotation in radians.
51       * @return the angle of rotation in radians
52       */
53      public double getAngle() {
54          return angle;
55      }
56  
57      /** {@inheritDoc} */
58      @Override
59      public Rotation2D inverse() {
60          return new Rotation2D(-angle);
61      }
62  
63      /** {@inheritDoc}
64       *
65       * <p>This method simply returns true since rotations always preserve the orientation
66       * of the space.</p>
67       */
68      @Override
69      public boolean preservesOrientation() {
70          return true;
71      }
72  
73      /** {@inheritDoc} */
74      @Override
75      public Vector2D apply(final Vector2D pt) {
76          final double x = pt.getX();
77          final double y = pt.getY();
78  
79          return Vector2D.of(
80                      (x * cosAngle) - (y * sinAngle),
81                      (x * sinAngle) + (y * cosAngle)
82                  );
83      }
84  
85      /** {@inheritDoc}
86       *
87       * <p>This method simply calls {@code apply(vec)} since rotations treat
88       * points and vectors similarly.</p>
89       * */
90      @Override
91      public Vector2D applyVector(final Vector2D vec) {
92          return apply(vec);
93      }
94  
95      /** Return an {@link AffineTransformMatrix2D} representing the same rotation
96       * as this instance.
97       * @return a transform matrix representing the same rotation
98       */
99      public AffineTransformMatrix2D toMatrix() {
100         return AffineTransformMatrix2D.of(
101                     cosAngle, -sinAngle, 0.0,
102                     sinAngle, cosAngle, 0.0
103                 );
104     }
105 
106     /** {@inheritDoc} */
107     @Override
108     public int hashCode() {
109         return Double.hashCode(angle);
110     }
111 
112     /** {@inheritDoc} */
113     @Override
114     public boolean equals(final Object obj) {
115         if (this == obj) {
116             return true;
117         }
118         if (!(obj instanceof Rotation2D)) {
119             return false;
120         }
121 
122         final Rotation2D other = (Rotation2D) obj;
123 
124         return Double.compare(this.angle, other.angle) == 0;
125     }
126 
127     /** {@inheritDoc} */
128     @Override
129     public String toString() {
130         final StringBuilder sb = new StringBuilder();
131         sb.append(this.getClass().getSimpleName())
132             .append("[angle=")
133             .append(angle)
134             .append(']');
135 
136         return sb.toString();
137     }
138 
139     /** Create a new instance with the given angle of rotation.
140      * @param angle the angle of rotation in radians
141      * @return a new instance with the given angle of rotation
142      */
143     public static Rotation2D of(final double angle) {
144         return new Rotation2D(angle);
145     }
146 
147     /** Return an instance representing the identity rotation, ie a rotation
148      * of zero radians.
149      * @return an instance representing a rotation of zero radians
150      */
151     public static Rotation2D identity() {
152         return IDENTITY;
153     }
154 
155     /** Create a rotation instance that rotates the vector {@code u} to point in the direction of
156      * vector {@code v}.
157      * @param u input vector
158      * @param v target vector
159      * @return a rotation instance that rotates {@code u} to point in the direction of {@code v}
160      * @throws IllegalArgumentException if either vector cannot be normalized
161      */
162     public static Rotation2D createVectorRotation(final Vector2D u, final Vector2D v) {
163         // make sure that the vectors are real-valued and of non-zero length; we don't
164         // actually need to use the norm value; we just need to check its properties
165         Vectors.checkedNorm(u);
166         Vectors.checkedNorm(v);
167 
168         final double uAzimuth = Math.atan2(u.getY(), u.getX());
169         final double vAzimuth = Math.atan2(v.getY(), v.getX());
170 
171         return of(vAzimuth - uAzimuth);
172     }
173 }