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 }