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