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.spherical.twod;
18
19 import org.apache.commons.geometry.core.Transform;
20 import org.apache.commons.geometry.euclidean.threed.AffineTransformMatrix3D;
21 import org.apache.commons.geometry.euclidean.threed.Vector3D;
22 import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
23
24 /** Implementation of the {@link Transform} interface for spherical 2D points.
25 *
26 * <p>This class uses an {@link AffineTransformMatrix3D} to perform spherical point transforms
27 * in Euclidean 3D space.</p>
28 *
29 * <p>Instances of this class are guaranteed to be immutable.</p>
30 */
31 public final class Transform2S implements Transform<Point2S> {
32 /** Static instance representing the identity transform. */
33 private static final Transform2S IDENTITY = new Transform2S(AffineTransformMatrix3D.identity());
34
35 /** Static transform instance that reflects across the x-y plane. */
36 private static final AffineTransformMatrix3D XY_PLANE_REFLECTION = AffineTransformMatrix3D.createScale(1, 1, -1);
37
38 /** Euclidean transform matrix underlying the spherical transform. */
39 private final AffineTransformMatrix3D euclideanTransform;
40
41 /** Construct a new instance from its underlying Euclidean transform.
42 * @param euclideanTransform underlying Euclidean transform
43 */
44 private Transform2S(final AffineTransformMatrix3D euclideanTransform) {
45 this.euclideanTransform = euclideanTransform;
46 }
47
48 /** Get the Euclidean transform matrix underlying the spherical transform.
49 * @return the Euclidean transform matrix underlying the spherical transform
50 */
51 public AffineTransformMatrix3D getEuclideanTransform() {
52 return euclideanTransform;
53 }
54
55 /** {@inheritDoc} */
56 @Override
57 public Point2S apply(final Point2S pt) {
58 final Vector3D vec = pt.getVector();
59 return Point2S.from(euclideanTransform.apply(vec));
60 }
61
62 /** {@inheritDoc} */
63 @Override
64 public boolean preservesOrientation() {
65 return euclideanTransform.preservesOrientation();
66 }
67
68 /** {@inheritDoc} */
69 @Override
70 public Transform2S inverse() {
71 return new Transform2S(euclideanTransform.inverse());
72 }
73
74 /** Apply a rotation of {@code angle} radians around the given point to this instance.
75 * @param pt point to rotate around
76 * @param angle rotation angle in radians
77 * @return transform resulting from applying the specified rotation to this instance
78 */
79 public Transform2S rotate(final Point2S pt, final double angle) {
80 return premultiply(createRotation(pt, angle));
81 }
82
83 /** Apply a rotation of {@code angle} radians around the given 3D axis to this instance.
84 * @param axis 3D axis of rotation
85 * @param angle rotation angle in radians
86 * @return transform resulting from applying the specified rotation to this instance
87 */
88 public Transform2S rotate(final Vector3D axis, final double angle) {
89 return premultiply(createRotation(axis, angle));
90 }
91
92 /** Apply the given quaternion rotation to this instance.
93 * @param quaternion quaternion rotation to apply
94 * @return transform resulting from applying the specified rotation to this instance
95 */
96 public Transform2S rotate(final QuaternionRotation quaternion) {
97 return premultiply(createRotation(quaternion));
98 }
99
100 /** Apply a reflection across the equatorial plane defined by the given pole point
101 * to this instance.
102 * @param pole pole point defining the equatorial reflection plane
103 * @return transform resulting from applying the specified reflection to this instance
104 */
105 public Transform2S reflect(final Point2S pole) {
106 return premultiply(createReflection(pole));
107 }
108
109 /** Apply a reflection across the equatorial plane defined by the given pole vector
110 * to this instance.
111 * @param poleVector pole vector defining the equatorial reflection plane
112 * @return transform resulting from applying the specified reflection to this instance
113 */
114 public Transform2S reflect(final Vector3D poleVector) {
115 return premultiply(createReflection(poleVector));
116 }
117
118 /** Multiply the underlying Euclidean transform of this instance by that of the argument, eg,
119 * {@code other * this}. The returned transform performs the equivalent of
120 * {@code other} followed by {@code this}.
121 * @param other transform to multiply with
122 * @return a new transform computed by multiplying the matrix of this
123 * instance by that of the argument
124 * @see AffineTransformMatrix3D#multiply(AffineTransformMatrix3D)
125 */
126 public Transform2S multiply(final Transform2S other) {
127 return multiply(this, other);
128 }
129
130 /** Multiply the underlying Euclidean transform matrix of the argument by that of this instance, eg,
131 * {@code this * other}. The returned transform performs the equivalent of {@code this}
132 * followed by {@code other}.
133 * @param other transform to multiply with
134 * @return a new transform computed by multiplying the matrix of the
135 * argument by that of this instance
136 * @see AffineTransformMatrix3D#premultiply(AffineTransformMatrix3D)
137 */
138 public Transform2S premultiply(final Transform2S other) {
139 return multiply(other, this);
140 }
141
142 /** {@inheritDoc} */
143 @Override
144 public int hashCode() {
145 return euclideanTransform.hashCode();
146 }
147
148 /**
149 * Return true if the given object is an instance of {@link Transform2S}
150 * and the underlying Euclidean transform matrices are exactly equal.
151 * @param obj object to test for equality with the current instance
152 * @return true if the underlying transform matrices are exactly equal
153 */
154 @Override
155 public boolean equals(final Object obj) {
156 if (this == obj) {
157 return true;
158 }
159 if (!(obj instanceof Transform2S)) {
160 return false;
161 }
162 final Transform2S other = (Transform2S) obj;
163
164 return euclideanTransform.equals(other.euclideanTransform);
165 }
166
167 /** {@inheritDoc} */
168 @Override
169 public String toString() {
170 final StringBuilder sb = new StringBuilder();
171
172 sb.append(this.getClass().getSimpleName())
173 .append("[euclideanTransform= ")
174 .append(getEuclideanTransform())
175 .append(']');
176
177 return sb.toString();
178 }
179
180 /** Return an instance representing the identity transform. This transform is guaranteed
181 * to return an <em>equivalent</em> (ie, co-located) point for any input point. However, the
182 * points are not guaranteed to contain exactly equal coordinates. For example, at the poles, an
183 * infinite number of points exist that vary only in the azimuth coordinate. When one of these
184 * points is transformed by this identity transform, the returned point may contain a different
185 * azimuth value from the input, but it will still represent the same location in space.
186 * @return an instance representing the identity transform
187 */
188 public static Transform2S identity() {
189 return IDENTITY;
190 }
191
192 /** Create a transform that rotates the given angle around {@code pt}.
193 * @param pt point to rotate around
194 * @param angle angle of rotation in radians
195 * @return a transform that rotates the given angle around {@code pt}
196 */
197 public static Transform2S createRotation(final Point2S pt, final double angle) {
198 return createRotation(pt.getVector(), angle);
199 }
200
201 /** Create a transform that rotates the given angle around {@code axis}.
202 * @param axis 3D axis of rotation
203 * @param angle angle of rotation in radians
204 * @return a transform that rotates the given angle {@code axis}
205 */
206 public static Transform2S createRotation(final Vector3D axis, final double angle) {
207 return createRotation(QuaternionRotation.fromAxisAngle(axis, angle));
208 }
209
210 /** Create a transform that performs the given 3D rotation.
211 * @param quaternion quaternion instance representing the 3D rotation
212 * @return a transform that performs the given 3D rotation
213 */
214 public static Transform2S createRotation(final QuaternionRotation quaternion) {
215 return new Transform2S(quaternion.toMatrix());
216 }
217
218 /** Create a transform that performs a reflection across the equatorial plane
219 * defined by the given pole point.
220 * @param pole pole point defining the equatorial reflection plane
221 * @return a transform that performs a reflection across the equatorial plane
222 * defined by the given pole point
223 */
224 public static Transform2S createReflection(final Point2S pole) {
225 return createReflection(pole.getVector());
226 }
227
228 /** Create a transform that performs a reflection across the equatorial plane
229 * defined by the given pole point.
230 * @param poleVector pole vector defining the equatorial reflection plane
231 * @return a transform that performs a reflection across the equatorial plane
232 * defined by the given pole point
233 */
234 public static Transform2S createReflection(final Vector3D poleVector) {
235 final QuaternionRotation quat = QuaternionRotation.createVectorRotation(poleVector, Vector3D.Unit.PLUS_Z);
236
237 final AffineTransformMatrix3D matrix = quat.toMatrix()
238 .premultiply(XY_PLANE_REFLECTION)
239 .premultiply(quat.inverse().toMatrix());
240
241 return new Transform2S(matrix);
242 }
243
244 /** Multiply the Euclidean transform matrices of the arguments together.
245 * @param a first transform
246 * @param b second transform
247 * @return the transform computed as {@code a x b}
248 */
249 private static Transform2S multiply(final Transform2S a, final Transform2S b) {
250
251 return new Transform2S(a.euclideanTransform.multiply(b.euclideanTransform));
252 }
253 }