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 }