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.euclidean.twod;
018
019import java.util.function.UnaryOperator;
020
021import org.apache.commons.geometry.core.internal.DoubleFunction2N;
022import org.apache.commons.geometry.euclidean.AbstractAffineTransformMatrix;
023import org.apache.commons.geometry.euclidean.internal.Matrices;
024import org.apache.commons.geometry.euclidean.internal.Vectors;
025import org.apache.commons.geometry.euclidean.twod.rotation.Rotation2D;
026
027/** Class using a matrix to represent affine transformations in 2 dimensional Euclidean space.
028*
029* <p>Instances of this class use a 3x3 matrix for all transform operations.
030* The last row of this matrix is always set to the values <code>[0 0 1]</code> and so
031* is not stored. Hence, the methods in this class that accept or return arrays always
032* use arrays containing 6 elements, instead of 9.
033* </p>
034*/
035public final class AffineTransformMatrix2D extends AbstractAffineTransformMatrix<Vector2D, AffineTransformMatrix2D> {
036    /** The number of internal matrix elements. */
037    private static final int NUM_ELEMENTS = 6;
038
039    /** String used to start the transform matrix string representation. */
040    private static final String MATRIX_START = "[ ";
041
042    /** String used to end the transform matrix string representation. */
043    private static final String MATRIX_END = " ]";
044
045    /** String used to separate elements in the matrix string representation. */
046    private static final String ELEMENT_SEPARATOR = ", ";
047
048    /** String used to separate rows in the matrix string representation. */
049    private static final String ROW_SEPARATOR = "; ";
050
051    /** Shared transform set to the identity matrix. */
052    private static final AffineTransformMatrix2D IDENTITY_INSTANCE = new AffineTransformMatrix2D(
053                1, 0, 0,
054                0, 1, 0
055            );
056
057    /** Transform matrix entry <code>m<sub>0,0</sub></code>. */
058    private final double m00;
059    /** Transform matrix entry <code>m<sub>0,1</sub></code>. */
060    private final double m01;
061    /** Transform matrix entry <code>m<sub>0,2</sub></code>. */
062    private final double m02;
063
064    /** Transform matrix entry <code>m<sub>1,0</sub></code>. */
065    private final double m10;
066    /** Transform matrix entry <code>m<sub>1,1</sub></code>. */
067    private final double m11;
068    /** Transform matrix entry <code>m<sub>1,2</sub></code>. */
069    private final double m12;
070
071    /**
072     * Simple constructor; sets all internal matrix elements.
073     * @param m00 matrix entry <code>m<sub>0,0</sub></code>
074     * @param m01 matrix entry <code>m<sub>0,1</sub></code>
075     * @param m02 matrix entry <code>m<sub>0,2</sub></code>
076     * @param m10 matrix entry <code>m<sub>1,0</sub></code>
077     * @param m11 matrix entry <code>m<sub>1,1</sub></code>
078     * @param m12 matrix entry <code>m<sub>1,2</sub></code>
079     */
080    private AffineTransformMatrix2D(
081            final double m00, final double m01, final double m02,
082            final double m10, final double m11, final double m12) {
083
084        this.m00 = m00;
085        this.m01 = m01;
086        this.m02 = m02;
087
088        this.m10 = m10;
089        this.m11 = m11;
090        this.m12 = m12;
091    }
092
093    /** Return a 6 element array containing the variable elements from the
094     * internal transformation matrix. The elements are in row-major order.
095     * The array indices map to the internal matrix as follows:
096     * <pre>
097     *      [
098     *          arr[0],   arr[1],   arr[2],
099     *          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}