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;
18  
19  import java.util.function.UnaryOperator;
20  
21  import org.apache.commons.geometry.core.internal.DoubleFunction2N;
22  import org.apache.commons.geometry.euclidean.AbstractAffineTransformMatrix;
23  import org.apache.commons.geometry.euclidean.internal.Matrices;
24  import org.apache.commons.geometry.euclidean.internal.Vectors;
25  import org.apache.commons.geometry.euclidean.twod.rotation.Rotation2D;
26  import org.apache.commons.numbers.arrays.LinearCombination;
27  
28  /** Class using a matrix to represent affine transformations in 2 dimensional Euclidean space.
29  *
30  * <p>Instances of this class use a 3x3 matrix for all transform operations.
31  * The last row of this matrix is always set to the values <code>[0 0 1]</code> and so
32  * is not stored. Hence, the methods in this class that accept or return arrays always
33  * use arrays containing 6 elements, instead of 9.
34  * </p>
35  */
36  public final class AffineTransformMatrix2D extends AbstractAffineTransformMatrix<Vector2D, AffineTransformMatrix2D> {
37      /** The number of internal matrix elements. */
38      private static final int NUM_ELEMENTS = 6;
39  
40      /** String used to start the transform matrix string representation. */
41      private static final String MATRIX_START = "[ ";
42  
43      /** String used to end the transform matrix string representation. */
44      private static final String MATRIX_END = " ]";
45  
46      /** String used to separate elements in the matrix string representation. */
47      private static final String ELEMENT_SEPARATOR = ", ";
48  
49      /** String used to separate rows in the matrix string representation. */
50      private static final String ROW_SEPARATOR = "; ";
51  
52      /** Shared transform set to the identity matrix. */
53      private static final AffineTransformMatrix2D IDENTITY_INSTANCE = new AffineTransformMatrix2D(
54                  1, 0, 0,
55                  0, 1, 0
56              );
57  
58      /** Transform matrix entry <code>m<sub>0,0</sub></code>. */
59      private final double m00;
60      /** Transform matrix entry <code>m<sub>0,1</sub></code>. */
61      private final double m01;
62      /** Transform matrix entry <code>m<sub>0,2</sub></code>. */
63      private final double m02;
64  
65      /** Transform matrix entry <code>m<sub>1,0</sub></code>. */
66      private final double m10;
67      /** Transform matrix entry <code>m<sub>1,1</sub></code>. */
68      private final double m11;
69      /** Transform matrix entry <code>m<sub>1,2</sub></code>. */
70      private final double m12;
71  
72      /**
73       * Simple constructor; sets all internal matrix elements.
74       * @param m00 matrix entry <code>m<sub>0,0</sub></code>
75       * @param m01 matrix entry <code>m<sub>0,1</sub></code>
76       * @param m02 matrix entry <code>m<sub>0,2</sub></code>
77       * @param m10 matrix entry <code>m<sub>1,0</sub></code>
78       * @param m11 matrix entry <code>m<sub>1,1</sub></code>
79       * @param m12 matrix entry <code>m<sub>1,2</sub></code>
80       */
81      private AffineTransformMatrix2D(
82              final double m00, final double m01, final double m02,
83              final double m10, final double m11, final double m12) {
84  
85          this.m00 = m00;
86          this.m01 = m01;
87          this.m02 = m02;
88  
89          this.m10 = m10;
90          this.m11 = m11;
91          this.m12 = m12;
92      }
93  
94      /** Return a 6 element array containing the variable elements from the
95       * internal transformation matrix. The elements are in row-major order.
96       * The array indices map to the internal matrix as follows:
97       * <pre>
98       *      [
99       *          arr[0],   arr[1],   arr[2],
100      *          arr[3],   arr[4],   arr[5],
101      *          0         0         1
102      *      ]
103      * </pre>
104      * @return 6 element array containing the variable elements from the
105      *      internal transformation matrix
106      */
107     public double[] toArray() {
108         return new double[] {
109             m00, m01, m02,
110             m10, m11, m12
111         };
112     }
113 
114     /** Apply this transform to the given point, returning the result as a new instance.
115     *
116     * <p>The transformed point is computed by creating a 3-element column vector from the
117     * coordinates in the input and setting the last element to 1. This is then multiplied with the
118     * 3x3 transform matrix to produce the transformed point. The {@code 1} in the last position
119     * is ignored.
120     * <pre>
121     *      [ m00  m01  m02 ]     [ x ]     [ x']
122     *      [ m10  m11  m12 ]  *  [ y ]  =  [ y']
123     *      [ 0    0    1   ]     [ 1 ]     [ 1 ]
124     * </pre>
125     */
126     @Override
127     public Vector2D apply(final Vector2D pt) {
128         final double x = pt.getX();
129         final double y = pt.getY();
130 
131         final double resultX = LinearCombination.value(m00, x, m01, y) + m02;
132         final double resultY = LinearCombination.value(m10, x, m11, y) + m12;
133 
134         return Vector2D.of(resultX, resultY);
135     }
136 
137     /** {@inheritDoc}
138     *
139     *  <p>The transformed vector is computed by creating a 3-element column vector from the
140     * coordinates in the input and setting the last element to 0. This is then multiplied with the
141     * 3x3 transform matrix to produce the transformed vector. The {@code 0} in the last position
142     * is ignored.
143     * <pre>
144     *      [ m00  m01  m02 ]     [ x ]     [ x']
145     *      [ m10  m11  m12 ]  *  [ y ]  =  [ y']
146     *      [ 0    0    1   ]     [ 0 ]     [ 0 ]
147     * </pre>
148     *
149     * @see #applyDirection(Vector2D)
150     */
151     @Override
152     public Vector2D applyVector(final Vector2D vec) {
153         return applyVector(vec, Vector2D::of);
154     }
155 
156     /** {@inheritDoc}
157      * @see #applyVector(Vector2D)
158      */
159     @Override
160     public Vector2D.Unit applyDirection(final Vector2D vec) {
161         return applyVector(vec, Vector2D.Unit::from);
162     }
163 
164     /** {@inheritDoc} */
165     @Override
166     public double determinant() {
167         return Matrices.determinant(
168                 m00, m01,
169                 m10, m11
170             );
171     }
172 
173     /** {@inheritDoc}
174      *
175      * <p><strong>Example</strong>
176      * <pre>
177      *      [ a, b, c ]   [ a, b, 0 ]
178      *      [ d, e, f ] &rarr; [ d, e, 0 ]
179      *      [ 0, 0, 1 ]   [ 0, 0, 1 ]
180      * </pre>
181      */
182     @Override
183     public AffineTransformMatrix2D linear() {
184         return new AffineTransformMatrix2D(
185                 m00, m01, 0.0,
186                 m10, m11, 0.0);
187     }
188 
189     /** {@inheritDoc}
190      *
191      * <p><strong>Example</strong>
192      * <pre>
193      *      [ a, b, c ]   [ a, d, 0 ]
194      *      [ d, e, f ] &rarr; [ b, e, 0 ]
195      *      [ 0, 0, 1 ]   [ 0, 0, 1 ]
196      * </pre>
197      */
198     @Override
199     public AffineTransformMatrix2D linearTranspose() {
200         return new AffineTransformMatrix2D(
201                 m00, m10, 0.0,
202                 m01, m11, 0.0);
203     }
204 
205     /** Apply a translation to the current instance, returning the result as a new transform.
206      * @param translation vector containing the translation values for each axis
207      * @return a new transform containing the result of applying a translation to
208      *      the current instance
209      */
210     public AffineTransformMatrix2D translate(final Vector2D translation) {
211         return translate(translation.getX(), translation.getY());
212     }
213 
214     /** Apply a translation to the current instance, returning the result as a new transform.
215      * @param x translation in the x direction
216      * @param y translation in the y direction
217      * @return a new transform containing the result of applying a translation to
218      *      the current instance
219      */
220     public AffineTransformMatrix2D translate(final double x, final double y) {
221         return new AffineTransformMatrix2D(
222                     m00, m01, m02 + x,
223                     m10, m11, m12 + y
224                 );
225     }
226 
227     /** Apply a scale operation to the current instance, returning the result as a new transform.
228      * @param factor the scale factor to apply to all axes
229      * @return a new transform containing the result of applying a scale operation to
230      *      the current instance
231      */
232     public AffineTransformMatrix2D scale(final double factor) {
233         return scale(factor, factor);
234     }
235 
236     /** Apply a scale operation to the current instance, returning the result as a new transform.
237      * @param scaleFactors vector containing scale factors for each axis
238      * @return a new transform containing the result of applying a scale operation to
239      *      the current instance
240      */
241     public AffineTransformMatrix2D scale(final Vector2D scaleFactors) {
242         return scale(scaleFactors.getX(), scaleFactors.getY());
243     }
244 
245     /** Apply a scale operation to the current instance, returning the result as a new transform.
246      * @param x scale factor for the x axis
247      * @param y scale factor for the y axis
248      * @return a new transform containing the result of applying a scale operation to
249      *      the current instance
250      */
251     public AffineTransformMatrix2D scale(final double x, final double y) {
252         return new AffineTransformMatrix2D(
253                 m00 * x, m01 * x, m02 * x,
254                 m10 * y, m11 * y, m12 * y
255             );
256     }
257 
258     /** Apply a <em>counterclockwise</em> rotation to the current instance, returning the result as a
259      * new transform.
260      * @param angle the angle of counterclockwise rotation in radians
261      * @return a new transform containing the result of applying a rotation to the
262      *      current instance
263      * @see Rotation2D#of(double)
264      */
265     public AffineTransformMatrix2D rotate(final double angle) {
266         return rotate(Rotation2D.of(angle));
267     }
268 
269     /** Apply a <em>counterclockwise</em> rotation to the current instance, returning the result as a
270      *  new transform.
271      * @param rotation the rotation to apply
272      * @return a new transform containing the result of applying the rotation to the
273      *      current instance
274      */
275     public AffineTransformMatrix2D rotate(final Rotation2D rotation) {
276         return multiply(rotation.toMatrix(), this);
277     }
278 
279     /** Apply a <em>counterclockwise</em> rotation about the given center point to the current instance,
280      * returning the result as a new transform. This is accomplished by translating the center to the origin,
281      * applying the rotation, and then translating back.
282      * @param center the center of rotation
283      * @param angle the angle of counterclockwise rotation in radians
284      * @return a new transform containing the result of applying a rotation about the given
285      *      center point to the current instance
286      */
287     public AffineTransformMatrix2D rotate(final Vector2D center, final double angle) {
288         return multiply(createRotation(center, angle), this);
289     }
290 
291     /** Apply a <em>counterclockwise</em> rotation about the given center point to the current instance,
292      * returning the result as a new transform. This is accomplished by translating the center to the origin,
293      * applying the rotation, and then translating back.
294      * @param center the center of rotation
295      * @param rotation the rotation to apply
296      * @return a new transform containing the result of applying a rotation about the given
297      *      center point to the current instance
298      */
299     public AffineTransformMatrix2D rotate(final Vector2D center, final Rotation2D rotation) {
300         // use to raw angle method to avoid matrix multiplication
301         return rotate(center, rotation.getAngle());
302     }
303 
304     /** Get a new transform created by multiplying this instance by the argument.
305      * This is equivalent to the expression {@code A * M} where {@code A} is the
306      * current transform matrix and {@code M} is the given transform matrix. In
307      * terms of transformations, applying the returned matrix is equivalent to
308      * applying {@code M} and <em>then</em> applying {@code A}. In other words,
309      * the rightmost transform is applied first.
310      *
311      * @param m the transform to multiply with
312      * @return the result of multiplying the current instance by the given
313      *      transform matrix
314      */
315     public AffineTransformMatrix2D multiply(final AffineTransformMatrix2D m) {
316         return multiply(this, m);
317     }
318 
319     /** Get a new transform created by multiplying the argument by this instance.
320      * This is equivalent to the expression {@code M * A} where {@code A} is the
321      * current transform matrix and {@code M} is the given transform matrix. In
322      * terms of transformations, applying the returned matrix is equivalent to
323      * applying {@code A} and <em>then</em> applying {@code M}. In other words,
324      * the rightmost transform is applied first.
325      *
326      * @param m the transform to multiply with
327      * @return the result of multiplying the given transform matrix by the current
328      *      instance
329      */
330     public AffineTransformMatrix2D premultiply(final AffineTransformMatrix2D m) {
331         return multiply(m, this);
332     }
333 
334     /** {@inheritDoc}
335     *
336     * @throws IllegalStateException if the matrix cannot be inverted
337     */
338     @Override
339     public AffineTransformMatrix2D inverse() {
340 
341         // Our full matrix is 3x3 but we can significantly reduce the amount of computations
342         // needed here since we know that our last row is [0 0 1].
343 
344         final double det = Matrices.checkDeterminantForInverse(determinant());
345 
346         // validate the remaining matrix elements that were not part of the determinant
347         Matrices.checkElementForInverse(m02);
348         Matrices.checkElementForInverse(m12);
349 
350         // compute the necessary elements of the cofactor matrix
351         // (we need all but the last column)
352 
353         final double invDet = 1.0 / det;
354 
355         final double c00 = invDet * m11;
356         final double c01 = -invDet * m10;
357 
358         final double c10 = -invDet * m01;
359         final double c11 = invDet * m00;
360 
361         final double c20 = invDet * Matrices.determinant(m01, m02, m11, m12);
362         final double c21 = -invDet * Matrices.determinant(m00, m02, m10, m12);
363 
364         return new AffineTransformMatrix2D(
365                     c00, c10, c20,
366                     c01, c11, c21
367                 );
368     }
369 
370     /** {@inheritDoc} */
371     @Override
372     public int hashCode() {
373         final int prime = 31;
374         int result = 1;
375 
376         result = (result * prime) + (Double.hashCode(m00) - Double.hashCode(m01) + Double.hashCode(m02));
377         result = (result * prime) + (Double.hashCode(m10) - Double.hashCode(m11) + Double.hashCode(m12));
378 
379         return result;
380     }
381 
382     /**
383      * Return true if the given object is an instance of {@link AffineTransformMatrix2D}
384      * and all matrix element values are exactly equal.
385      * @param obj object to test for equality with the current instance
386      * @return true if all transform matrix elements are exactly equal; otherwise false
387      */
388     @Override
389     public boolean equals(final Object obj) {
390         if (this == obj) {
391             return true;
392         }
393         if (!(obj instanceof AffineTransformMatrix2D)) {
394             return false;
395         }
396 
397         final AffineTransformMatrix2D other = (AffineTransformMatrix2D) obj;
398 
399         return Double.compare(this.m00, other.m00) == 0 &&
400                 Double.compare(this.m01, other.m01) == 0 &&
401                 Double.compare(this.m02, other.m02) == 0 &&
402 
403                 Double.compare(this.m10, other.m10) == 0 &&
404                 Double.compare(this.m11, other.m11) == 0 &&
405                 Double.compare(this.m12, other.m12) == 0;
406     }
407 
408     /** {@inheritDoc} */
409     @Override
410     public String toString() {
411         final StringBuilder sb = new StringBuilder();
412 
413         sb.append(MATRIX_START)
414 
415             .append(m00)
416             .append(ELEMENT_SEPARATOR)
417             .append(m01)
418             .append(ELEMENT_SEPARATOR)
419             .append(m02)
420             .append(ROW_SEPARATOR)
421 
422             .append(m10)
423             .append(ELEMENT_SEPARATOR)
424             .append(m11)
425             .append(ELEMENT_SEPARATOR)
426             .append(m12)
427 
428             .append(MATRIX_END);
429 
430         return sb.toString();
431     }
432 
433     /** Multiplies the given vector by the 2x2 linear transformation matrix contained in the
434      * upper-right corner of the affine transformation matrix. This applies all transformation
435      * operations except for translations. The computed coordinates are passed to the given
436      * factory function.
437      * @param <T> factory output type
438      * @param vec the vector to transform
439      * @param factory the factory instance that will be passed the transformed coordinates
440      * @return the factory return value
441      */
442     private <T> T applyVector(final Vector2D vec, final DoubleFunction2N<T> factory) {
443         final double x = vec.getX();
444         final double y = vec.getY();
445 
446         final double resultX = LinearCombination.value(m00, x, m01, y);
447         final double resultY = LinearCombination.value(m10, x, m11, y);
448 
449         return factory.apply(resultX, resultY);
450     }
451 
452     /** Get a new transform with the given matrix elements. The array must contain 6 elements.
453      * @param arr 6-element array containing values for the variable entries in the
454      *      transform matrix
455      * @return a new transform initialized with the given matrix values
456      * @throws IllegalArgumentException if the array does not have 6 elements
457      */
458     public static AffineTransformMatrix2D of(final double... arr) {
459         if (arr.length != NUM_ELEMENTS) {
460             throw new IllegalArgumentException("Dimension mismatch: " + arr.length + " != " + NUM_ELEMENTS);
461         }
462 
463         return new AffineTransformMatrix2D(
464                     arr[0], arr[1], arr[2],
465                     arr[3], arr[4], arr[5]
466                 );
467     }
468 
469     /** Construct a new transform representing the given function. The function is sampled at
470      * the origin and along each axis and a matrix is created to perform the transformation.
471      * @param fn function to create a transform matrix from
472      * @return a transform matrix representing the given function
473      * @throws IllegalArgumentException if the given function does not represent a valid
474      *      affine transform
475      */
476     public static AffineTransformMatrix2D from(final UnaryOperator<Vector2D> fn) {
477         final Vector2D tPlusX = fn.apply(Vector2D.Unit.PLUS_X);
478         final Vector2D tPlusY = fn.apply(Vector2D.Unit.PLUS_Y);
479         final Vector2D tZero = fn.apply(Vector2D.ZERO);
480 
481         final Vector2D u = tPlusX.subtract(tZero);
482         final Vector2D v = tPlusY.subtract(tZero);
483 
484         final AffineTransformMatrix2D mat =  AffineTransformMatrix2D.fromColumnVectors(u, v, tZero);
485 
486         final double det = mat.determinant();
487         if (!Vectors.isRealNonZero(det)) {
488             throw new IllegalArgumentException("Transform function is invalid: matrix determinant is " + det);
489         }
490 
491         return mat;
492     }
493 
494     /** Get a new transform create from the given column vectors. The returned transform
495      * does not include any translation component.
496      * @param u first column vector; this corresponds to the first basis vector
497      *      in the coordinate frame
498      * @param v second column vector; this corresponds to the second basis vector
499      *      in the coordinate frame
500      * @return a new transform with the given column vectors
501      */
502     public static AffineTransformMatrix2D fromColumnVectors(final Vector2D u, final Vector2D v) {
503         return fromColumnVectors(u, v, Vector2D.ZERO);
504     }
505 
506     /** Get a new transform created from the given column vectors.
507      * @param u first column vector; this corresponds to the first basis vector
508      *      in the coordinate frame
509      * @param v second column vector; this corresponds to the second basis vector
510      *      in the coordinate frame
511      * @param t third column vector; this corresponds to the translation of the transform
512      * @return a new transform with the given column vectors
513      */
514     public static AffineTransformMatrix2D fromColumnVectors(final Vector2D u, final Vector2D v, final Vector2D t) {
515         return new AffineTransformMatrix2D(
516                     u.getX(), v.getX(), t.getX(),
517                     u.getY(), v.getY(), t.getY()
518                 );
519     }
520 
521     /** Get the transform representing the identity matrix. This transform does not
522      * modify point or vector values when applied.
523      * @return transform representing the identity matrix
524      */
525     public static AffineTransformMatrix2D identity() {
526         return IDENTITY_INSTANCE;
527     }
528 
529     /** Create a transform representing the given translation.
530      * @param translation vector containing translation values for each axis
531      * @return a new transform representing the given translation
532      */
533     public static AffineTransformMatrix2D createTranslation(final Vector2D translation) {
534         return createTranslation(translation.getX(), translation.getY());
535     }
536 
537     /** Create a transform representing the given translation.
538      * @param x translation in the x direction
539      * @param y translation in the y direction
540      * @return a new transform representing the given translation
541      */
542     public static AffineTransformMatrix2D createTranslation(final double x, final double y) {
543         return new AffineTransformMatrix2D(
544                     1, 0, x,
545                     0, 1, y
546                 );
547     }
548 
549     /** Create a transform representing a scale operation with the given scale factor applied to all axes.
550      * @param factor scale factor to apply to all axes
551      * @return a new transform representing a uniform scaling in all axes
552      */
553     public static AffineTransformMatrix2D createScale(final double factor) {
554         return createScale(factor, factor);
555     }
556 
557     /** Create a transform representing a scale operation.
558      * @param factors vector containing scale factors for each axis
559      * @return a new transform representing a scale operation
560      */
561     public static AffineTransformMatrix2D createScale(final Vector2D factors) {
562         return createScale(factors.getX(), factors.getY());
563     }
564 
565     /** Create a transform representing a scale operation.
566      * @param x scale factor for the x axis
567      * @param y scale factor for the y axis
568      * @return a new transform representing a scale operation
569      */
570     public static AffineTransformMatrix2D createScale(final double x, final double y) {
571         return new AffineTransformMatrix2D(
572                     x, 0, 0,
573                     0, y, 0
574                 );
575     }
576 
577     /** Create a transform representing a <em>counterclockwise</em> rotation of {@code angle}
578      * radians around the origin.
579      * @param angle the angle of rotation in radians
580      * @return a new transform representing the rotation
581      * @see Rotation2D#toMatrix()
582      */
583     public static AffineTransformMatrix2D createRotation(final double angle) {
584         return Rotation2D.of(angle).toMatrix();
585     }
586 
587     /** Create a transform representing a <em>counterclockwise</em> rotation of {@code angle}
588      * radians around the given center point. This is accomplished by translating the center point
589      * to the origin, applying the rotation, and then translating back.
590      * @param center the center of rotation
591      * @param angle the angle of rotation in radians
592      * @return a new transform representing the rotation about the given center
593      */
594     public static AffineTransformMatrix2D createRotation(final Vector2D center, final double angle) {
595         // it's possible to do this using Rotation2D to create the rotation matrix but we
596         // can avoid the matrix multiplications by simply doing everything in-line here
597         final double x = center.getX();
598         final double y = center.getY();
599 
600         final double sin = Math.sin(angle);
601         final double cos = Math.cos(angle);
602 
603         return new AffineTransformMatrix2D(
604                 cos, -sin, (-x * cos) + (y * sin) + x,
605                 sin, cos, (-x * sin) - (y * cos) + y
606             );
607     }
608 
609     /** Create a transform representing a <em>counterclockwise</em> rotation around the given center point.
610      * This is accomplished by translating the center point to the origin, applying the rotation, and then
611      * translating back.
612      * @param center the center of rotation
613      * @param rotation the rotation to apply
614      * @return a new transform representing the rotation about the given center
615      */
616     public static AffineTransformMatrix2D createRotation(final Vector2D center, final Rotation2D rotation) {
617         return createRotation(center, rotation.getAngle());
618     }
619 
620     /** Multiply two transform matrices together.
621      * @param a first transform
622      * @param b second transform
623      * @return the transform computed as {@code a x b}
624      */
625     private static AffineTransformMatrix2D multiply(final AffineTransformMatrix2D a,
626             final AffineTransformMatrix2D b) {
627 
628         final double c00 = LinearCombination.value(a.m00, b.m00, a.m01, b.m10);
629         final double c01 = LinearCombination.value(a.m00, b.m01, a.m01, b.m11);
630         final double c02 = LinearCombination.value(a.m00, b.m02, a.m01, b.m12) + a.m02;
631 
632         final double c10 = LinearCombination.value(a.m10, b.m00, a.m11, b.m10);
633         final double c11 = LinearCombination.value(a.m10, b.m01, a.m11, b.m11);
634         final double c12 = LinearCombination.value(a.m10, b.m02, a.m11, b.m12) + a.m12;
635 
636         return new AffineTransformMatrix2D(
637                     c00, c01, c02,
638                     c10, c11, c12
639                 );
640     }
641 }