AffineTransformMatrix2D.java

  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. import java.util.function.UnaryOperator;

  19. import org.apache.commons.geometry.core.internal.DoubleFunction2N;
  20. import org.apache.commons.geometry.euclidean.AbstractAffineTransformMatrix;
  21. import org.apache.commons.geometry.euclidean.internal.Matrices;
  22. import org.apache.commons.geometry.euclidean.internal.Vectors;
  23. import org.apache.commons.geometry.euclidean.twod.rotation.Rotation2D;

  24. /** Class using a matrix to represent affine transformations in 2 dimensional Euclidean space.
  25. *
  26. * <p>Instances of this class use a 3x3 matrix for all transform operations.
  27. * The last row of this matrix is always set to the values <code>[0 0 1]</code> and so
  28. * is not stored. Hence, the methods in this class that accept or return arrays always
  29. * use arrays containing 6 elements, instead of 9.
  30. * </p>
  31. */
  32. public final class AffineTransformMatrix2D extends AbstractAffineTransformMatrix<Vector2D, AffineTransformMatrix2D> {
  33.     /** The number of internal matrix elements. */
  34.     private static final int NUM_ELEMENTS = 6;

  35.     /** String used to start the transform matrix string representation. */
  36.     private static final String MATRIX_START = "[ ";

  37.     /** String used to end the transform matrix string representation. */
  38.     private static final String MATRIX_END = " ]";

  39.     /** String used to separate elements in the matrix string representation. */
  40.     private static final String ELEMENT_SEPARATOR = ", ";

  41.     /** String used to separate rows in the matrix string representation. */
  42.     private static final String ROW_SEPARATOR = "; ";

  43.     /** Shared transform set to the identity matrix. */
  44.     private static final AffineTransformMatrix2D IDENTITY_INSTANCE = new AffineTransformMatrix2D(
  45.                 1, 0, 0,
  46.                 0, 1, 0
  47.             );

  48.     /** Transform matrix entry <code>m<sub>0,0</sub></code>. */
  49.     private final double m00;
  50.     /** Transform matrix entry <code>m<sub>0,1</sub></code>. */
  51.     private final double m01;
  52.     /** Transform matrix entry <code>m<sub>0,2</sub></code>. */
  53.     private final double m02;

  54.     /** Transform matrix entry <code>m<sub>1,0</sub></code>. */
  55.     private final double m10;
  56.     /** Transform matrix entry <code>m<sub>1,1</sub></code>. */
  57.     private final double m11;
  58.     /** Transform matrix entry <code>m<sub>1,2</sub></code>. */
  59.     private final double m12;

  60.     /**
  61.      * Simple constructor; sets all internal matrix elements.
  62.      * @param m00 matrix entry <code>m<sub>0,0</sub></code>
  63.      * @param m01 matrix entry <code>m<sub>0,1</sub></code>
  64.      * @param m02 matrix entry <code>m<sub>0,2</sub></code>
  65.      * @param m10 matrix entry <code>m<sub>1,0</sub></code>
  66.      * @param m11 matrix entry <code>m<sub>1,1</sub></code>
  67.      * @param m12 matrix entry <code>m<sub>1,2</sub></code>
  68.      */
  69.     private AffineTransformMatrix2D(
  70.             final double m00, final double m01, final double m02,
  71.             final double m10, final double m11, final double m12) {

  72.         this.m00 = m00;
  73.         this.m01 = m01;
  74.         this.m02 = m02;

  75.         this.m10 = m10;
  76.         this.m11 = m11;
  77.         this.m12 = m12;
  78.     }

  79.     /** Return a 6 element array containing the variable elements from the
  80.      * internal transformation matrix. The elements are in row-major order.
  81.      * The array indices map to the internal matrix as follows:
  82.      * <pre>
  83.      *      [
  84.      *          arr[0],   arr[1],   arr[2],
  85.      *          arr[3],   arr[4],   arr[5],
  86.      *          0         0         1
  87.      *      ]
  88.      * </pre>
  89.      * @return 6 element array containing the variable elements from the
  90.      *      internal transformation matrix
  91.      */
  92.     public double[] toArray() {
  93.         return new double[] {
  94.             m00, m01, m02,
  95.             m10, m11, m12
  96.         };
  97.     }

  98.     /** Apply this transform to the given point, returning the result as a new instance.
  99.     *
  100.     * <p>The transformed point is computed by creating a 3-element column vector from the
  101.     * coordinates in the input and setting the last element to 1. This is then multiplied with the
  102.     * 3x3 transform matrix to produce the transformed point. The {@code 1} in the last position
  103.     * is ignored.
  104.     * <pre>
  105.     *      [ m00  m01  m02 ]     [ x ]     [ x']
  106.     *      [ m10  m11  m12 ]  *  [ y ]  =  [ y']
  107.     *      [ 0    0    1   ]     [ 1 ]     [ 1 ]
  108.     * </pre>
  109.     */
  110.     @Override
  111.     public Vector2D apply(final Vector2D pt) {
  112.         final double x = pt.getX();
  113.         final double y = pt.getY();

  114.         return Vector2D.of(
  115.                 applyX(x, y),
  116.                 applyY(x, y));
  117.     }

  118.     /** Apply this transform to the given point coordinates and return the transformed
  119.      * x value. The return value is equal to
  120.      * <code>(x * m<sub>00</sub>) + (y * m<sub>01</sub>) + m<sub>02</sub></code>.
  121.      * @param x x coordinate value
  122.      * @param y y coordinate value
  123.      * @return transformed x coordinate value
  124.      * @see #apply(Vector2D)
  125.      */
  126.     public double applyX(final double x, final double y) {
  127.         return applyVectorX(x, y) + m02;
  128.     }

  129.     /** Apply this transform to the given point coordinates and return the transformed
  130.      * y value. The return value is equal to
  131.      * <code>(x * m<sub>10</sub>) + (y * m<sub>11</sub>) + m<sub>12</sub></code>.
  132.      * @param x x coordinate value
  133.      * @param y y coordinate value
  134.      * @return transformed y coordinate value
  135.      * @see #apply(Vector2D)
  136.      */
  137.     public double applyY(final double x, final double y) {
  138.         return applyVectorY(x, y) + m12;
  139.     }

  140.     /** {@inheritDoc}
  141.     *
  142.     *  <p>The transformed vector is computed by creating a 3-element column vector from the
  143.     * coordinates in the input and setting the last element to 0. This is then multiplied with the
  144.     * 3x3 transform matrix to produce the transformed vector. The {@code 0} in the last position
  145.     * is ignored.
  146.     * <pre>
  147.     *      [ m00  m01  m02 ]     [ x ]     [ x']
  148.     *      [ m10  m11  m12 ]  *  [ y ]  =  [ y']
  149.     *      [ 0    0    1   ]     [ 0 ]     [ 0 ]
  150.     * </pre>
  151.     *
  152.     * @see #applyDirection(Vector2D)
  153.     */
  154.     @Override
  155.     public Vector2D applyVector(final Vector2D vec) {
  156.         return applyVector(vec, Vector2D::of);
  157.     }

  158.     /** Apply this transform to the given vector coordinates, ignoring translations, and
  159.      * return the transformed x value. The return value is equal to
  160.      * <code>(x * m<sub>00</sub>) + (y * m<sub>01</sub>)</code>.
  161.      * @param x x coordinate value
  162.      * @param y y coordinate value
  163.      * @return transformed x coordinate value
  164.      * @see #applyVector(Vector2D)
  165.      */
  166.     public double applyVectorX(final double x, final double y) {
  167.         return Vectors.linearCombination(m00, x, m01, y);
  168.     }

  169.     /** Apply this transform to the given vector coordinates, ignoring translations, and
  170.      * return the transformed y value. The return value is equal to
  171.      * <code>(x * m<sub>10</sub>) + (y * m<sub>11</sub>)</code>.
  172.      * @param x x coordinate value
  173.      * @param y y coordinate value
  174.      * @return transformed y coordinate value
  175.      * @see #applyVector(Vector2D)
  176.      */
  177.     public double applyVectorY(final double x, final double y) {
  178.         return Vectors.linearCombination(m10, x, m11, y);
  179.     }

  180.     /** {@inheritDoc}
  181.      * @see #applyVector(Vector2D)
  182.      */
  183.     @Override
  184.     public Vector2D.Unit applyDirection(final Vector2D vec) {
  185.         return applyVector(vec, Vector2D.Unit::from);
  186.     }

  187.     /** {@inheritDoc} */
  188.     @Override
  189.     public double determinant() {
  190.         return Matrices.determinant(
  191.                 m00, m01,
  192.                 m10, m11
  193.             );
  194.     }

  195.     /** {@inheritDoc}
  196.      *
  197.      * <p><strong>Example</strong>
  198.      * <pre>
  199.      *      [ a, b, c ]   [ a, b, 0 ]
  200.      *      [ d, e, f ] &rarr; [ d, e, 0 ]
  201.      *      [ 0, 0, 1 ]   [ 0, 0, 1 ]
  202.      * </pre>
  203.      */
  204.     @Override
  205.     public AffineTransformMatrix2D linear() {
  206.         return new AffineTransformMatrix2D(
  207.                 m00, m01, 0.0,
  208.                 m10, m11, 0.0);
  209.     }

  210.     /** {@inheritDoc}
  211.      *
  212.      * <p><strong>Example</strong>
  213.      * <pre>
  214.      *      [ a, b, c ]   [ a, d, 0 ]
  215.      *      [ d, e, f ] &rarr; [ b, e, 0 ]
  216.      *      [ 0, 0, 1 ]   [ 0, 0, 1 ]
  217.      * </pre>
  218.      */
  219.     @Override
  220.     public AffineTransformMatrix2D linearTranspose() {
  221.         return new AffineTransformMatrix2D(
  222.                 m00, m10, 0.0,
  223.                 m01, m11, 0.0);
  224.     }

  225.     /** Apply a translation to the current instance, returning the result as a new transform.
  226.      * @param translation vector containing the translation values for each axis
  227.      * @return a new transform containing the result of applying a translation to
  228.      *      the current instance
  229.      */
  230.     public AffineTransformMatrix2D translate(final Vector2D translation) {
  231.         return translate(translation.getX(), translation.getY());
  232.     }

  233.     /** Apply a translation to the current instance, returning the result as a new transform.
  234.      * @param x translation in the x direction
  235.      * @param y translation in the y direction
  236.      * @return a new transform containing the result of applying a translation to
  237.      *      the current instance
  238.      */
  239.     public AffineTransformMatrix2D translate(final double x, final double y) {
  240.         return new AffineTransformMatrix2D(
  241.                     m00, m01, m02 + x,
  242.                     m10, m11, m12 + y
  243.                 );
  244.     }

  245.     /** Apply a scale operation to the current instance, returning the result as a new transform.
  246.      * @param factor the scale factor to apply to all axes
  247.      * @return a new transform containing the result of applying a scale operation to
  248.      *      the current instance
  249.      */
  250.     public AffineTransformMatrix2D scale(final double factor) {
  251.         return scale(factor, factor);
  252.     }

  253.     /** Apply a scale operation to the current instance, returning the result as a new transform.
  254.      * @param scaleFactors vector containing scale factors for each axis
  255.      * @return a new transform containing the result of applying a scale operation to
  256.      *      the current instance
  257.      */
  258.     public AffineTransformMatrix2D scale(final Vector2D scaleFactors) {
  259.         return scale(scaleFactors.getX(), scaleFactors.getY());
  260.     }

  261.     /** Apply a scale operation to the current instance, returning the result as a new transform.
  262.      * @param x scale factor for the x axis
  263.      * @param y scale factor for the y axis
  264.      * @return a new transform containing the result of applying a scale operation to
  265.      *      the current instance
  266.      */
  267.     public AffineTransformMatrix2D scale(final double x, final double y) {
  268.         return new AffineTransformMatrix2D(
  269.                 m00 * x, m01 * x, m02 * x,
  270.                 m10 * y, m11 * y, m12 * y
  271.             );
  272.     }

  273.     /** Apply a <em>counterclockwise</em> rotation to the current instance, returning the result as a
  274.      * new transform.
  275.      * @param angle the angle of counterclockwise rotation in radians
  276.      * @return a new transform containing the result of applying a rotation to the
  277.      *      current instance
  278.      * @see Rotation2D#of(double)
  279.      */
  280.     public AffineTransformMatrix2D rotate(final double angle) {
  281.         return rotate(Rotation2D.of(angle));
  282.     }

  283.     /** Apply a <em>counterclockwise</em> rotation to the current instance, returning the result as a
  284.      *  new transform.
  285.      * @param rotation the rotation to apply
  286.      * @return a new transform containing the result of applying the rotation to the
  287.      *      current instance
  288.      */
  289.     public AffineTransformMatrix2D rotate(final Rotation2D rotation) {
  290.         return multiply(rotation.toMatrix(), this);
  291.     }

  292.     /** Apply a <em>counterclockwise</em> rotation about the given center point to the current instance,
  293.      * returning the result as a new transform. This is accomplished by translating the center to the origin,
  294.      * applying the rotation, and then translating back.
  295.      * @param center the center of rotation
  296.      * @param angle the angle of counterclockwise rotation in radians
  297.      * @return a new transform containing the result of applying a rotation about the given
  298.      *      center point to the current instance
  299.      */
  300.     public AffineTransformMatrix2D rotate(final Vector2D center, final double angle) {
  301.         return multiply(createRotation(center, angle), this);
  302.     }

  303.     /** Apply a <em>counterclockwise</em> rotation about the given center point to the current instance,
  304.      * returning the result as a new transform. This is accomplished by translating the center to the origin,
  305.      * applying the rotation, and then translating back.
  306.      * @param center the center of rotation
  307.      * @param rotation the rotation to apply
  308.      * @return a new transform containing the result of applying a rotation about the given
  309.      *      center point to the current instance
  310.      */
  311.     public AffineTransformMatrix2D rotate(final Vector2D center, final Rotation2D rotation) {
  312.         // use to raw angle method to avoid matrix multiplication
  313.         return rotate(center, rotation.getAngle());
  314.     }

  315.     /** Apply a shear to the current instance, returning the result as a new transform.
  316.      * @param shx multiplier by which coordinates are shifted along the positive x-axis as a factor of their
  317.      *      y coordinate; a value of 0 indicates no shift along the x-axis
  318.      * @param shy multiplier by which coordinates are shifted along the positive y-axis as a factor of their
  319.      *      x coordinate; a value of 0 indicates no shift along the y-axis
  320.      * @return a new transform containing the result of applying a shear to the current instance
  321.      */
  322.     public AffineTransformMatrix2D shear(final double shx, final double shy) {
  323.         return multiply(createShear(shx, shy), this);
  324.     }

  325.     /** Get a new transform created by multiplying this instance by the argument.
  326.      * This is equivalent to the expression {@code A * M} where {@code A} is the
  327.      * current transform matrix and {@code M} is the given transform matrix. In
  328.      * terms of transformations, applying the returned matrix is equivalent to
  329.      * applying {@code M} and <em>then</em> applying {@code A}. In other words,
  330.      * the rightmost transform is applied first.
  331.      *
  332.      * @param m the transform to multiply with
  333.      * @return the result of multiplying the current instance by the given
  334.      *      transform matrix
  335.      */
  336.     public AffineTransformMatrix2D multiply(final AffineTransformMatrix2D m) {
  337.         return multiply(this, m);
  338.     }

  339.     /** Get a new transform created by multiplying the argument by this instance.
  340.      * This is equivalent to the expression {@code M * A} where {@code A} is the
  341.      * current transform matrix and {@code M} is the given transform matrix. In
  342.      * terms of transformations, applying the returned matrix is equivalent to
  343.      * applying {@code A} and <em>then</em> applying {@code M}. In other words,
  344.      * the rightmost transform is applied first.
  345.      *
  346.      * @param m the transform to multiply with
  347.      * @return the result of multiplying the given transform matrix by the current
  348.      *      instance
  349.      */
  350.     public AffineTransformMatrix2D premultiply(final AffineTransformMatrix2D m) {
  351.         return multiply(m, this);
  352.     }

  353.     /** {@inheritDoc}
  354.     *
  355.     * @throws IllegalStateException if the matrix cannot be inverted
  356.     */
  357.     @Override
  358.     public AffineTransformMatrix2D inverse() {

  359.         // Our full matrix is 3x3 but we can significantly reduce the amount of computations
  360.         // needed here since we know that our last row is [0 0 1].

  361.         final double det = Matrices.checkDeterminantForInverse(determinant());

  362.         // validate the remaining matrix elements that were not part of the determinant
  363.         Matrices.checkElementForInverse(m02);
  364.         Matrices.checkElementForInverse(m12);

  365.         // compute the necessary elements of the cofactor matrix
  366.         // (we need all but the last column)

  367.         final double invDet = 1.0 / det;

  368.         final double c00 = invDet * m11;
  369.         final double c01 = -invDet * m10;

  370.         final double c10 = -invDet * m01;
  371.         final double c11 = invDet * m00;

  372.         final double c20 = invDet * Matrices.determinant(m01, m02, m11, m12);
  373.         final double c21 = -invDet * Matrices.determinant(m00, m02, m10, m12);

  374.         return new AffineTransformMatrix2D(
  375.                     c00, c10, c20,
  376.                     c01, c11, c21
  377.                 );
  378.     }

  379.     /** {@inheritDoc} */
  380.     @Override
  381.     public int hashCode() {
  382.         final int prime = 31;
  383.         int result = 1;

  384.         result = (result * prime) + (Double.hashCode(m00) - Double.hashCode(m01) + Double.hashCode(m02));
  385.         result = (result * prime) + (Double.hashCode(m10) - Double.hashCode(m11) + Double.hashCode(m12));

  386.         return result;
  387.     }

  388.     /**
  389.      * Return true if the given object is an instance of {@link AffineTransformMatrix2D}
  390.      * and all matrix element values are exactly equal.
  391.      * @param obj object to test for equality with the current instance
  392.      * @return true if all transform matrix elements are exactly equal; otherwise false
  393.      */
  394.     @Override
  395.     public boolean equals(final Object obj) {
  396.         if (this == obj) {
  397.             return true;
  398.         }
  399.         if (!(obj instanceof AffineTransformMatrix2D)) {
  400.             return false;
  401.         }

  402.         final AffineTransformMatrix2D other = (AffineTransformMatrix2D) obj;

  403.         return Double.compare(this.m00, other.m00) == 0 &&
  404.                 Double.compare(this.m01, other.m01) == 0 &&
  405.                 Double.compare(this.m02, other.m02) == 0 &&

  406.                 Double.compare(this.m10, other.m10) == 0 &&
  407.                 Double.compare(this.m11, other.m11) == 0 &&
  408.                 Double.compare(this.m12, other.m12) == 0;
  409.     }

  410.     /** {@inheritDoc} */
  411.     @Override
  412.     public String toString() {
  413.         final StringBuilder sb = new StringBuilder();

  414.         sb.append(MATRIX_START)

  415.             .append(m00)
  416.             .append(ELEMENT_SEPARATOR)
  417.             .append(m01)
  418.             .append(ELEMENT_SEPARATOR)
  419.             .append(m02)
  420.             .append(ROW_SEPARATOR)

  421.             .append(m10)
  422.             .append(ELEMENT_SEPARATOR)
  423.             .append(m11)
  424.             .append(ELEMENT_SEPARATOR)
  425.             .append(m12)

  426.             .append(MATRIX_END);

  427.         return sb.toString();
  428.     }

  429.     /** Multiplies the given vector by the 2x2 linear transformation matrix contained in the
  430.      * upper-right corner of the affine transformation matrix. This applies all transformation
  431.      * operations except for translations. The computed coordinates are passed to the given
  432.      * factory function.
  433.      * @param <T> factory output type
  434.      * @param vec the vector to transform
  435.      * @param factory the factory instance that will be passed the transformed coordinates
  436.      * @return the factory return value
  437.      */
  438.     private <T> T applyVector(final Vector2D vec, final DoubleFunction2N<T> factory) {
  439.         final double x = vec.getX();
  440.         final double y = vec.getY();

  441.         return factory.apply(
  442.                 applyVectorX(x, y),
  443.                 applyVectorY(x, y));
  444.     }

  445.     /** Get a new transform with the given matrix elements. The array must contain 6 elements.
  446.      * @param arr 6-element array containing values for the variable entries in the
  447.      *      transform matrix
  448.      * @return a new transform initialized with the given matrix values
  449.      * @throws IllegalArgumentException if the array does not have 6 elements
  450.      */
  451.     public static AffineTransformMatrix2D of(final double... arr) {
  452.         if (arr.length != NUM_ELEMENTS) {
  453.             throw new IllegalArgumentException("Dimension mismatch: " + arr.length + " != " + NUM_ELEMENTS);
  454.         }

  455.         return new AffineTransformMatrix2D(
  456.                     arr[0], arr[1], arr[2],
  457.                     arr[3], arr[4], arr[5]
  458.                 );
  459.     }

  460.     /** Construct a new transform representing the given function. The function is sampled at
  461.      * the origin and along each axis and a matrix is created to perform the transformation.
  462.      * @param fn function to create a transform matrix from
  463.      * @return a transform matrix representing the given function
  464.      * @throws IllegalArgumentException if the given function does not represent a valid
  465.      *      affine transform
  466.      */
  467.     public static AffineTransformMatrix2D from(final UnaryOperator<Vector2D> fn) {
  468.         final Vector2D tPlusX = fn.apply(Vector2D.Unit.PLUS_X);
  469.         final Vector2D tPlusY = fn.apply(Vector2D.Unit.PLUS_Y);
  470.         final Vector2D tZero = fn.apply(Vector2D.ZERO);

  471.         final Vector2D u = tPlusX.subtract(tZero);
  472.         final Vector2D v = tPlusY.subtract(tZero);

  473.         final AffineTransformMatrix2D mat =  AffineTransformMatrix2D.fromColumnVectors(u, v, tZero);

  474.         final double det = mat.determinant();
  475.         if (!Vectors.isRealNonZero(det)) {
  476.             throw new IllegalArgumentException("Transform function is invalid: matrix determinant is " + det);
  477.         }

  478.         return mat;
  479.     }

  480.     /** Get a new transform create from the given column vectors. The returned transform
  481.      * does not include any translation component.
  482.      * @param u first column vector; this corresponds to the first basis vector
  483.      *      in the coordinate frame
  484.      * @param v second column vector; this corresponds to the second basis vector
  485.      *      in the coordinate frame
  486.      * @return a new transform with the given column vectors
  487.      */
  488.     public static AffineTransformMatrix2D fromColumnVectors(final Vector2D u, final Vector2D v) {
  489.         return fromColumnVectors(u, v, Vector2D.ZERO);
  490.     }

  491.     /** Get a new transform created from the given column vectors.
  492.      * @param u first column vector; this corresponds to the first basis vector
  493.      *      in the coordinate frame
  494.      * @param v second column vector; this corresponds to the second basis vector
  495.      *      in the coordinate frame
  496.      * @param t third column vector; this corresponds to the translation of the transform
  497.      * @return a new transform with the given column vectors
  498.      */
  499.     public static AffineTransformMatrix2D fromColumnVectors(final Vector2D u, final Vector2D v, final Vector2D t) {
  500.         return new AffineTransformMatrix2D(
  501.                     u.getX(), v.getX(), t.getX(),
  502.                     u.getY(), v.getY(), t.getY()
  503.                 );
  504.     }

  505.     /** Get the transform representing the identity matrix. This transform does not
  506.      * modify point or vector values when applied.
  507.      * @return transform representing the identity matrix
  508.      */
  509.     public static AffineTransformMatrix2D identity() {
  510.         return IDENTITY_INSTANCE;
  511.     }

  512.     /** Create a transform representing the given translation.
  513.      * @param translation vector containing translation values for each axis
  514.      * @return a new transform representing the given translation
  515.      */
  516.     public static AffineTransformMatrix2D createTranslation(final Vector2D translation) {
  517.         return createTranslation(translation.getX(), translation.getY());
  518.     }

  519.     /** Create a transform representing the given translation.
  520.      * @param x translation in the x direction
  521.      * @param y translation in the y direction
  522.      * @return a new transform representing the given translation
  523.      */
  524.     public static AffineTransformMatrix2D createTranslation(final double x, final double y) {
  525.         return new AffineTransformMatrix2D(
  526.                     1, 0, x,
  527.                     0, 1, y
  528.                 );
  529.     }

  530.     /** Create a transform representing a scale operation with the given scale factor applied to all axes.
  531.      * @param factor scale factor to apply to all axes
  532.      * @return a new transform representing a uniform scaling in all axes
  533.      */
  534.     public static AffineTransformMatrix2D createScale(final double factor) {
  535.         return createScale(factor, factor);
  536.     }

  537.     /** Create a transform representing a scale operation.
  538.      * @param factors vector containing scale factors for each axis
  539.      * @return a new transform representing a scale operation
  540.      */
  541.     public static AffineTransformMatrix2D createScale(final Vector2D factors) {
  542.         return createScale(factors.getX(), factors.getY());
  543.     }

  544.     /** Create a transform representing a scale operation.
  545.      * @param x scale factor for the x axis
  546.      * @param y scale factor for the y axis
  547.      * @return a new transform representing a scale operation
  548.      */
  549.     public static AffineTransformMatrix2D createScale(final double x, final double y) {
  550.         return new AffineTransformMatrix2D(
  551.                     x, 0, 0,
  552.                     0, y, 0
  553.                 );
  554.     }

  555.     /** Create a transform representing a <em>counterclockwise</em> rotation of {@code angle}
  556.      * radians around the origin.
  557.      * @param angle the angle of rotation in radians
  558.      * @return a new transform representing the rotation
  559.      * @see Rotation2D#toMatrix()
  560.      */
  561.     public static AffineTransformMatrix2D createRotation(final double angle) {
  562.         return Rotation2D.of(angle).toMatrix();
  563.     }

  564.     /** Create a transform representing a <em>counterclockwise</em> rotation of {@code angle}
  565.      * radians around the given center point. This is accomplished by translating the center point
  566.      * to the origin, applying the rotation, and then translating back.
  567.      * @param center the center of rotation
  568.      * @param angle the angle of rotation in radians
  569.      * @return a new transform representing the rotation about the given center
  570.      */
  571.     public static AffineTransformMatrix2D createRotation(final Vector2D center, final double angle) {
  572.         // it's possible to do this using Rotation2D to create the rotation matrix but we
  573.         // can avoid the matrix multiplications by simply doing everything in-line here
  574.         final double x = center.getX();
  575.         final double y = center.getY();

  576.         final double sin = Math.sin(angle);
  577.         final double cos = Math.cos(angle);

  578.         return new AffineTransformMatrix2D(
  579.                 cos, -sin, (-x * cos) + (y * sin) + x,
  580.                 sin, cos, (-x * sin) - (y * cos) + y
  581.             );
  582.     }

  583.     /** Create a transform representing a <em>counterclockwise</em> rotation around the given center point.
  584.      * This is accomplished by translating the center point to the origin, applying the rotation, and then
  585.      * translating back.
  586.      * @param center the center of rotation
  587.      * @param rotation the rotation to apply
  588.      * @return a new transform representing the rotation about the given center
  589.      */
  590.     public static AffineTransformMatrix2D createRotation(final Vector2D center, final Rotation2D rotation) {
  591.         return createRotation(center, rotation.getAngle());
  592.     }

  593.     /** Create a transform representing a shear operation. The returned instance contains the
  594.      * matrix values
  595.      * <pre>
  596.      *      [ 1,    shx,  0 ]
  597.      *      [ shy,  1,    0 ]
  598.      *      [ 0,    0,    0 ]
  599.      * </pre>
  600.      * @param shx multiplier by which coordinates are shifted along the positive x-axis as a factor of their
  601.      *      y coordinate; a value of 0 indicates no shift along the x-axis
  602.      * @param shy multiplier by which coordinates are shifted along the positive y-axis as a factor of their
  603.      *      x coordinate; a value of 0 indicates no shift along the y-axis
  604.      * @return a new transform representing the shear operation
  605.      */
  606.     public static AffineTransformMatrix2D createShear(final double shx, final double shy) {
  607.         return new AffineTransformMatrix2D(
  608.                     1, shx, 0,
  609.                     shy, 1, 0
  610.                 );
  611.     }

  612.     /** Multiply two transform matrices together.
  613.      * @param a first transform
  614.      * @param b second transform
  615.      * @return the transform computed as {@code a x b}
  616.      */
  617.     private static AffineTransformMatrix2D multiply(final AffineTransformMatrix2D a,
  618.             final AffineTransformMatrix2D b) {

  619.         final double c00 = Vectors.linearCombination(a.m00, b.m00, a.m01, b.m10);
  620.         final double c01 = Vectors.linearCombination(a.m00, b.m01, a.m01, b.m11);
  621.         final double c02 = Vectors.linearCombination(a.m00, b.m02, a.m01, b.m12) + a.m02;

  622.         final double c10 = Vectors.linearCombination(a.m10, b.m00, a.m11, b.m10);
  623.         final double c11 = Vectors.linearCombination(a.m10, b.m01, a.m11, b.m11);
  624.         final double c12 = Vectors.linearCombination(a.m10, b.m02, a.m11, b.m12) + a.m12;

  625.         return new AffineTransformMatrix2D(
  626.                     c00, c01, c02,
  627.                     c10, c11, c12
  628.                 );
  629.     }
  630. }