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 }