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.threed;
18  
19  import java.util.function.UnaryOperator;
20  
21  import org.apache.commons.geometry.core.internal.DoubleFunction3N;
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.threed.rotation.QuaternionRotation;
26  
27  /** Class using a matrix to represent affine transformations in 3 dimensional Euclidean space.
28   *
29   * <p>Instances of this class use a 4x4 matrix for all transform operations.
30   * The last row of this matrix is always set to the values <code>[0 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 12 elements, instead of 16.
33   * </p>
34   */
35  public final class AffineTransformMatrix3D extends AbstractAffineTransformMatrix<Vector3D, AffineTransformMatrix3D> {
36      /** The number of internal matrix elements. */
37      private static final int NUM_ELEMENTS = 12;
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 AffineTransformMatrix3D IDENTITY_INSTANCE = new AffineTransformMatrix3D(
53                  1, 0, 0, 0,
54                  0, 1, 0, 0,
55                  0, 0, 1, 0
56              );
57  
58      /** Transform matrix entry <code>m<sub>0,0</sub></code>. */
59      private final double m00;
60      /** Transform matrix entry <code>m<sub>0,1</sub></code>. */
61      private final double m01;
62      /** Transform matrix entry <code>m<sub>0,2</sub></code>. */
63      private final double m02;
64      /** Transform matrix entry <code>m<sub>0,3</sub></code>. */
65      private final double m03;
66  
67      /** Transform matrix entry <code>m<sub>1,0</sub></code>. */
68      private final double m10;
69      /** Transform matrix entry <code>m<sub>1,1</sub></code>. */
70      private final double m11;
71      /** Transform matrix entry <code>m<sub>1,2</sub></code>. */
72      private final double m12;
73      /** Transform matrix entry <code>m<sub>1,3</sub></code>. */
74      private final double m13;
75  
76      /** Transform matrix entry <code>m<sub>2,0</sub></code>. */
77      private final double m20;
78      /** Transform matrix entry <code>m<sub>2,1</sub></code>. */
79      private final double m21;
80      /** Transform matrix entry <code>m<sub>2,2</sub></code>. */
81      private final double m22;
82      /** Transform matrix entry <code>m<sub>2,3</sub></code>. */
83      private final double m23;
84  
85      /**
86       * Package-private constructor; sets all internal matrix elements.
87       * @param m00 matrix entry <code>m<sub>0,0</sub></code>
88       * @param m01 matrix entry <code>m<sub>0,1</sub></code>
89       * @param m02 matrix entry <code>m<sub>0,2</sub></code>
90       * @param m03 matrix entry <code>m<sub>0,3</sub></code>
91       * @param m10 matrix entry <code>m<sub>1,0</sub></code>
92       * @param m11 matrix entry <code>m<sub>1,1</sub></code>
93       * @param m12 matrix entry <code>m<sub>1,2</sub></code>
94       * @param m13 matrix entry <code>m<sub>1,3</sub></code>
95       * @param m20 matrix entry <code>m<sub>2,0</sub></code>
96       * @param m21 matrix entry <code>m<sub>2,1</sub></code>
97       * @param m22 matrix entry <code>m<sub>2,2</sub></code>
98       * @param m23 matrix entry <code>m<sub>2,3</sub></code>
99       */
100     private AffineTransformMatrix3D(
101             final double m00, final double m01, final double m02, final double m03,
102             final double m10, final double m11, final double m12, final double m13,
103             final double m20, final double m21, final double m22, final double m23) {
104 
105         this.m00 = m00;
106         this.m01 = m01;
107         this.m02 = m02;
108         this.m03 = m03;
109 
110         this.m10 = m10;
111         this.m11 = m11;
112         this.m12 = m12;
113         this.m13 = m13;
114 
115         this.m20 = m20;
116         this.m21 = m21;
117         this.m22 = m22;
118         this.m23 = m23;
119     }
120 
121     /** Return a 12 element array containing the variable elements from the
122      * internal transformation matrix. The elements are in row-major order.
123      * The array indices map to the internal matrix as follows:
124      * <pre>
125      *      [
126      *          arr[0],   arr[1],   arr[2],   arr[3]
127      *          arr[4],   arr[5],   arr[6],   arr[7],
128      *          arr[8],   arr[9],   arr[10],  arr[11],
129      *          0         0         0         1
130      *      ]
131      * </pre>
132      * @return 12 element array containing the variable elements from the
133      *      internal transformation matrix
134      */
135     public double[] toArray() {
136         return new double[] {
137             m00, m01, m02, m03,
138             m10, m11, m12, m13,
139             m20, m21, m22, m23
140         };
141     }
142 
143     /** Apply this transform to the given point, returning the result as a new instance.
144      *
145      * <p>The transformed point is computed by creating a 4-element column vector from the
146      * coordinates in the input and setting the last element to 1. This is then multiplied with the
147      * 4x4 transform matrix to produce the transformed point. The {@code 1} in the last position
148      * is ignored.
149      * <pre>
150      *      [ m00  m01  m02  m03 ]     [ x ]     [ x']
151      *      [ m10  m11  m12  m13 ]  *  [ y ]  =  [ y']
152      *      [ m20  m21  m22  m23 ]     [ z ]     [ z']
153      *      [ 0    0    0    1   ]     [ 1 ]     [ 1 ]
154      * </pre>
155      */
156     @Override
157     public Vector3D apply(final Vector3D pt) {
158         final double x = pt.getX();
159         final double y = pt.getY();
160         final double z = pt.getZ();
161 
162         return Vector3D.of(
163                 applyX(x, y, z),
164                 applyY(x, y, z),
165                 applyZ(x, y, z));
166     }
167 
168     /** Apply this transform to the given point coordinates and return the transformed
169      * x value. The return value is equal to
170      * <code>(x * m<sub>00</sub>) + (y * m<sub>01</sub>) + (z * m<sub>02</sub>) + m<sub>03</sub></code>.
171      * @param x x coordinate value
172      * @param y y coordinate value
173      * @param z z coordinate value
174      * @return transformed x coordinate value
175      * @see #apply(Vector3D)
176      */
177     public double applyX(final double x, final double y, final double z) {
178         return applyVectorX(x, y, z) + m03;
179     }
180 
181     /** Apply this transform to the given point coordinates and return the transformed
182      * y value. The return value is equal to
183      * <code>(x * m<sub>10</sub>) + (y * m<sub>11</sub>) + (z * m<sub>12</sub>) + m<sub>13</sub></code>.
184      * @param x x coordinate value
185      * @param y y coordinate value
186      * @param z z coordinate value
187      * @return transformed y coordinate value
188      * @see #apply(Vector3D)
189      */
190     public double applyY(final double x, final double y, final double z) {
191         return applyVectorY(x, y, z) + m13;
192     }
193 
194     /** Apply this transform to the given point coordinates and return the transformed
195      * z value. The return value is equal to
196      * <code>(x * m<sub>20</sub>) + (y * m<sub>21</sub>) + (z * m<sub>22</sub>) + m<sub>23</sub></code>.
197      * @param x x coordinate value
198      * @param y y coordinate value
199      * @param z z coordinate value
200      * @return transformed z coordinate value
201      * @see #apply(Vector3D)
202      */
203     public double applyZ(final double x, final double y, final double z) {
204         return applyVectorZ(x, y, z) + m23;
205     }
206 
207     /** {@inheritDoc}
208      *
209      *  <p>The transformed vector is computed by creating a 4-element column vector from the
210      * coordinates in the input and setting the last element to 0. This is then multiplied with the
211      * 4x4 transform matrix to produce the transformed vector. The {@code 0} in the last position
212      * is ignored.
213      * <pre>
214      *      [ m00  m01  m02  m03 ]     [ x ]     [ x']
215      *      [ m10  m11  m12  m13 ]  *  [ y ]  =  [ y']
216      *      [ m20  m21  m22  m23 ]     [ z ]     [ z']
217      *      [ 0    0    0    1   ]     [ 0 ]     [ 0 ]
218      * </pre>
219      *
220      * @see #applyDirection(Vector3D)
221      */
222     @Override
223     public Vector3D applyVector(final Vector3D vec) {
224         return applyVector(vec, Vector3D::of);
225     }
226 
227     /** Apply this transform to the given vector coordinates, ignoring translations, and
228      * return the transformed x value. The return value is equal to
229      * <code>(x * m<sub>00</sub>) + (y * m<sub>01</sub>) + (z * m<sub>02</sub>)</code>.
230      * @param x x coordinate value
231      * @param y y coordinate value
232      * @param z z coordinate value
233      * @return transformed x coordinate value
234      * @see #applyVector(Vector3D)
235      */
236     public double applyVectorX(final double x, final double y, final double z) {
237         return Vectors.linearCombination(m00, x, m01, y, m02, z);
238     }
239 
240     /** Apply this transform to the given vector coordinates, ignoring translations, and
241      * return the transformed y value. The return value is equal to
242      * <code>(x * m<sub>10</sub>) + (y * m<sub>11</sub>) + (z * m<sub>12</sub>)</code>.
243      * @param x x coordinate value
244      * @param y y coordinate value
245      * @param z z coordinate value
246      * @return transformed y coordinate value
247      * @see #applyVector(Vector3D)
248      */
249     public double applyVectorY(final double x, final double y, final double z) {
250         return Vectors.linearCombination(m10, x, m11, y, m12, z);
251     }
252 
253     /** Apply this transform to the given vector coordinates, ignoring translations, and
254      * return the transformed z value. The return value is equal to
255      * <code>(x * m<sub>20</sub>) + (y * m<sub>21</sub>) + (z * m<sub>22</sub>)</code>.
256      * @param x x coordinate value
257      * @param y y coordinate value
258      * @param z z coordinate value
259      * @return transformed z coordinate value
260      * @see #applyVector(Vector3D)
261      */
262     public double applyVectorZ(final double x, final double y, final double z) {
263         return Vectors.linearCombination(m20, x, m21, y, m22, z);
264     }
265 
266     /** {@inheritDoc}
267      * @see #applyVector(Vector3D)
268      */
269     @Override
270     public Vector3D.Unit applyDirection(final Vector3D vec) {
271         return applyVector(vec, Vector3D.Unit::from);
272     }
273 
274     /** {@inheritDoc} */
275     @Override
276     public double determinant() {
277         return Matrices.determinant(
278                 m00, m01, m02,
279                 m10, m11, m12,
280                 m20, m21, m22
281             );
282     }
283 
284     /** {@inheritDoc}
285      *
286      * <p><strong>Example</strong>
287      * <pre>
288      *      [ a, b, c, d ]   [ a, b, c, 0 ]
289      *      [ e, f, g, h ]   [ e, f, g, 0 ]
290      *      [ i, j, k, l ] &rarr; [ i, j, k, 0 ]
291      *      [ 0, 0, 0, 1 ]   [ 0, 0, 0, 1 ]
292      * </pre>
293      */
294     @Override
295     public AffineTransformMatrix3D linear() {
296         return new AffineTransformMatrix3D(
297                 m00, m01, m02, 0.0,
298                 m10, m11, m12, 0.0,
299                 m20, m21, m22, 0.0);
300     }
301 
302     /** {@inheritDoc}
303      *
304      * <p><strong>Example</strong>
305      * <pre>
306      *      [ a, b, c, d ]   [ a, e, i, 0 ]
307      *      [ e, f, g, h ]   [ b, f, j, 0 ]
308      *      [ i, j, k, l ] &rarr; [ c, g, k, 0 ]
309      *      [ 0, 0, 0, 1 ]   [ 0, 0, 0, 1 ]
310      * </pre>
311      */
312     @Override
313     public AffineTransformMatrix3D linearTranspose() {
314         return new AffineTransformMatrix3D(
315                 m00, m10, m20, 0.0,
316                 m01, m11, m21, 0.0,
317                 m02, m12, m22, 0.0);
318     }
319 
320     /** Apply a translation to the current instance, returning the result as a new transform.
321      * @param translation vector containing the translation values for each axis
322      * @return a new transform containing the result of applying a translation to
323      *      the current instance
324      */
325     public AffineTransformMatrix3D translate(final Vector3D translation) {
326         return translate(translation.getX(), translation.getY(), translation.getZ());
327     }
328 
329     /** Apply a translation to the current instance, returning the result as a new transform.
330      * @param x translation in the x direction
331      * @param y translation in the y direction
332      * @param z translation in the z direction
333      * @return a new transform containing the result of applying a translation to
334      *      the current instance
335      */
336     public AffineTransformMatrix3D translate(final double x, final double y, final double z) {
337         return new AffineTransformMatrix3D(
338                     m00, m01, m02, m03 + x,
339                     m10, m11, m12, m13 + y,
340                     m20, m21, m22, m23 + z
341                 );
342     }
343 
344     /** Apply a scale operation to the current instance, returning the result as a new transform.
345      * @param factor the scale factor to apply to all axes
346      * @return a new transform containing the result of applying a scale operation to
347      *      the current instance
348      */
349     public AffineTransformMatrix3D scale(final double factor) {
350         return scale(factor, factor, factor);
351     }
352 
353     /** Apply a scale operation to the current instance, returning the result as a new transform.
354      * @param scaleFactors vector containing scale factors for each axis
355      * @return a new transform containing the result of applying a scale operation to
356      *      the current instance
357      */
358     public AffineTransformMatrix3D scale(final Vector3D scaleFactors) {
359         return scale(scaleFactors.getX(), scaleFactors.getY(), scaleFactors.getZ());
360     }
361 
362     /** Apply a scale operation to the current instance, returning the result as a new transform.
363      * @param x scale factor for the x axis
364      * @param y scale factor for the y axis
365      * @param z scale factor for the z axis
366      * @return a new transform containing the result of applying a scale operation to
367      *      the current instance
368      */
369     public AffineTransformMatrix3D scale(final double x, final double y, final double z) {
370         return new AffineTransformMatrix3D(
371                     m00 * x, m01 * x, m02 * x, m03 * x,
372                     m10 * y, m11 * y, m12 * y, m13 * y,
373                     m20 * z, m21 * z, m22 * z, m23 * z
374                 );
375     }
376 
377     /** Apply a rotation to the current instance, returning the result as a new transform.
378      * @param rotation the rotation to apply
379      * @return a new transform containing the result of applying a rotation to the
380      *      current instance
381      * @see QuaternionRotation#toMatrix()
382      */
383     public AffineTransformMatrix3D rotate(final QuaternionRotation rotation) {
384         return multiply(rotation.toMatrix(), this);
385     }
386 
387     /** Apply a rotation around the given center point to the current instance, returning the result
388      * as a new transform. This is achieved by translating the center point to the origin, applying
389      * the rotation, and then translating back.
390      * @param center the center of rotation
391      * @param rotation the rotation to apply
392      * @return a new transform containing the result of applying a rotation about the given center
393      *      point to the current instance
394      * @see QuaternionRotation#toMatrix()
395      */
396     public AffineTransformMatrix3D rotate(final Vector3D center, final QuaternionRotation rotation) {
397         return multiply(createRotation(center, rotation), this);
398     }
399 
400     /** Get a new transform created by multiplying this instance by the argument.
401      * This is equivalent to the expression {@code A * M} where {@code A} is the
402      * current transform matrix and {@code M} is the given transform matrix. In
403      * terms of transformations, applying the returned matrix is equivalent to
404      * applying {@code M} and <em>then</em> applying {@code A}. In other words,
405      * the rightmost transform is applied first.
406      *
407      * @param m the transform to multiply with
408      * @return the result of multiplying the current instance by the given
409      *      transform matrix
410      */
411     public AffineTransformMatrix3D multiply(final AffineTransformMatrix3D m) {
412         return multiply(this, m);
413     }
414 
415     /** Get a new transform created by multiplying the argument by this instance.
416      * This is equivalent to the expression {@code M * A} where {@code A} is the
417      * current transform matrix and {@code M} is the given transform matrix. In
418      * terms of transformations, applying the returned matrix is equivalent to
419      * applying {@code A} and <em>then</em> applying {@code M}. In other words,
420      * the rightmost transform is applied first.
421      *
422      * @param m the transform to multiply with
423      * @return the result of multiplying the given transform matrix by the current
424      *      instance
425      */
426     public AffineTransformMatrix3D premultiply(final AffineTransformMatrix3D m) {
427         return multiply(m, this);
428     }
429 
430     /** {@inheritDoc}
431     *
432     * @throws IllegalStateException if the matrix cannot be inverted
433     */
434     @Override
435     public AffineTransformMatrix3D inverse() {
436 
437         // Our full matrix is 4x4 but we can significantly reduce the amount of computations
438         // needed here since we know that our last row is [0 0 0 1].
439 
440         final double det = Matrices.checkDeterminantForInverse(determinant());
441 
442         // validate the remaining matrix elements that were not part of the determinant
443         Matrices.checkElementForInverse(m03);
444         Matrices.checkElementForInverse(m13);
445         Matrices.checkElementForInverse(m23);
446 
447         // compute the necessary elements of the cofactor matrix
448         // (we need all but the last column)
449 
450         final double invDet = 1.0 / det;
451 
452         final double c00 = invDet * Matrices.determinant(m11, m12, m21, m22);
453         final double c01 = -invDet * Matrices.determinant(m10, m12, m20, m22);
454         final double c02 = invDet * Matrices.determinant(m10, m11, m20, m21);
455 
456         final double c10 = -invDet * Matrices.determinant(m01, m02, m21, m22);
457         final double c11 = invDet * Matrices.determinant(m00, m02, m20, m22);
458         final double c12 = -invDet * Matrices.determinant(m00, m01, m20, m21);
459 
460         final double c20 = invDet * Matrices.determinant(m01, m02, m11, m12);
461         final double c21 = -invDet * Matrices.determinant(m00, m02, m10, m12);
462         final double c22 = invDet * Matrices.determinant(m00, m01, m10, m11);
463 
464         final double c30 = -invDet * Matrices.determinant(
465                     m01, m02, m03,
466                     m11, m12, m13,
467                     m21, m22, m23
468                 );
469         final double c31 = invDet * Matrices.determinant(
470                     m00, m02, m03,
471                     m10, m12, m13,
472                     m20, m22, m23
473                 );
474         final double c32 = -invDet * Matrices.determinant(
475                     m00, m01, m03,
476                     m10, m11, m13,
477                     m20, m21, m23
478                 );
479 
480         return new AffineTransformMatrix3D(
481                     c00, c10, c20, c30,
482                     c01, c11, c21, c31,
483                     c02, c12, c22, c32
484                 );
485     }
486 
487     /** {@inheritDoc} */
488     @Override
489     public int hashCode() {
490         final int prime = 31;
491         int result = 1;
492 
493         result = (result * prime) + (Double.hashCode(m00) - Double.hashCode(m01) +
494                 Double.hashCode(m02) - Double.hashCode(m03));
495         result = (result * prime) + (Double.hashCode(m10) - Double.hashCode(m11) +
496                 Double.hashCode(m12) - Double.hashCode(m13));
497         result = (result * prime) + (Double.hashCode(m20) - Double.hashCode(m21) +
498                 Double.hashCode(m22) - Double.hashCode(m23));
499 
500         return result;
501     }
502 
503     /**
504      * Return true if the given object is an instance of {@link AffineTransformMatrix3D}
505      * and all matrix element values are exactly equal.
506      * @param obj object to test for equality with the current instance
507      * @return true if all transform matrix elements are exactly equal; otherwise false
508      */
509     @Override
510     public boolean equals(final Object obj) {
511         if (this == obj) {
512             return true;
513         }
514         if (!(obj instanceof AffineTransformMatrix3D)) {
515             return false;
516         }
517 
518         final AffineTransformMatrix3D other = (AffineTransformMatrix3D) obj;
519 
520         return Double.compare(this.m00, other.m00) == 0 &&
521                 Double.compare(this.m01, other.m01) == 0 &&
522                 Double.compare(this.m02, other.m02) == 0 &&
523                 Double.compare(this.m03, other.m03) == 0 &&
524 
525                 Double.compare(this.m10, other.m10) == 0 &&
526                 Double.compare(this.m11, other.m11) == 0 &&
527                 Double.compare(this.m12, other.m12) == 0 &&
528                 Double.compare(this.m13, other.m13) == 0 &&
529 
530                 Double.compare(this.m20, other.m20) == 0 &&
531                 Double.compare(this.m21, other.m21) == 0 &&
532                 Double.compare(this.m22, other.m22) == 0 &&
533                 Double.compare(this.m23, other.m23) == 0;
534     }
535 
536     /** {@inheritDoc} */
537     @Override
538     public String toString() {
539         final StringBuilder sb = new StringBuilder();
540 
541         sb.append(MATRIX_START)
542 
543             .append(m00)
544             .append(ELEMENT_SEPARATOR)
545             .append(m01)
546             .append(ELEMENT_SEPARATOR)
547             .append(m02)
548             .append(ELEMENT_SEPARATOR)
549             .append(m03)
550             .append(ROW_SEPARATOR)
551 
552             .append(m10)
553             .append(ELEMENT_SEPARATOR)
554             .append(m11)
555             .append(ELEMENT_SEPARATOR)
556             .append(m12)
557             .append(ELEMENT_SEPARATOR)
558             .append(m13)
559             .append(ROW_SEPARATOR)
560 
561             .append(m20)
562             .append(ELEMENT_SEPARATOR)
563             .append(m21)
564             .append(ELEMENT_SEPARATOR)
565             .append(m22)
566             .append(ELEMENT_SEPARATOR)
567             .append(m23)
568 
569             .append(MATRIX_END);
570 
571         return sb.toString();
572     }
573 
574     /** Multiplies the given vector by the 3x3 linear transformation matrix contained in the
575      * upper-right corner of the affine transformation matrix. This applies all transformation
576      * operations except for translations. The computed coordinates are passed to the given
577      * factory function.
578      * @param <T> factory output type
579      * @param vec the vector to transform
580      * @param factory the factory instance that will be passed the transformed coordinates
581      * @return the factory return value
582      */
583     private <T> T applyVector(final Vector3D vec, final DoubleFunction3N<T> factory) {
584         final double x = vec.getX();
585         final double y = vec.getY();
586         final double z = vec.getZ();
587 
588         return factory.apply(
589                 applyVectorX(x, y, z),
590                 applyVectorY(x, y, z),
591                 applyVectorZ(x, y, z));
592     }
593 
594     /** Get a new transform with the given matrix elements. The array must contain 12 elements.
595      * @param arr 12-element array containing values for the variable entries in the
596      *      transform matrix
597      * @return a new transform initialized with the given matrix values
598      * @throws IllegalArgumentException if the array does not have 12 elements
599      */
600     public static AffineTransformMatrix3D of(final double... arr) {
601         if (arr.length != NUM_ELEMENTS) {
602             throw new IllegalArgumentException("Dimension mismatch: " + arr.length + " != " + NUM_ELEMENTS);
603         }
604 
605         return new AffineTransformMatrix3D(
606                     arr[0], arr[1], arr[2], arr[3],
607                     arr[4], arr[5], arr[6], arr[7],
608                     arr[8], arr[9], arr[10], arr[11]
609                 );
610     }
611 
612     /** Construct a new transform representing the given function. The function is sampled at
613      * the origin and along each axis and a matrix is created to perform the transformation.
614      * @param fn function to create a transform matrix from
615      * @return a transform matrix representing the given function
616      * @throws IllegalArgumentException if the given function does not represent a valid
617      *      affine transform
618      */
619     public static AffineTransformMatrix3D from(final UnaryOperator<Vector3D> fn) {
620         final Vector3D tPlusX = fn.apply(Vector3D.Unit.PLUS_X);
621         final Vector3D tPlusY = fn.apply(Vector3D.Unit.PLUS_Y);
622         final Vector3D tPlusZ = fn.apply(Vector3D.Unit.PLUS_Z);
623         final Vector3D tZero = fn.apply(Vector3D.ZERO);
624 
625         final Vector3D u = tPlusX.subtract(tZero);
626         final Vector3D v = tPlusY.subtract(tZero);
627         final Vector3D w = tPlusZ.subtract(tZero);
628 
629         final AffineTransformMatrix3D mat =  AffineTransformMatrix3D.fromColumnVectors(u, v, w, tZero);
630 
631         final double det = mat.determinant();
632         if (!Vectors.isRealNonZero(det)) {
633             throw new IllegalArgumentException("Transform function is invalid: matrix determinant is " + det);
634         }
635 
636         return mat;
637     }
638 
639     /** Get a new transform create from the given column vectors. The returned transform
640      * does not include any translation component.
641      * @param u first column vector; this corresponds to the first basis vector
642      *      in the coordinate frame
643      * @param v second column vector; this corresponds to the second basis vector
644      *      in the coordinate frame
645      * @param w third column vector; this corresponds to the third basis vector
646      *      in the coordinate frame
647      * @return a new transform with the given column vectors
648      */
649     public static AffineTransformMatrix3D fromColumnVectors(final Vector3D u, final Vector3D v, final Vector3D w) {
650         return fromColumnVectors(u, v, w, Vector3D.ZERO);
651     }
652 
653     /** Get a new transform created from the given column vectors.
654      * @param u first column vector; this corresponds to the first basis vector
655      *      in the coordinate frame
656      * @param v second column vector; this corresponds to the second basis vector
657      *      in the coordinate frame
658      * @param w third column vector; this corresponds to the third basis vector
659      *      in the coordinate frame
660      * @param t fourth column vector; this corresponds to the translation of the transform
661      * @return a new transform with the given column vectors
662      */
663     public static AffineTransformMatrix3D fromColumnVectors(final Vector3D u, final Vector3D v,
664             final Vector3D w, final Vector3D t) {
665 
666         return new AffineTransformMatrix3D(
667                     u.getX(), v.getX(), w.getX(), t.getX(),
668                     u.getY(), v.getY(), w.getY(), t.getY(),
669                     u.getZ(), v.getZ(), w.getZ(), t.getZ()
670                 );
671     }
672 
673     /** Get the transform representing the identity matrix. This transform does not
674      * modify point or vector values when applied.
675      * @return transform representing the identity matrix
676      */
677     public static AffineTransformMatrix3D identity() {
678         return IDENTITY_INSTANCE;
679     }
680 
681     /** Create a transform representing the given translation.
682      * @param translation vector containing translation values for each axis
683      * @return a new transform representing the given translation
684      */
685     public static AffineTransformMatrix3D createTranslation(final Vector3D translation) {
686         return createTranslation(translation.getX(), translation.getY(), translation.getZ());
687     }
688 
689     /** Create a transform representing the given translation.
690      * @param x translation in the x direction
691      * @param y translation in the y direction
692      * @param z translation in the z direction
693      * @return a new transform representing the given translation
694      */
695     public static AffineTransformMatrix3D createTranslation(final double x, final double y, final double z) {
696         return new AffineTransformMatrix3D(
697                     1, 0, 0, x,
698                     0, 1, 0, y,
699                     0, 0, 1, z
700                 );
701     }
702 
703     /** Create a transform representing a scale operation with the given scale factor applied to all axes.
704      * @param factor scale factor to apply to all axes
705      * @return a new transform representing a uniform scaling in all axes
706      */
707     public static AffineTransformMatrix3D createScale(final double factor) {
708         return createScale(factor, factor, factor);
709     }
710 
711     /** Create a transform representing a scale operation.
712      * @param factors vector containing scale factors for each axis
713      * @return a new transform representing a scale operation
714      */
715     public static AffineTransformMatrix3D createScale(final Vector3D factors) {
716         return createScale(factors.getX(), factors.getY(), factors.getZ());
717     }
718 
719     /** Create a transform representing a scale operation.
720      * @param x scale factor for the x axis
721      * @param y scale factor for the y axis
722      * @param z scale factor for the z axis
723      * @return a new transform representing a scale operation
724      */
725     public static AffineTransformMatrix3D createScale(final double x, final double y, final double z) {
726         return new AffineTransformMatrix3D(
727                     x, 0, 0, 0,
728                     0, y, 0, 0,
729                     0, 0, z, 0
730                 );
731     }
732 
733     /** Create a transform representing a rotation about the given center point. This is achieved by translating
734      * the center to the origin, applying the rotation, and then translating back.
735      * @param center the center of rotation
736      * @param rotation the rotation to apply
737      * @return a new transform representing a rotation about the given center point
738      * @see QuaternionRotation#toMatrix()
739      */
740     public static AffineTransformMatrix3D createRotation(final Vector3D center, final QuaternionRotation rotation) {
741         return createTranslation(center.negate())
742                 .rotate(rotation)
743                 .translate(center);
744     }
745 
746     /** Multiply two transform matrices together and return the result as a new transform instance.
747      * @param a first transform
748      * @param b second transform
749      * @return the transform computed as {@code a x b}
750      */
751     private static AffineTransformMatrix3D multiply(final AffineTransformMatrix3D a,
752             final AffineTransformMatrix3D b) {
753 
754         // calculate the matrix elements
755         final double c00 = Vectors.linearCombination(a.m00, b.m00, a.m01, b.m10, a.m02, b.m20);
756         final double c01 = Vectors.linearCombination(a.m00, b.m01, a.m01, b.m11, a.m02, b.m21);
757         final double c02 = Vectors.linearCombination(a.m00, b.m02, a.m01, b.m12, a.m02, b.m22);
758         final double c03 = Vectors.linearCombination(a.m00, b.m03, a.m01, b.m13, a.m02, b.m23) + a.m03;
759 
760         final double c10 = Vectors.linearCombination(a.m10, b.m00, a.m11, b.m10, a.m12, b.m20);
761         final double c11 = Vectors.linearCombination(a.m10, b.m01, a.m11, b.m11, a.m12, b.m21);
762         final double c12 = Vectors.linearCombination(a.m10, b.m02, a.m11, b.m12, a.m12, b.m22);
763         final double c13 = Vectors.linearCombination(a.m10, b.m03, a.m11, b.m13, a.m12, b.m23) + a.m13;
764 
765         final double c20 = Vectors.linearCombination(a.m20, b.m00, a.m21, b.m10, a.m22, b.m20);
766         final double c21 = Vectors.linearCombination(a.m20, b.m01, a.m21, b.m11, a.m22, b.m21);
767         final double c22 = Vectors.linearCombination(a.m20, b.m02, a.m21, b.m12, a.m22, b.m22);
768         final double c23 = Vectors.linearCombination(a.m20, b.m03, a.m21, b.m13, a.m22, b.m23) + a.m23;
769 
770         return new AffineTransformMatrix3D(
771                     c00, c01, c02, c03,
772                     c10, c11, c12, c13,
773                     c20, c21, c22, c23
774                 );
775     }
776 }