001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.geometry.spherical.twod;
018
019import org.apache.commons.geometry.core.Transform;
020import org.apache.commons.geometry.euclidean.threed.AffineTransformMatrix3D;
021import org.apache.commons.geometry.euclidean.threed.Vector3D;
022import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
023
024/** Implementation of the {@link Transform} interface for spherical 2D points.
025 *
026 * <p>This class uses an {@link AffineTransformMatrix3D} to perform spherical point transforms
027 * in Euclidean 3D space.</p>
028 *
029 * <p>Instances of this class are guaranteed to be immutable.</p>
030 */
031public final class Transform2S implements Transform<Point2S> {
032    /** Static instance representing the identity transform. */
033    private static final Transform2S IDENTITY = new Transform2S(AffineTransformMatrix3D.identity());
034
035    /** Static transform instance that reflects across the x-y plane. */
036    private static final AffineTransformMatrix3D XY_PLANE_REFLECTION = AffineTransformMatrix3D.createScale(1, 1, -1);
037
038    /** Euclidean transform matrix underlying the spherical transform. */
039    private final AffineTransformMatrix3D euclideanTransform;
040
041    /** Construct a new instance from its underlying Euclidean transform.
042     * @param euclideanTransform underlying Euclidean transform
043     */
044    private Transform2S(final AffineTransformMatrix3D euclideanTransform) {
045        this.euclideanTransform = euclideanTransform;
046    }
047
048    /** Get the Euclidean transform matrix underlying the spherical transform.
049     * @return the Euclidean transform matrix underlying the spherical transform
050     */
051    public AffineTransformMatrix3D getEuclideanTransform() {
052        return euclideanTransform;
053    }
054
055    /** {@inheritDoc} */
056    @Override
057    public Point2S apply(final Point2S pt) {
058        final Vector3D vec = pt.getVector();
059        return Point2S.from(euclideanTransform.apply(vec));
060    }
061
062    /** {@inheritDoc} */
063    @Override
064    public boolean preservesOrientation() {
065        return euclideanTransform.preservesOrientation();
066    }
067
068    /** {@inheritDoc} */
069    @Override
070    public Transform2S inverse() {
071        return new Transform2S(euclideanTransform.inverse());
072    }
073
074    /** Apply a rotation of {@code angle} radians around the given point to this instance.
075     * @param pt point to rotate around
076     * @param angle rotation angle in radians
077     * @return transform resulting from applying the specified rotation to this instance
078     */
079    public Transform2S rotate(final Point2S pt, final double angle) {
080        return premultiply(createRotation(pt, angle));
081    }
082
083    /** Apply a rotation of {@code angle} radians around the given 3D axis to this instance.
084     * @param axis 3D axis of rotation
085     * @param angle rotation angle in radians
086     * @return transform resulting from applying the specified rotation to this instance
087     */
088    public Transform2S rotate(final Vector3D axis, final double angle) {
089        return premultiply(createRotation(axis, angle));
090    }
091
092    /** Apply the given quaternion rotation to this instance.
093     * @param quaternion quaternion rotation to apply
094     * @return transform resulting from applying the specified rotation to this instance
095     */
096    public Transform2S rotate(final QuaternionRotation quaternion) {
097        return premultiply(createRotation(quaternion));
098    }
099
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}