1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.geometry.euclidean.twod;
18
19 import java.util.function.UnaryOperator;
20
21 import org.apache.commons.geometry.core.internal.DoubleFunction2N;
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.twod.rotation.Rotation2D;
26 import org.apache.commons.numbers.arrays.LinearCombination;
27
28 /** Class using a matrix to represent affine transformations in 2 dimensional Euclidean space.
29 *
30 * <p>Instances of this class use a 3x3 matrix for all transform operations.
31 * The last row of this matrix is always set to the values <code>[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 6 elements, instead of 9.
34 * </p>
35 */
36 public final class AffineTransformMatrix2D extends AbstractAffineTransformMatrix<Vector2D, AffineTransformMatrix2D> {
37 /** The number of internal matrix elements. */
38 private static final int NUM_ELEMENTS = 6;
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 AffineTransformMatrix2D IDENTITY_INSTANCE = new AffineTransformMatrix2D(
54 1, 0, 0,
55 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
65 /** Transform matrix entry <code>m<sub>1,0</sub></code>. */
66 private final double m10;
67 /** Transform matrix entry <code>m<sub>1,1</sub></code>. */
68 private final double m11;
69 /** Transform matrix entry <code>m<sub>1,2</sub></code>. */
70 private final double m12;
71
72 /**
73 * Simple constructor; sets all internal matrix elements.
74 * @param m00 matrix entry <code>m<sub>0,0</sub></code>
75 * @param m01 matrix entry <code>m<sub>0,1</sub></code>
76 * @param m02 matrix entry <code>m<sub>0,2</sub></code>
77 * @param m10 matrix entry <code>m<sub>1,0</sub></code>
78 * @param m11 matrix entry <code>m<sub>1,1</sub></code>
79 * @param m12 matrix entry <code>m<sub>1,2</sub></code>
80 */
81 private AffineTransformMatrix2D(
82 final double m00, final double m01, final double m02,
83 final double m10, final double m11, final double m12) {
84
85 this.m00 = m00;
86 this.m01 = m01;
87 this.m02 = m02;
88
89 this.m10 = m10;
90 this.m11 = m11;
91 this.m12 = m12;
92 }
93
94 /** Return a 6 element array containing the variable elements from the
95 * internal transformation matrix. The elements are in row-major order.
96 * The array indices map to the internal matrix as follows:
97 * <pre>
98 * [
99 * arr[0], arr[1], arr[2],
100 * arr[3], arr[4], arr[5],
101 * 0 0 1
102 * ]
103 * </pre>
104 * @return 6 element array containing the variable elements from the
105 * internal transformation matrix
106 */
107 public double[] toArray() {
108 return new double[] {
109 m00, m01, m02,
110 m10, m11, m12
111 };
112 }
113
114 /** Apply this transform to the given point, returning the result as a new instance.
115 *
116 * <p>The transformed point is computed by creating a 3-element column vector from the
117 * coordinates in the input and setting the last element to 1. This is then multiplied with the
118 * 3x3 transform matrix to produce the transformed point. The {@code 1} in the last position
119 * is ignored.
120 * <pre>
121 * [ m00 m01 m02 ] [ x ] [ x']
122 * [ m10 m11 m12 ] * [ y ] = [ y']
123 * [ 0 0 1 ] [ 1 ] [ 1 ]
124 * </pre>
125 */
126 @Override
127 public Vector2D apply(final Vector2D pt) {
128 final double x = pt.getX();
129 final double y = pt.getY();
130
131 final double resultX = LinearCombination.value(m00, x, m01, y) + m02;
132 final double resultY = LinearCombination.value(m10, x, m11, y) + m12;
133
134 return Vector2D.of(resultX, resultY);
135 }
136
137 /** {@inheritDoc}
138 *
139 * <p>The transformed vector is computed by creating a 3-element column vector from the
140 * coordinates in the input and setting the last element to 0. This is then multiplied with the
141 * 3x3 transform matrix to produce the transformed vector. The {@code 0} in the last position
142 * is ignored.
143 * <pre>
144 * [ m00 m01 m02 ] [ x ] [ x']
145 * [ m10 m11 m12 ] * [ y ] = [ y']
146 * [ 0 0 1 ] [ 0 ] [ 0 ]
147 * </pre>
148 *
149 * @see #applyDirection(Vector2D)
150 */
151 @Override
152 public Vector2D applyVector(final Vector2D vec) {
153 return applyVector(vec, Vector2D::of);
154 }
155
156 /** {@inheritDoc}
157 * @see #applyVector(Vector2D)
158 */
159 @Override
160 public Vector2D.Unit applyDirection(final Vector2D vec) {
161 return applyVector(vec, Vector2D.Unit::from);
162 }
163
164 /** {@inheritDoc} */
165 @Override
166 public double determinant() {
167 return Matrices.determinant(
168 m00, m01,
169 m10, m11
170 );
171 }
172
173 /** {@inheritDoc}
174 *
175 * <p><strong>Example</strong>
176 * <pre>
177 * [ a, b, c ] [ a, b, 0 ]
178 * [ d, e, f ] → [ d, e, 0 ]
179 * [ 0, 0, 1 ] [ 0, 0, 1 ]
180 * </pre>
181 */
182 @Override
183 public AffineTransformMatrix2D linear() {
184 return new AffineTransformMatrix2D(
185 m00, m01, 0.0,
186 m10, m11, 0.0);
187 }
188
189 /** {@inheritDoc}
190 *
191 * <p><strong>Example</strong>
192 * <pre>
193 * [ a, b, c ] [ a, d, 0 ]
194 * [ d, e, f ] → [ b, e, 0 ]
195 * [ 0, 0, 1 ] [ 0, 0, 1 ]
196 * </pre>
197 */
198 @Override
199 public AffineTransformMatrix2D linearTranspose() {
200 return new AffineTransformMatrix2D(
201 m00, m10, 0.0,
202 m01, m11, 0.0);
203 }
204
205 /** Apply a translation to the current instance, returning the result as a new transform.
206 * @param translation vector containing the translation values for each axis
207 * @return a new transform containing the result of applying a translation to
208 * the current instance
209 */
210 public AffineTransformMatrix2D translate(final Vector2D translation) {
211 return translate(translation.getX(), translation.getY());
212 }
213
214 /** Apply a translation to the current instance, returning the result as a new transform.
215 * @param x translation in the x direction
216 * @param y translation in the y direction
217 * @return a new transform containing the result of applying a translation to
218 * the current instance
219 */
220 public AffineTransformMatrix2D translate(final double x, final double y) {
221 return new AffineTransformMatrix2D(
222 m00, m01, m02 + x,
223 m10, m11, m12 + y
224 );
225 }
226
227 /** Apply a scale operation to the current instance, returning the result as a new transform.
228 * @param factor the scale factor to apply to all axes
229 * @return a new transform containing the result of applying a scale operation to
230 * the current instance
231 */
232 public AffineTransformMatrix2D scale(final double factor) {
233 return scale(factor, factor);
234 }
235
236 /** Apply a scale operation to the current instance, returning the result as a new transform.
237 * @param scaleFactors vector containing scale factors for each axis
238 * @return a new transform containing the result of applying a scale operation to
239 * the current instance
240 */
241 public AffineTransformMatrix2D scale(final Vector2D scaleFactors) {
242 return scale(scaleFactors.getX(), scaleFactors.getY());
243 }
244
245 /** Apply a scale operation to the current instance, returning the result as a new transform.
246 * @param x scale factor for the x axis
247 * @param y scale factor for the y axis
248 * @return a new transform containing the result of applying a scale operation to
249 * the current instance
250 */
251 public AffineTransformMatrix2D scale(final double x, final double y) {
252 return new AffineTransformMatrix2D(
253 m00 * x, m01 * x, m02 * x,
254 m10 * y, m11 * y, m12 * y
255 );
256 }
257
258 /** Apply a <em>counterclockwise</em> rotation to the current instance, returning the result as a
259 * new transform.
260 * @param angle the angle of counterclockwise rotation in radians
261 * @return a new transform containing the result of applying a rotation to the
262 * current instance
263 * @see Rotation2D#of(double)
264 */
265 public AffineTransformMatrix2D rotate(final double angle) {
266 return rotate(Rotation2D.of(angle));
267 }
268
269 /** Apply a <em>counterclockwise</em> rotation to the current instance, returning the result as a
270 * new transform.
271 * @param rotation the rotation to apply
272 * @return a new transform containing the result of applying the rotation to the
273 * current instance
274 */
275 public AffineTransformMatrix2D rotate(final Rotation2D rotation) {
276 return multiply(rotation.toMatrix(), this);
277 }
278
279 /** Apply a <em>counterclockwise</em> rotation about the given center point to the current instance,
280 * returning the result as a new transform. This is accomplished by translating the center to the origin,
281 * applying the rotation, and then translating back.
282 * @param center the center of rotation
283 * @param angle the angle of counterclockwise rotation in radians
284 * @return a new transform containing the result of applying a rotation about the given
285 * center point to the current instance
286 */
287 public AffineTransformMatrix2D rotate(final Vector2D center, final double angle) {
288 return multiply(createRotation(center, angle), this);
289 }
290
291 /** Apply a <em>counterclockwise</em> rotation about the given center point to the current instance,
292 * returning the result as a new transform. This is accomplished by translating the center to the origin,
293 * applying the rotation, and then translating back.
294 * @param center the center of rotation
295 * @param rotation the rotation to apply
296 * @return a new transform containing the result of applying a rotation about the given
297 * center point to the current instance
298 */
299 public AffineTransformMatrix2D rotate(final Vector2D center, final Rotation2D rotation) {
300 // use to raw angle method to avoid matrix multiplication
301 return rotate(center, rotation.getAngle());
302 }
303
304 /** Get a new transform created by multiplying this instance by the argument.
305 * This is equivalent to the expression {@code A * M} where {@code A} is the
306 * current transform matrix and {@code M} is the given transform matrix. In
307 * terms of transformations, applying the returned matrix is equivalent to
308 * applying {@code M} and <em>then</em> applying {@code A}. In other words,
309 * the rightmost transform is applied first.
310 *
311 * @param m the transform to multiply with
312 * @return the result of multiplying the current instance by the given
313 * transform matrix
314 */
315 public AffineTransformMatrix2D multiply(final AffineTransformMatrix2D m) {
316 return multiply(this, m);
317 }
318
319 /** Get a new transform created by multiplying the argument by this instance.
320 * This is equivalent to the expression {@code M * A} where {@code A} is the
321 * current transform matrix and {@code M} is the given transform matrix. In
322 * terms of transformations, applying the returned matrix is equivalent to
323 * applying {@code A} and <em>then</em> applying {@code M}. In other words,
324 * the rightmost transform is applied first.
325 *
326 * @param m the transform to multiply with
327 * @return the result of multiplying the given transform matrix by the current
328 * instance
329 */
330 public AffineTransformMatrix2D premultiply(final AffineTransformMatrix2D m) {
331 return multiply(m, this);
332 }
333
334 /** {@inheritDoc}
335 *
336 * @throws IllegalStateException if the matrix cannot be inverted
337 */
338 @Override
339 public AffineTransformMatrix2D inverse() {
340
341 // Our full matrix is 3x3 but we can significantly reduce the amount of computations
342 // needed here since we know that our last row is [0 0 1].
343
344 final double det = Matrices.checkDeterminantForInverse(determinant());
345
346 // validate the remaining matrix elements that were not part of the determinant
347 Matrices.checkElementForInverse(m02);
348 Matrices.checkElementForInverse(m12);
349
350 // compute the necessary elements of the cofactor matrix
351 // (we need all but the last column)
352
353 final double invDet = 1.0 / det;
354
355 final double c00 = invDet * m11;
356 final double c01 = -invDet * m10;
357
358 final double c10 = -invDet * m01;
359 final double c11 = invDet * m00;
360
361 final double c20 = invDet * Matrices.determinant(m01, m02, m11, m12);
362 final double c21 = -invDet * Matrices.determinant(m00, m02, m10, m12);
363
364 return new AffineTransformMatrix2D(
365 c00, c10, c20,
366 c01, c11, c21
367 );
368 }
369
370 /** {@inheritDoc} */
371 @Override
372 public int hashCode() {
373 final int prime = 31;
374 int result = 1;
375
376 result = (result * prime) + (Double.hashCode(m00) - Double.hashCode(m01) + Double.hashCode(m02));
377 result = (result * prime) + (Double.hashCode(m10) - Double.hashCode(m11) + Double.hashCode(m12));
378
379 return result;
380 }
381
382 /**
383 * Return true if the given object is an instance of {@link AffineTransformMatrix2D}
384 * and all matrix element values are exactly equal.
385 * @param obj object to test for equality with the current instance
386 * @return true if all transform matrix elements are exactly equal; otherwise false
387 */
388 @Override
389 public boolean equals(final Object obj) {
390 if (this == obj) {
391 return true;
392 }
393 if (!(obj instanceof AffineTransformMatrix2D)) {
394 return false;
395 }
396
397 final AffineTransformMatrix2D other = (AffineTransformMatrix2D) obj;
398
399 return Double.compare(this.m00, other.m00) == 0 &&
400 Double.compare(this.m01, other.m01) == 0 &&
401 Double.compare(this.m02, other.m02) == 0 &&
402
403 Double.compare(this.m10, other.m10) == 0 &&
404 Double.compare(this.m11, other.m11) == 0 &&
405 Double.compare(this.m12, other.m12) == 0;
406 }
407
408 /** {@inheritDoc} */
409 @Override
410 public String toString() {
411 final StringBuilder sb = new StringBuilder();
412
413 sb.append(MATRIX_START)
414
415 .append(m00)
416 .append(ELEMENT_SEPARATOR)
417 .append(m01)
418 .append(ELEMENT_SEPARATOR)
419 .append(m02)
420 .append(ROW_SEPARATOR)
421
422 .append(m10)
423 .append(ELEMENT_SEPARATOR)
424 .append(m11)
425 .append(ELEMENT_SEPARATOR)
426 .append(m12)
427
428 .append(MATRIX_END);
429
430 return sb.toString();
431 }
432
433 /** Multiplies the given vector by the 2x2 linear transformation matrix contained in the
434 * upper-right corner of the affine transformation matrix. This applies all transformation
435 * operations except for translations. The computed coordinates are passed to the given
436 * factory function.
437 * @param <T> factory output type
438 * @param vec the vector to transform
439 * @param factory the factory instance that will be passed the transformed coordinates
440 * @return the factory return value
441 */
442 private <T> T applyVector(final Vector2D vec, final DoubleFunction2N<T> factory) {
443 final double x = vec.getX();
444 final double y = vec.getY();
445
446 final double resultX = LinearCombination.value(m00, x, m01, y);
447 final double resultY = LinearCombination.value(m10, x, m11, y);
448
449 return factory.apply(resultX, resultY);
450 }
451
452 /** Get a new transform with the given matrix elements. The array must contain 6 elements.
453 * @param arr 6-element array containing values for the variable entries in the
454 * transform matrix
455 * @return a new transform initialized with the given matrix values
456 * @throws IllegalArgumentException if the array does not have 6 elements
457 */
458 public static AffineTransformMatrix2D of(final double... arr) {
459 if (arr.length != NUM_ELEMENTS) {
460 throw new IllegalArgumentException("Dimension mismatch: " + arr.length + " != " + NUM_ELEMENTS);
461 }
462
463 return new AffineTransformMatrix2D(
464 arr[0], arr[1], arr[2],
465 arr[3], arr[4], arr[5]
466 );
467 }
468
469 /** Construct a new transform representing the given function. The function is sampled at
470 * the origin and along each axis and a matrix is created to perform the transformation.
471 * @param fn function to create a transform matrix from
472 * @return a transform matrix representing the given function
473 * @throws IllegalArgumentException if the given function does not represent a valid
474 * affine transform
475 */
476 public static AffineTransformMatrix2D from(final UnaryOperator<Vector2D> fn) {
477 final Vector2D tPlusX = fn.apply(Vector2D.Unit.PLUS_X);
478 final Vector2D tPlusY = fn.apply(Vector2D.Unit.PLUS_Y);
479 final Vector2D tZero = fn.apply(Vector2D.ZERO);
480
481 final Vector2D u = tPlusX.subtract(tZero);
482 final Vector2D v = tPlusY.subtract(tZero);
483
484 final AffineTransformMatrix2D mat = AffineTransformMatrix2D.fromColumnVectors(u, v, tZero);
485
486 final double det = mat.determinant();
487 if (!Vectors.isRealNonZero(det)) {
488 throw new IllegalArgumentException("Transform function is invalid: matrix determinant is " + det);
489 }
490
491 return mat;
492 }
493
494 /** Get a new transform create from the given column vectors. The returned transform
495 * does not include any translation component.
496 * @param u first column vector; this corresponds to the first basis vector
497 * in the coordinate frame
498 * @param v second column vector; this corresponds to the second basis vector
499 * in the coordinate frame
500 * @return a new transform with the given column vectors
501 */
502 public static AffineTransformMatrix2D fromColumnVectors(final Vector2D u, final Vector2D v) {
503 return fromColumnVectors(u, v, Vector2D.ZERO);
504 }
505
506 /** Get a new transform created from the given column vectors.
507 * @param u first column vector; this corresponds to the first basis vector
508 * in the coordinate frame
509 * @param v second column vector; this corresponds to the second basis vector
510 * in the coordinate frame
511 * @param t third column vector; this corresponds to the translation of the transform
512 * @return a new transform with the given column vectors
513 */
514 public static AffineTransformMatrix2D fromColumnVectors(final Vector2D u, final Vector2D v, final Vector2D t) {
515 return new AffineTransformMatrix2D(
516 u.getX(), v.getX(), t.getX(),
517 u.getY(), v.getY(), t.getY()
518 );
519 }
520
521 /** Get the transform representing the identity matrix. This transform does not
522 * modify point or vector values when applied.
523 * @return transform representing the identity matrix
524 */
525 public static AffineTransformMatrix2D identity() {
526 return IDENTITY_INSTANCE;
527 }
528
529 /** Create a transform representing the given translation.
530 * @param translation vector containing translation values for each axis
531 * @return a new transform representing the given translation
532 */
533 public static AffineTransformMatrix2D createTranslation(final Vector2D translation) {
534 return createTranslation(translation.getX(), translation.getY());
535 }
536
537 /** Create a transform representing the given translation.
538 * @param x translation in the x direction
539 * @param y translation in the y direction
540 * @return a new transform representing the given translation
541 */
542 public static AffineTransformMatrix2D createTranslation(final double x, final double y) {
543 return new AffineTransformMatrix2D(
544 1, 0, x,
545 0, 1, y
546 );
547 }
548
549 /** Create a transform representing a scale operation with the given scale factor applied to all axes.
550 * @param factor scale factor to apply to all axes
551 * @return a new transform representing a uniform scaling in all axes
552 */
553 public static AffineTransformMatrix2D createScale(final double factor) {
554 return createScale(factor, factor);
555 }
556
557 /** Create a transform representing a scale operation.
558 * @param factors vector containing scale factors for each axis
559 * @return a new transform representing a scale operation
560 */
561 public static AffineTransformMatrix2D createScale(final Vector2D factors) {
562 return createScale(factors.getX(), factors.getY());
563 }
564
565 /** Create a transform representing a scale operation.
566 * @param x scale factor for the x axis
567 * @param y scale factor for the y axis
568 * @return a new transform representing a scale operation
569 */
570 public static AffineTransformMatrix2D createScale(final double x, final double y) {
571 return new AffineTransformMatrix2D(
572 x, 0, 0,
573 0, y, 0
574 );
575 }
576
577 /** Create a transform representing a <em>counterclockwise</em> rotation of {@code angle}
578 * radians around the origin.
579 * @param angle the angle of rotation in radians
580 * @return a new transform representing the rotation
581 * @see Rotation2D#toMatrix()
582 */
583 public static AffineTransformMatrix2D createRotation(final double angle) {
584 return Rotation2D.of(angle).toMatrix();
585 }
586
587 /** Create a transform representing a <em>counterclockwise</em> rotation of {@code angle}
588 * radians around the given center point. This is accomplished by translating the center point
589 * to the origin, applying the rotation, and then translating back.
590 * @param center the center of rotation
591 * @param angle the angle of rotation in radians
592 * @return a new transform representing the rotation about the given center
593 */
594 public static AffineTransformMatrix2D createRotation(final Vector2D center, final double angle) {
595 // it's possible to do this using Rotation2D to create the rotation matrix but we
596 // can avoid the matrix multiplications by simply doing everything in-line here
597 final double x = center.getX();
598 final double y = center.getY();
599
600 final double sin = Math.sin(angle);
601 final double cos = Math.cos(angle);
602
603 return new AffineTransformMatrix2D(
604 cos, -sin, (-x * cos) + (y * sin) + x,
605 sin, cos, (-x * sin) - (y * cos) + y
606 );
607 }
608
609 /** Create a transform representing a <em>counterclockwise</em> rotation around the given center point.
610 * This is accomplished by translating the center point to the origin, applying the rotation, and then
611 * translating back.
612 * @param center the center of rotation
613 * @param rotation the rotation to apply
614 * @return a new transform representing the rotation about the given center
615 */
616 public static AffineTransformMatrix2D createRotation(final Vector2D center, final Rotation2D rotation) {
617 return createRotation(center, rotation.getAngle());
618 }
619
620 /** Multiply two transform matrices together.
621 * @param a first transform
622 * @param b second transform
623 * @return the transform computed as {@code a x b}
624 */
625 private static AffineTransformMatrix2D multiply(final AffineTransformMatrix2D a,
626 final AffineTransformMatrix2D b) {
627
628 final double c00 = LinearCombination.value(a.m00, b.m00, a.m01, b.m10);
629 final double c01 = LinearCombination.value(a.m00, b.m01, a.m01, b.m11);
630 final double c02 = LinearCombination.value(a.m00, b.m02, a.m01, b.m12) + a.m02;
631
632 final double c10 = LinearCombination.value(a.m10, b.m00, a.m11, b.m10);
633 final double c11 = LinearCombination.value(a.m10, b.m01, a.m11, b.m11);
634 final double c12 = LinearCombination.value(a.m10, b.m02, a.m11, b.m12) + a.m12;
635
636 return new AffineTransformMatrix2D(
637 c00, c01, c02,
638 c10, c11, c12
639 );
640 }
641 }