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