AffineTransformMatrix3D.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.threed;

  18. import java.util.function.UnaryOperator;

  19. import org.apache.commons.geometry.core.internal.DoubleFunction3N;
  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.threed.rotation.QuaternionRotation;

  24. /** Class using a matrix to represent affine transformations in 3 dimensional Euclidean space.
  25.  *
  26.  * <p>Instances of this class use a 4x4 matrix for all transform operations.
  27.  * The last row of this matrix is always set to the values <code>[0 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 12 elements, instead of 16.
  30.  * </p>
  31.  */
  32. public final class AffineTransformMatrix3D extends AbstractAffineTransformMatrix<Vector3D, AffineTransformMatrix3D> {
  33.     /** The number of internal matrix elements. */
  34.     private static final int NUM_ELEMENTS = 12;

  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 AffineTransformMatrix3D IDENTITY_INSTANCE = new AffineTransformMatrix3D(
  45.                 1, 0, 0, 0,
  46.                 0, 1, 0, 0,
  47.                 0, 0, 1, 0
  48.             );

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

  57.     /** Transform matrix entry <code>m<sub>1,0</sub></code>. */
  58.     private final double m10;
  59.     /** Transform matrix entry <code>m<sub>1,1</sub></code>. */
  60.     private final double m11;
  61.     /** Transform matrix entry <code>m<sub>1,2</sub></code>. */
  62.     private final double m12;
  63.     /** Transform matrix entry <code>m<sub>1,3</sub></code>. */
  64.     private final double m13;

  65.     /** Transform matrix entry <code>m<sub>2,0</sub></code>. */
  66.     private final double m20;
  67.     /** Transform matrix entry <code>m<sub>2,1</sub></code>. */
  68.     private final double m21;
  69.     /** Transform matrix entry <code>m<sub>2,2</sub></code>. */
  70.     private final double m22;
  71.     /** Transform matrix entry <code>m<sub>2,3</sub></code>. */
  72.     private final double m23;

  73.     /**
  74.      * Package-private constructor; sets all internal matrix elements.
  75.      * @param m00 matrix entry <code>m<sub>0,0</sub></code>
  76.      * @param m01 matrix entry <code>m<sub>0,1</sub></code>
  77.      * @param m02 matrix entry <code>m<sub>0,2</sub></code>
  78.      * @param m03 matrix entry <code>m<sub>0,3</sub></code>
  79.      * @param m10 matrix entry <code>m<sub>1,0</sub></code>
  80.      * @param m11 matrix entry <code>m<sub>1,1</sub></code>
  81.      * @param m12 matrix entry <code>m<sub>1,2</sub></code>
  82.      * @param m13 matrix entry <code>m<sub>1,3</sub></code>
  83.      * @param m20 matrix entry <code>m<sub>2,0</sub></code>
  84.      * @param m21 matrix entry <code>m<sub>2,1</sub></code>
  85.      * @param m22 matrix entry <code>m<sub>2,2</sub></code>
  86.      * @param m23 matrix entry <code>m<sub>2,3</sub></code>
  87.      */
  88.     private AffineTransformMatrix3D(
  89.             final double m00, final double m01, final double m02, final double m03,
  90.             final double m10, final double m11, final double m12, final double m13,
  91.             final double m20, final double m21, final double m22, final double m23) {

  92.         this.m00 = m00;
  93.         this.m01 = m01;
  94.         this.m02 = m02;
  95.         this.m03 = m03;

  96.         this.m10 = m10;
  97.         this.m11 = m11;
  98.         this.m12 = m12;
  99.         this.m13 = m13;

  100.         this.m20 = m20;
  101.         this.m21 = m21;
  102.         this.m22 = m22;
  103.         this.m23 = m23;
  104.     }

  105.     /** Return a 12 element array containing the variable elements from the
  106.      * internal transformation matrix. The elements are in row-major order.
  107.      * The array indices map to the internal matrix as follows:
  108.      * <pre>
  109.      *      [
  110.      *          arr[0],   arr[1],   arr[2],   arr[3]
  111.      *          arr[4],   arr[5],   arr[6],   arr[7],
  112.      *          arr[8],   arr[9],   arr[10],  arr[11],
  113.      *          0         0         0         1
  114.      *      ]
  115.      * </pre>
  116.      * @return 12 element array containing the variable elements from the
  117.      *      internal transformation matrix
  118.      */
  119.     public double[] toArray() {
  120.         return new double[] {
  121.             m00, m01, m02, m03,
  122.             m10, m11, m12, m13,
  123.             m20, m21, m22, m23
  124.         };
  125.     }

  126.     /** Apply this transform to the given point, returning the result as a new instance.
  127.      *
  128.      * <p>The transformed point is computed by creating a 4-element column vector from the
  129.      * coordinates in the input and setting the last element to 1. This is then multiplied with the
  130.      * 4x4 transform matrix to produce the transformed point. The {@code 1} in the last position
  131.      * is ignored.
  132.      * <pre>
  133.      *      [ m00  m01  m02  m03 ]     [ x ]     [ x']
  134.      *      [ m10  m11  m12  m13 ]  *  [ y ]  =  [ y']
  135.      *      [ m20  m21  m22  m23 ]     [ z ]     [ z']
  136.      *      [ 0    0    0    1   ]     [ 1 ]     [ 1 ]
  137.      * </pre>
  138.      */
  139.     @Override
  140.     public Vector3D apply(final Vector3D pt) {
  141.         final double x = pt.getX();
  142.         final double y = pt.getY();
  143.         final double z = pt.getZ();

  144.         return Vector3D.of(
  145.                 applyX(x, y, z),
  146.                 applyY(x, y, z),
  147.                 applyZ(x, y, z));
  148.     }

  149.     /** Apply this transform to the given point coordinates and return the transformed
  150.      * x value. The return value is equal to
  151.      * <code>(x * m<sub>00</sub>) + (y * m<sub>01</sub>) + (z * m<sub>02</sub>) + m<sub>03</sub></code>.
  152.      * @param x x coordinate value
  153.      * @param y y coordinate value
  154.      * @param z z coordinate value
  155.      * @return transformed x coordinate value
  156.      * @see #apply(Vector3D)
  157.      */
  158.     public double applyX(final double x, final double y, final double z) {
  159.         return applyVectorX(x, y, z) + m03;
  160.     }

  161.     /** Apply this transform to the given point coordinates and return the transformed
  162.      * y value. The return value is equal to
  163.      * <code>(x * m<sub>10</sub>) + (y * m<sub>11</sub>) + (z * m<sub>12</sub>) + m<sub>13</sub></code>.
  164.      * @param x x coordinate value
  165.      * @param y y coordinate value
  166.      * @param z z coordinate value
  167.      * @return transformed y coordinate value
  168.      * @see #apply(Vector3D)
  169.      */
  170.     public double applyY(final double x, final double y, final double z) {
  171.         return applyVectorY(x, y, z) + m13;
  172.     }

  173.     /** Apply this transform to the given point coordinates and return the transformed
  174.      * z value. The return value is equal to
  175.      * <code>(x * m<sub>20</sub>) + (y * m<sub>21</sub>) + (z * m<sub>22</sub>) + m<sub>23</sub></code>.
  176.      * @param x x coordinate value
  177.      * @param y y coordinate value
  178.      * @param z z coordinate value
  179.      * @return transformed z coordinate value
  180.      * @see #apply(Vector3D)
  181.      */
  182.     public double applyZ(final double x, final double y, final double z) {
  183.         return applyVectorZ(x, y, z) + m23;
  184.     }

  185.     /** {@inheritDoc}
  186.      *
  187.      *  <p>The transformed vector is computed by creating a 4-element column vector from the
  188.      * coordinates in the input and setting the last element to 0. This is then multiplied with the
  189.      * 4x4 transform matrix to produce the transformed vector. The {@code 0} in the last position
  190.      * is ignored.
  191.      * <pre>
  192.      *      [ m00  m01  m02  m03 ]     [ x ]     [ x']
  193.      *      [ m10  m11  m12  m13 ]  *  [ y ]  =  [ y']
  194.      *      [ m20  m21  m22  m23 ]     [ z ]     [ z']
  195.      *      [ 0    0    0    1   ]     [ 0 ]     [ 0 ]
  196.      * </pre>
  197.      *
  198.      * @see #applyDirection(Vector3D)
  199.      */
  200.     @Override
  201.     public Vector3D applyVector(final Vector3D vec) {
  202.         return applyVector(vec, Vector3D::of);
  203.     }

  204.     /** Apply this transform to the given vector coordinates, ignoring translations, and
  205.      * return the transformed x value. The return value is equal to
  206.      * <code>(x * m<sub>00</sub>) + (y * m<sub>01</sub>) + (z * m<sub>02</sub>)</code>.
  207.      * @param x x coordinate value
  208.      * @param y y coordinate value
  209.      * @param z z coordinate value
  210.      * @return transformed x coordinate value
  211.      * @see #applyVector(Vector3D)
  212.      */
  213.     public double applyVectorX(final double x, final double y, final double z) {
  214.         return Vectors.linearCombination(m00, x, m01, y, m02, z);
  215.     }

  216.     /** Apply this transform to the given vector coordinates, ignoring translations, and
  217.      * return the transformed y value. The return value is equal to
  218.      * <code>(x * m<sub>10</sub>) + (y * m<sub>11</sub>) + (z * m<sub>12</sub>)</code>.
  219.      * @param x x coordinate value
  220.      * @param y y coordinate value
  221.      * @param z z coordinate value
  222.      * @return transformed y coordinate value
  223.      * @see #applyVector(Vector3D)
  224.      */
  225.     public double applyVectorY(final double x, final double y, final double z) {
  226.         return Vectors.linearCombination(m10, x, m11, y, m12, z);
  227.     }

  228.     /** Apply this transform to the given vector coordinates, ignoring translations, and
  229.      * return the transformed z value. The return value is equal to
  230.      * <code>(x * m<sub>20</sub>) + (y * m<sub>21</sub>) + (z * m<sub>22</sub>)</code>.
  231.      * @param x x coordinate value
  232.      * @param y y coordinate value
  233.      * @param z z coordinate value
  234.      * @return transformed z coordinate value
  235.      * @see #applyVector(Vector3D)
  236.      */
  237.     public double applyVectorZ(final double x, final double y, final double z) {
  238.         return Vectors.linearCombination(m20, x, m21, y, m22, z);
  239.     }

  240.     /** {@inheritDoc}
  241.      * @see #applyVector(Vector3D)
  242.      */
  243.     @Override
  244.     public Vector3D.Unit applyDirection(final Vector3D vec) {
  245.         return applyVector(vec, Vector3D.Unit::from);
  246.     }

  247.     /** {@inheritDoc} */
  248.     @Override
  249.     public double determinant() {
  250.         return Matrices.determinant(
  251.                 m00, m01, m02,
  252.                 m10, m11, m12,
  253.                 m20, m21, m22
  254.             );
  255.     }

  256.     /** {@inheritDoc}
  257.      *
  258.      * <p><strong>Example</strong>
  259.      * <pre>
  260.      *      [ a, b, c, d ]   [ a, b, c, 0 ]
  261.      *      [ e, f, g, h ]   [ e, f, g, 0 ]
  262.      *      [ i, j, k, l ] &rarr; [ i, j, k, 0 ]
  263.      *      [ 0, 0, 0, 1 ]   [ 0, 0, 0, 1 ]
  264.      * </pre>
  265.      */
  266.     @Override
  267.     public AffineTransformMatrix3D linear() {
  268.         return new AffineTransformMatrix3D(
  269.                 m00, m01, m02, 0.0,
  270.                 m10, m11, m12, 0.0,
  271.                 m20, m21, m22, 0.0);
  272.     }

  273.     /** {@inheritDoc}
  274.      *
  275.      * <p><strong>Example</strong>
  276.      * <pre>
  277.      *      [ a, b, c, d ]   [ a, e, i, 0 ]
  278.      *      [ e, f, g, h ]   [ b, f, j, 0 ]
  279.      *      [ i, j, k, l ] &rarr; [ c, g, k, 0 ]
  280.      *      [ 0, 0, 0, 1 ]   [ 0, 0, 0, 1 ]
  281.      * </pre>
  282.      */
  283.     @Override
  284.     public AffineTransformMatrix3D linearTranspose() {
  285.         return new AffineTransformMatrix3D(
  286.                 m00, m10, m20, 0.0,
  287.                 m01, m11, m21, 0.0,
  288.                 m02, m12, m22, 0.0);
  289.     }

  290.     /** Apply a translation to the current instance, returning the result as a new transform.
  291.      * @param translation vector containing the translation values for each axis
  292.      * @return a new transform containing the result of applying a translation to
  293.      *      the current instance
  294.      */
  295.     public AffineTransformMatrix3D translate(final Vector3D translation) {
  296.         return translate(translation.getX(), translation.getY(), translation.getZ());
  297.     }

  298.     /** Apply a translation to the current instance, returning the result as a new transform.
  299.      * @param x translation in the x direction
  300.      * @param y translation in the y direction
  301.      * @param z translation in the z direction
  302.      * @return a new transform containing the result of applying a translation to
  303.      *      the current instance
  304.      */
  305.     public AffineTransformMatrix3D translate(final double x, final double y, final double z) {
  306.         return new AffineTransformMatrix3D(
  307.                     m00, m01, m02, m03 + x,
  308.                     m10, m11, m12, m13 + y,
  309.                     m20, m21, m22, m23 + z
  310.                 );
  311.     }

  312.     /** Apply a scale operation to the current instance, returning the result as a new transform.
  313.      * @param factor the scale factor to apply to all axes
  314.      * @return a new transform containing the result of applying a scale operation to
  315.      *      the current instance
  316.      */
  317.     public AffineTransformMatrix3D scale(final double factor) {
  318.         return scale(factor, factor, factor);
  319.     }

  320.     /** Apply a scale operation to the current instance, returning the result as a new transform.
  321.      * @param scaleFactors vector containing scale factors for each axis
  322.      * @return a new transform containing the result of applying a scale operation to
  323.      *      the current instance
  324.      */
  325.     public AffineTransformMatrix3D scale(final Vector3D scaleFactors) {
  326.         return scale(scaleFactors.getX(), scaleFactors.getY(), scaleFactors.getZ());
  327.     }

  328.     /** Apply a scale operation to the current instance, returning the result as a new transform.
  329.      * @param x scale factor for the x axis
  330.      * @param y scale factor for the y axis
  331.      * @param z scale factor for the z axis
  332.      * @return a new transform containing the result of applying a scale operation to
  333.      *      the current instance
  334.      */
  335.     public AffineTransformMatrix3D scale(final double x, final double y, final double z) {
  336.         return new AffineTransformMatrix3D(
  337.                     m00 * x, m01 * x, m02 * x, m03 * x,
  338.                     m10 * y, m11 * y, m12 * y, m13 * y,
  339.                     m20 * z, m21 * z, m22 * z, m23 * z
  340.                 );
  341.     }

  342.     /** Apply a rotation to the current instance, returning the result as a new transform.
  343.      * @param rotation the rotation to apply
  344.      * @return a new transform containing the result of applying a rotation to the
  345.      *      current instance
  346.      * @see QuaternionRotation#toMatrix()
  347.      */
  348.     public AffineTransformMatrix3D rotate(final QuaternionRotation rotation) {
  349.         return multiply(rotation.toMatrix(), this);
  350.     }

  351.     /** Apply a rotation around the given center point to the current instance, returning the result
  352.      * as a new transform. This is achieved by translating the center point to the origin, applying
  353.      * the rotation, and then translating back.
  354.      * @param center the center of rotation
  355.      * @param rotation the rotation to apply
  356.      * @return a new transform containing the result of applying a rotation about the given center
  357.      *      point to the current instance
  358.      * @see QuaternionRotation#toMatrix()
  359.      */
  360.     public AffineTransformMatrix3D rotate(final Vector3D center, final QuaternionRotation rotation) {
  361.         return multiply(createRotation(center, rotation), this);
  362.     }

  363.     /** Get a new transform created by multiplying this instance by the argument.
  364.      * This is equivalent to the expression {@code A * M} where {@code A} is the
  365.      * current transform matrix and {@code M} is the given transform matrix. In
  366.      * terms of transformations, applying the returned matrix is equivalent to
  367.      * applying {@code M} and <em>then</em> applying {@code A}. In other words,
  368.      * the rightmost transform is applied first.
  369.      *
  370.      * @param m the transform to multiply with
  371.      * @return the result of multiplying the current instance by the given
  372.      *      transform matrix
  373.      */
  374.     public AffineTransformMatrix3D multiply(final AffineTransformMatrix3D m) {
  375.         return multiply(this, m);
  376.     }

  377.     /** Get a new transform created by multiplying the argument by this instance.
  378.      * This is equivalent to the expression {@code M * A} where {@code A} is the
  379.      * current transform matrix and {@code M} is the given transform matrix. In
  380.      * terms of transformations, applying the returned matrix is equivalent to
  381.      * applying {@code A} and <em>then</em> applying {@code M}. In other words,
  382.      * the rightmost transform is applied first.
  383.      *
  384.      * @param m the transform to multiply with
  385.      * @return the result of multiplying the given transform matrix by the current
  386.      *      instance
  387.      */
  388.     public AffineTransformMatrix3D premultiply(final AffineTransformMatrix3D m) {
  389.         return multiply(m, this);
  390.     }

  391.     /** {@inheritDoc}
  392.     *
  393.     * @throws IllegalStateException if the matrix cannot be inverted
  394.     */
  395.     @Override
  396.     public AffineTransformMatrix3D inverse() {

  397.         // Our full matrix is 4x4 but we can significantly reduce the amount of computations
  398.         // needed here since we know that our last row is [0 0 0 1].

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

  400.         // validate the remaining matrix elements that were not part of the determinant
  401.         Matrices.checkElementForInverse(m03);
  402.         Matrices.checkElementForInverse(m13);
  403.         Matrices.checkElementForInverse(m23);

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

  406.         final double invDet = 1.0 / det;

  407.         final double c00 = invDet * Matrices.determinant(m11, m12, m21, m22);
  408.         final double c01 = -invDet * Matrices.determinant(m10, m12, m20, m22);
  409.         final double c02 = invDet * Matrices.determinant(m10, m11, m20, m21);

  410.         final double c10 = -invDet * Matrices.determinant(m01, m02, m21, m22);
  411.         final double c11 = invDet * Matrices.determinant(m00, m02, m20, m22);
  412.         final double c12 = -invDet * Matrices.determinant(m00, m01, m20, m21);

  413.         final double c20 = invDet * Matrices.determinant(m01, m02, m11, m12);
  414.         final double c21 = -invDet * Matrices.determinant(m00, m02, m10, m12);
  415.         final double c22 = invDet * Matrices.determinant(m00, m01, m10, m11);

  416.         final double c30 = -invDet * Matrices.determinant(
  417.                     m01, m02, m03,
  418.                     m11, m12, m13,
  419.                     m21, m22, m23
  420.                 );
  421.         final double c31 = invDet * Matrices.determinant(
  422.                     m00, m02, m03,
  423.                     m10, m12, m13,
  424.                     m20, m22, m23
  425.                 );
  426.         final double c32 = -invDet * Matrices.determinant(
  427.                     m00, m01, m03,
  428.                     m10, m11, m13,
  429.                     m20, m21, m23
  430.                 );

  431.         return new AffineTransformMatrix3D(
  432.                     c00, c10, c20, c30,
  433.                     c01, c11, c21, c31,
  434.                     c02, c12, c22, c32
  435.                 );
  436.     }

  437.     /** {@inheritDoc} */
  438.     @Override
  439.     public int hashCode() {
  440.         final int prime = 31;
  441.         int result = 1;

  442.         result = (result * prime) + (Double.hashCode(m00) - Double.hashCode(m01) +
  443.                 Double.hashCode(m02) - Double.hashCode(m03));
  444.         result = (result * prime) + (Double.hashCode(m10) - Double.hashCode(m11) +
  445.                 Double.hashCode(m12) - Double.hashCode(m13));
  446.         result = (result * prime) + (Double.hashCode(m20) - Double.hashCode(m21) +
  447.                 Double.hashCode(m22) - Double.hashCode(m23));

  448.         return result;
  449.     }

  450.     /**
  451.      * Return true if the given object is an instance of {@link AffineTransformMatrix3D}
  452.      * and all matrix element values are exactly equal.
  453.      * @param obj object to test for equality with the current instance
  454.      * @return true if all transform matrix elements are exactly equal; otherwise false
  455.      */
  456.     @Override
  457.     public boolean equals(final Object obj) {
  458.         if (this == obj) {
  459.             return true;
  460.         }
  461.         if (!(obj instanceof AffineTransformMatrix3D)) {
  462.             return false;
  463.         }

  464.         final AffineTransformMatrix3D other = (AffineTransformMatrix3D) obj;

  465.         return Double.compare(this.m00, other.m00) == 0 &&
  466.                 Double.compare(this.m01, other.m01) == 0 &&
  467.                 Double.compare(this.m02, other.m02) == 0 &&
  468.                 Double.compare(this.m03, other.m03) == 0 &&

  469.                 Double.compare(this.m10, other.m10) == 0 &&
  470.                 Double.compare(this.m11, other.m11) == 0 &&
  471.                 Double.compare(this.m12, other.m12) == 0 &&
  472.                 Double.compare(this.m13, other.m13) == 0 &&

  473.                 Double.compare(this.m20, other.m20) == 0 &&
  474.                 Double.compare(this.m21, other.m21) == 0 &&
  475.                 Double.compare(this.m22, other.m22) == 0 &&
  476.                 Double.compare(this.m23, other.m23) == 0;
  477.     }

  478.     /** {@inheritDoc} */
  479.     @Override
  480.     public String toString() {
  481.         final StringBuilder sb = new StringBuilder();

  482.         sb.append(MATRIX_START)

  483.             .append(m00)
  484.             .append(ELEMENT_SEPARATOR)
  485.             .append(m01)
  486.             .append(ELEMENT_SEPARATOR)
  487.             .append(m02)
  488.             .append(ELEMENT_SEPARATOR)
  489.             .append(m03)
  490.             .append(ROW_SEPARATOR)

  491.             .append(m10)
  492.             .append(ELEMENT_SEPARATOR)
  493.             .append(m11)
  494.             .append(ELEMENT_SEPARATOR)
  495.             .append(m12)
  496.             .append(ELEMENT_SEPARATOR)
  497.             .append(m13)
  498.             .append(ROW_SEPARATOR)

  499.             .append(m20)
  500.             .append(ELEMENT_SEPARATOR)
  501.             .append(m21)
  502.             .append(ELEMENT_SEPARATOR)
  503.             .append(m22)
  504.             .append(ELEMENT_SEPARATOR)
  505.             .append(m23)

  506.             .append(MATRIX_END);

  507.         return sb.toString();
  508.     }

  509.     /** Multiplies the given vector by the 3x3 linear transformation matrix contained in the
  510.      * upper-right corner of the affine transformation matrix. This applies all transformation
  511.      * operations except for translations. The computed coordinates are passed to the given
  512.      * factory function.
  513.      * @param <T> factory output type
  514.      * @param vec the vector to transform
  515.      * @param factory the factory instance that will be passed the transformed coordinates
  516.      * @return the factory return value
  517.      */
  518.     private <T> T applyVector(final Vector3D vec, final DoubleFunction3N<T> factory) {
  519.         final double x = vec.getX();
  520.         final double y = vec.getY();
  521.         final double z = vec.getZ();

  522.         return factory.apply(
  523.                 applyVectorX(x, y, z),
  524.                 applyVectorY(x, y, z),
  525.                 applyVectorZ(x, y, z));
  526.     }

  527.     /** Get a new transform with the given matrix elements. The array must contain 12 elements.
  528.      * @param arr 12-element array containing values for the variable entries in the
  529.      *      transform matrix
  530.      * @return a new transform initialized with the given matrix values
  531.      * @throws IllegalArgumentException if the array does not have 12 elements
  532.      */
  533.     public static AffineTransformMatrix3D of(final double... arr) {
  534.         if (arr.length != NUM_ELEMENTS) {
  535.             throw new IllegalArgumentException("Dimension mismatch: " + arr.length + " != " + NUM_ELEMENTS);
  536.         }

  537.         return new AffineTransformMatrix3D(
  538.                     arr[0], arr[1], arr[2], arr[3],
  539.                     arr[4], arr[5], arr[6], arr[7],
  540.                     arr[8], arr[9], arr[10], arr[11]
  541.                 );
  542.     }

  543.     /** Construct a new transform representing the given function. The function is sampled at
  544.      * the origin and along each axis and a matrix is created to perform the transformation.
  545.      * @param fn function to create a transform matrix from
  546.      * @return a transform matrix representing the given function
  547.      * @throws IllegalArgumentException if the given function does not represent a valid
  548.      *      affine transform
  549.      */
  550.     public static AffineTransformMatrix3D from(final UnaryOperator<Vector3D> fn) {
  551.         final Vector3D tPlusX = fn.apply(Vector3D.Unit.PLUS_X);
  552.         final Vector3D tPlusY = fn.apply(Vector3D.Unit.PLUS_Y);
  553.         final Vector3D tPlusZ = fn.apply(Vector3D.Unit.PLUS_Z);
  554.         final Vector3D tZero = fn.apply(Vector3D.ZERO);

  555.         final Vector3D u = tPlusX.subtract(tZero);
  556.         final Vector3D v = tPlusY.subtract(tZero);
  557.         final Vector3D w = tPlusZ.subtract(tZero);

  558.         final AffineTransformMatrix3D mat =  AffineTransformMatrix3D.fromColumnVectors(u, v, w, tZero);

  559.         final double det = mat.determinant();
  560.         if (!Vectors.isRealNonZero(det)) {
  561.             throw new IllegalArgumentException("Transform function is invalid: matrix determinant is " + det);
  562.         }

  563.         return mat;
  564.     }

  565.     /** Get a new transform create from the given column vectors. The returned transform
  566.      * does not include any translation component.
  567.      * @param u first column vector; this corresponds to the first basis vector
  568.      *      in the coordinate frame
  569.      * @param v second column vector; this corresponds to the second basis vector
  570.      *      in the coordinate frame
  571.      * @param w third column vector; this corresponds to the third basis vector
  572.      *      in the coordinate frame
  573.      * @return a new transform with the given column vectors
  574.      */
  575.     public static AffineTransformMatrix3D fromColumnVectors(final Vector3D u, final Vector3D v, final Vector3D w) {
  576.         return fromColumnVectors(u, v, w, Vector3D.ZERO);
  577.     }

  578.     /** Get a new transform created from the given column vectors.
  579.      * @param u first column vector; this corresponds to the first basis vector
  580.      *      in the coordinate frame
  581.      * @param v second column vector; this corresponds to the second basis vector
  582.      *      in the coordinate frame
  583.      * @param w third column vector; this corresponds to the third basis vector
  584.      *      in the coordinate frame
  585.      * @param t fourth column vector; this corresponds to the translation of the transform
  586.      * @return a new transform with the given column vectors
  587.      */
  588.     public static AffineTransformMatrix3D fromColumnVectors(final Vector3D u, final Vector3D v,
  589.             final Vector3D w, final Vector3D t) {

  590.         return new AffineTransformMatrix3D(
  591.                     u.getX(), v.getX(), w.getX(), t.getX(),
  592.                     u.getY(), v.getY(), w.getY(), t.getY(),
  593.                     u.getZ(), v.getZ(), w.getZ(), t.getZ()
  594.                 );
  595.     }

  596.     /** Get the transform representing the identity matrix. This transform does not
  597.      * modify point or vector values when applied.
  598.      * @return transform representing the identity matrix
  599.      */
  600.     public static AffineTransformMatrix3D identity() {
  601.         return IDENTITY_INSTANCE;
  602.     }

  603.     /** Create a transform representing the given translation.
  604.      * @param translation vector containing translation values for each axis
  605.      * @return a new transform representing the given translation
  606.      */
  607.     public static AffineTransformMatrix3D createTranslation(final Vector3D translation) {
  608.         return createTranslation(translation.getX(), translation.getY(), translation.getZ());
  609.     }

  610.     /** Create a transform representing the given translation.
  611.      * @param x translation in the x direction
  612.      * @param y translation in the y direction
  613.      * @param z translation in the z direction
  614.      * @return a new transform representing the given translation
  615.      */
  616.     public static AffineTransformMatrix3D createTranslation(final double x, final double y, final double z) {
  617.         return new AffineTransformMatrix3D(
  618.                     1, 0, 0, x,
  619.                     0, 1, 0, y,
  620.                     0, 0, 1, z
  621.                 );
  622.     }

  623.     /** Create a transform representing a scale operation with the given scale factor applied to all axes.
  624.      * @param factor scale factor to apply to all axes
  625.      * @return a new transform representing a uniform scaling in all axes
  626.      */
  627.     public static AffineTransformMatrix3D createScale(final double factor) {
  628.         return createScale(factor, factor, factor);
  629.     }

  630.     /** Create a transform representing a scale operation.
  631.      * @param factors vector containing scale factors for each axis
  632.      * @return a new transform representing a scale operation
  633.      */
  634.     public static AffineTransformMatrix3D createScale(final Vector3D factors) {
  635.         return createScale(factors.getX(), factors.getY(), factors.getZ());
  636.     }

  637.     /** Create a transform representing a scale operation.
  638.      * @param x scale factor for the x axis
  639.      * @param y scale factor for the y axis
  640.      * @param z scale factor for the z axis
  641.      * @return a new transform representing a scale operation
  642.      */
  643.     public static AffineTransformMatrix3D createScale(final double x, final double y, final double z) {
  644.         return new AffineTransformMatrix3D(
  645.                     x, 0, 0, 0,
  646.                     0, y, 0, 0,
  647.                     0, 0, z, 0
  648.                 );
  649.     }

  650.     /** Create a transform representing a rotation about the given center point. This is achieved by translating
  651.      * the center to the origin, applying the rotation, and then translating back.
  652.      * @param center the center of rotation
  653.      * @param rotation the rotation to apply
  654.      * @return a new transform representing a rotation about the given center point
  655.      * @see QuaternionRotation#toMatrix()
  656.      */
  657.     public static AffineTransformMatrix3D createRotation(final Vector3D center, final QuaternionRotation rotation) {
  658.         return createTranslation(center.negate())
  659.                 .rotate(rotation)
  660.                 .translate(center);
  661.     }

  662.     /** Multiply two transform matrices together and return the result as a new transform instance.
  663.      * @param a first transform
  664.      * @param b second transform
  665.      * @return the transform computed as {@code a x b}
  666.      */
  667.     private static AffineTransformMatrix3D multiply(final AffineTransformMatrix3D a,
  668.             final AffineTransformMatrix3D b) {

  669.         // calculate the matrix elements
  670.         final double c00 = Vectors.linearCombination(a.m00, b.m00, a.m01, b.m10, a.m02, b.m20);
  671.         final double c01 = Vectors.linearCombination(a.m00, b.m01, a.m01, b.m11, a.m02, b.m21);
  672.         final double c02 = Vectors.linearCombination(a.m00, b.m02, a.m01, b.m12, a.m02, b.m22);
  673.         final double c03 = Vectors.linearCombination(a.m00, b.m03, a.m01, b.m13, a.m02, b.m23) + a.m03;

  674.         final double c10 = Vectors.linearCombination(a.m10, b.m00, a.m11, b.m10, a.m12, b.m20);
  675.         final double c11 = Vectors.linearCombination(a.m10, b.m01, a.m11, b.m11, a.m12, b.m21);
  676.         final double c12 = Vectors.linearCombination(a.m10, b.m02, a.m11, b.m12, a.m12, b.m22);
  677.         final double c13 = Vectors.linearCombination(a.m10, b.m03, a.m11, b.m13, a.m12, b.m23) + a.m13;

  678.         final double c20 = Vectors.linearCombination(a.m20, b.m00, a.m21, b.m10, a.m22, b.m20);
  679.         final double c21 = Vectors.linearCombination(a.m20, b.m01, a.m21, b.m11, a.m22, b.m21);
  680.         final double c22 = Vectors.linearCombination(a.m20, b.m02, a.m21, b.m12, a.m22, b.m22);
  681.         final double c23 = Vectors.linearCombination(a.m20, b.m03, a.m21, b.m13, a.m22, b.m23) + a.m23;

  682.         return new AffineTransformMatrix3D(
  683.                     c00, c01, c02, c03,
  684.                     c10, c11, c12, c13,
  685.                     c20, c21, c22, c23
  686.                 );
  687.     }
  688. }