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 ] → [ 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 ] → [ 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 }