1 /*
2 * Copyright (C) 2012 Alberto Irurueta Carro (alberto@irurueta.com)
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.irurueta.geometry;
17
18 import com.irurueta.algebra.ArrayUtils;
19 import com.irurueta.algebra.Matrix;
20 import com.irurueta.algebra.Utils;
21 import com.irurueta.algebra.WrongSizeException;
22 import com.irurueta.geometry.estimators.EuclideanTransformation2DEstimator;
23 import com.irurueta.geometry.estimators.LockedException;
24 import com.irurueta.geometry.estimators.NotReadyException;
25
26 import java.io.Serializable;
27 import java.util.ArrayList;
28
29 /**
30 * This class performs Euclidean transformations on 2D space.
31 * Euclidean transformations include transformations related to rotations and
32 * translations.
33 * Scale cannot be modified on Euclidean scale.
34 */
35 @SuppressWarnings("DuplicatedCode")
36 public class EuclideanTransformation2D extends Transformation2D implements Serializable {
37
38 /**
39 * Constant indicating number of coordinates required in translation arrays.
40 */
41 public static final int NUM_TRANSLATION_COORDS = 2;
42
43 /**
44 * Constant defining number of homogeneous coordinates in 2D space.
45 */
46 public static final int HOM_COORDS = 3;
47
48 /**
49 * 2D rotation to be performed on geometric objects.
50 */
51 private Rotation2D rotation;
52
53 /**
54 * 2D translation to be performed on geometric objects.
55 * Translation is specified using inhomogeneous coordinates.
56 */
57 private double[] translation;
58
59 /**
60 * Empty constructor.
61 * Creates transformation that has no effect.
62 */
63 public EuclideanTransformation2D() {
64 rotation = new Rotation2D();
65 translation = new double[NUM_TRANSLATION_COORDS];
66 }
67
68 /**
69 * Creates transformation with provided rotation.
70 *
71 * @param rotation a 2D rotation.
72 * @throws NullPointerException raised if provided rotation is null.
73 */
74 public EuclideanTransformation2D(final Rotation2D rotation) {
75 if (rotation == null) {
76 throw new NullPointerException();
77 }
78
79 this.rotation = rotation;
80 translation = new double[NUM_TRANSLATION_COORDS];
81 }
82
83 /**
84 * Creates transformation with provided 2D translation.
85 *
86 * @param translation Array indicating 2D translation using inhomogeneous
87 * coordinates.
88 * @throws NullPointerException raised if provided array is null.
89 * @throws IllegalArgumentException raised if length of array is not equal
90 * to NUM_TRANSLATION_COORDS.
91 */
92 public EuclideanTransformation2D(final double[] translation) {
93 if (translation.length != NUM_TRANSLATION_COORDS) {
94 throw new IllegalArgumentException();
95 }
96
97 rotation = new Rotation2D();
98 this.translation = translation;
99 }
100
101 /**
102 * Creates transformation with provided 2D rotation and translation.
103 *
104 * @param rotation a 2D rotation.
105 * @param translation array indicating 2D translation using inhomogeneous
106 * coordinates.
107 * @throws NullPointerException raised if provided array is null.
108 * @throws IllegalArgumentException Raised if length of array is not equal
109 * to NUM_TRANSLATION_COORDS.
110 */
111 public EuclideanTransformation2D(final Rotation2D rotation, final double[] translation) {
112 if (rotation == null) {
113 throw new NullPointerException();
114 }
115 if (translation.length != NUM_TRANSLATION_COORDS) {
116 throw new IllegalArgumentException();
117 }
118
119 this.rotation = rotation;
120 this.translation = translation;
121 }
122
123 /**
124 * Creates transformation by estimating its internal values using provided 3
125 * corresponding original and transformed points.
126 *
127 * @param inputPoint1 1st input point.
128 * @param inputPoint2 2nd input point.
129 * @param inputPoint3 3rd input point.
130 * @param outputPoint1 1st transformed point corresponding to 1st input
131 * point.
132 * @param outputPoint2 2nd transformed point corresponding to 2nd input
133 * point.
134 * @param outputPoint3 3rd transformed point corresponding to 3rd input
135 * point.
136 * @throws CoincidentPointsException raised if transformation cannot be
137 * estimated for some reason (point configuration degeneracy, duplicate
138 * points or numerical instabilities).
139 */
140 public EuclideanTransformation2D(
141 final Point2D inputPoint1, final Point2D inputPoint2, final Point2D inputPoint3, final Point2D outputPoint1,
142 final Point2D outputPoint2, final Point2D outputPoint3) throws CoincidentPointsException {
143 internalSetTransformationFromPoints(inputPoint1, inputPoint2, inputPoint3, outputPoint1, outputPoint2,
144 outputPoint3);
145 }
146
147 /**
148 * Returns 2D rotation assigned to this transformation.
149 *
150 * @return 2D rotation.
151 */
152 public Rotation2D getRotation() {
153 return rotation;
154 }
155
156 /**
157 * Sets 2D rotation for this transformation.
158 *
159 * @param rotation a 2D rotation.
160 * @throws NullPointerException raised if provided rotation is null.
161 */
162 public void setRotation(final Rotation2D rotation) {
163 if (rotation == null) {
164 throw new NullPointerException();
165 }
166 this.rotation = rotation;
167 }
168
169 /**
170 * Adds provided rotation to current rotation assigned to this
171 * transformation.
172 *
173 * @param rotation 2D rotation to be added.
174 */
175 public void addRotation(final Rotation2D rotation) {
176 this.rotation.combine(rotation);
177 }
178
179 /**
180 * Returns 2D translation assigned to this transformation as an array
181 * expressed in inhomogeneous coordinates.
182 *
183 * @return 2D translation array.
184 */
185 public double[] getTranslation() {
186 return translation;
187 }
188
189 /**
190 * Sets 2D translation assigned to this transformation as an array expressed
191 * in inhomogeneous coordinates.
192 *
193 * @param translation 2D translation array.
194 * @throws IllegalArgumentException Raised if provided array does not have
195 * length equal to NUM_TRANSLATION_COORDS.
196 */
197 public void setTranslation(final double[] translation) {
198 if (translation.length != NUM_TRANSLATION_COORDS) {
199 throw new IllegalArgumentException();
200 }
201
202 this.translation = translation;
203 }
204
205 /**
206 * Adds provided translation to current translation on this transformation.
207 * Provided translation must be expressed as an array of inhomogeneous
208 * coordinates.
209 *
210 * @param translation 2D translation array.
211 * @throws IllegalArgumentException Raised if provided array does not have
212 * length equal to NUM_TRANSLATION_COORDS.
213 */
214 public void addTranslation(final double[] translation) {
215 ArrayUtils.sum(this.translation, translation, this.translation);
216 }
217
218 /**
219 * Returns current x coordinate translation assigned to this transformation.
220 *
221 * @return X coordinate translation.
222 */
223 public double getTranslationX() {
224 return translation[0];
225 }
226
227 /**
228 * Sets x coordinate translation to be made by this transformation.
229 *
230 * @param translationX X coordinate translation to be set.
231 */
232 public void setTranslationX(final double translationX) {
233 translation[0] = translationX;
234 }
235
236 /**
237 * Returns current y coordinate translation assigned to this transformation.
238 *
239 * @return Y coordinate translation.
240 */
241 public double getTranslationY() {
242 return translation[1];
243 }
244
245 /**
246 * Sets y coordinate translation to be made by this transformation.
247 *
248 * @param translationY Y coordinate translation to be set.
249 */
250 public void setTranslationY(final double translationY) {
251 translation[1] = translationY;
252 }
253
254 /**
255 * Sets x, y coordinates of translation to be made by this transformation.
256 *
257 * @param translationX translation x coordinate to be set.
258 * @param translationY translation y coordinate to be set.
259 */
260 public void setTranslation(final double translationX, final double translationY) {
261 translation[0] = translationX;
262 translation[1] = translationY;
263 }
264
265 /**
266 * Sets x, y coordinates of translation to be made by this transformation.
267 *
268 * @param translation translation to be set.
269 */
270 public void setTranslation(final Point2D translation) {
271 setTranslation(translation.getInhomX(), translation.getInhomY());
272 }
273
274 /**
275 * Gets x, y coordinates of translation to be made by this transformation
276 * as a new point.
277 *
278 * @return a new point containing translation coordinates.
279 */
280 public Point2D getTranslationPoint() {
281 final var out = Point2D.create();
282 getTranslationPoint(out);
283 return out;
284 }
285
286 /**
287 * Gets x, y coordinates of translation to be made by this transformation
288 * and stores them into provided point.
289 *
290 * @param out point where translation coordinates will be stored.
291 */
292 public void getTranslationPoint(final Point2D out) {
293 out.setInhomogeneousCoordinates(translation[0], translation[1]);
294 }
295
296 /**
297 * Adds provided x coordinate to current translation assigned to this
298 * transformation.
299 *
300 * @param translationX X coordinate to be added to current translation.
301 */
302 public void addTranslationX(final double translationX) {
303 translation[0] += translationX;
304 }
305
306 /**
307 * Adds provided y coordinate to current translation assigned to this
308 * transformation.
309 *
310 * @param translationY Y coordinate to be added to current translation.
311 */
312 public void addTranslationY(final double translationY) {
313 translation[1] += translationY;
314 }
315
316 /**
317 * Adds provided coordinates to current translation assigned to this
318 * transformation.
319 *
320 * @param translationX x coordinate to be added to current translation.
321 * @param translationY y coordinate to be added to current translation.
322 */
323 public void addTranslation(final double translationX, final double translationY) {
324 translation[0] += translationX;
325 translation[1] += translationY;
326 }
327
328 /**
329 * Adds provided coordinates to current translation assigned to this
330 * transformation.
331 *
332 * @param translation x, y coordinates to be added to current translation.
333 */
334 public void addTranslation(final Point2D translation) {
335 addTranslation(translation.getInhomX(), translation.getInhomY());
336 }
337
338 /**
339 * Represents this transformation as a 3x3 matrix.
340 * A point can be transformed as T * p, where T is the transformation matrix
341 * and p is a point expressed as an homogeneous vector.
342 *
343 * @return This transformation in matrix form.
344 */
345 @Override
346 public Matrix asMatrix() {
347 Matrix m = null;
348 try {
349 m = new Matrix(HOM_COORDS, HOM_COORDS);
350 asMatrix(m);
351 } catch (final WrongSizeException ignore) {
352 // never happens
353 }
354 return m;
355 }
356
357 /**
358 * Represents this transformation as a 3x3 matrix and stores the result in
359 * provided instance.
360 *
361 * @param m instance where transformation matrix will be stored.
362 * @throws IllegalArgumentException raised if provided instance is not a 3x3
363 * matrix.
364 */
365 @Override
366 public void asMatrix(final Matrix m) {
367 if (m.getRows() != HOM_COORDS || m.getColumns() != HOM_COORDS) {
368 throw new IllegalArgumentException();
369 }
370
371 m.initialize(0.0);
372
373 // set rotation
374 m.setSubmatrix(0, 0, Rotation2D.ROTATION2D_INHOM_MATRIX_ROWS - 1,
375 Rotation2D.ROTATION2D_INHOM_MATRIX_COLS - 1, rotation.asInhomogeneousMatrix());
376
377 // set translation
378 m.setSubmatrix(0, HOM_COORDS - 1, translation.length - 1,
379 HOM_COORDS - 1, translation);
380
381 // set last element
382 m.setElementAt(HOM_COORDS - 1, HOM_COORDS - 1, 1.0);
383 }
384
385 /**
386 * Transforms input point using this transformation and stores the result in
387 * provided output points.
388 *
389 * @param inputPoint point to be transformed.
390 * @param outputPoint instance where transformed point data will be stored.
391 */
392 @Override
393 public void transform(final Point2D inputPoint, final Point2D outputPoint) {
394 inputPoint.normalize();
395 rotation.rotate(inputPoint, outputPoint);
396 outputPoint.setInhomogeneousCoordinates(outputPoint.getInhomX() + translation[0],
397 outputPoint.getInhomY() + translation[1]);
398 }
399
400 /**
401 * Transforms a conic using this transformation and stores the result into
402 * provided output conic.
403 *
404 * @param inputConic conic to be transformed.
405 * @param outputConic instance where data of transformed conic will be
406 * stored.
407 * @throws NonSymmetricMatrixException raised if due to numerical precision
408 * the resulting output conic matrix is not considered to be symmetric.
409 */
410 @Override
411 public void transform(final Conic inputConic, final Conic outputConic) throws NonSymmetricMatrixException {
412 // point' * conic * point = 0
413 // point' * T' * transformedConic * T * point = 0
414 // where:
415 // - transformedPoint = T * point
416
417 // Hence:
418 // transformedConic = T^-1' * conic * T^-1
419
420 inputConic.normalize();
421
422 final var c = inputConic.asMatrix();
423 final var invT = inverseAndReturnNew().asMatrix();
424 // normalize transformation matrix T to increase accuracy
425 var norm = Utils.normF(invT);
426 invT.multiplyByScalar(1.0 / norm);
427
428 final var m = invT.transposeAndReturnNew();
429 try {
430 m.multiply(c);
431 m.multiply(invT);
432 } catch (final WrongSizeException ignore) {
433 // never happens
434 }
435
436 // normalize resulting m matrix to increase accuracy so that it can be
437 // considered symmetric
438 norm = Utils.normF(m);
439 m.multiplyByScalar(1.0 / norm);
440
441 outputConic.setParameters(m);
442 }
443
444 /**
445 * Transforms a dual conic using this transformation and stores the result
446 * into provided output dual conic.
447 *
448 * @param inputDualConic dual conic to be transformed.
449 * @param outputDualConic instance where data of transformed dual conic will
450 * be stored.
451 * @throws NonSymmetricMatrixException raised if due to numerical precision
452 * the resulting output dual conic matrix is not considered to be symmetric.
453 */
454 @Override
455 public void transform(final DualConic inputDualConic, final DualConic outputDualConic)
456 throws NonSymmetricMatrixException {
457 // line' * dualConic * line = 0
458 // line' * T^-1 * T * dualConic * T' * T^-1'* line
459
460 // Hence:
461 // transformed plane: T^-1'* line
462 // transformed dual quadric: T * dualQuadric * T'
463
464 inputDualConic.normalize();
465
466 final var dualC = inputDualConic.asMatrix();
467 final var t = asMatrix();
468 // normalize transformation matrix T to increase accuracy
469 var norm = Utils.normF(t);
470 t.multiplyByScalar(1.0 / norm);
471
472 final var transT = t.transposeAndReturnNew();
473 try {
474 t.multiply(dualC);
475 t.multiply(transT);
476 } catch (final WrongSizeException ignore) {
477 // never happens
478 }
479
480 // normalize resulting m matrix to increase accuracy so that it can be
481 // considered symmetric
482 norm = Utils.normF(t);
483 t.multiplyByScalar(1.0 / norm);
484
485 outputDualConic.setParameters(t);
486 }
487
488 /**
489 * Transforms provided input line using this transformation and stores the
490 * result into provided output line instance.
491 *
492 * @param inputLine line to be transformed.
493 * @param outputLine instance where data of transformed line will be stored.
494 */
495 @Override
496 public void transform(final Line2D inputLine, final Line2D outputLine) {
497 // line' * point = 0 --> line' * T^-1 * T * point
498 // (line' * T^-1)*(T*point) = (T^-1'*line)'*(T*point)
499 // where:
500 // - transformedLine = T^-1'*line
501 // - transformedPoint = T*point
502
503 inputLine.normalize();
504
505 final var invT = inverseAndReturnNew().asMatrix();
506 final var l = Matrix.newFromArray(inputLine.asArray());
507
508 // normalize transformation matrix T to increase accuracy
509 final var norm = Utils.normF(invT);
510 invT.multiplyByScalar(1.0 / norm);
511
512 invT.transpose();
513 try {
514 invT.multiply(l);
515 } catch (final WrongSizeException ignore) {
516 // never happens
517 }
518
519 outputLine.setParameters(invT.toArray());
520 }
521
522 /**
523 * Converts this transformation into a metric transformation.
524 *
525 * @return this transformation converted into a metric transformation.
526 */
527 public MetricTransformation2D toMetric() {
528 return new MetricTransformation2D(rotation, translation, MetricTransformation2D.DEFAULT_SCALE);
529 }
530
531 /**
532 * Inverses this transformation.
533 */
534 public void inverse() {
535 inverse(this);
536 }
537
538 /**
539 * Computes the inverse of this transformation and returns the result as a
540 * new transformation instance.
541 *
542 * @return inverse transformation.
543 */
544 public Transformation2D inverseAndReturnNew() {
545 final var result = new EuclideanTransformation2D();
546 inverse(result);
547 return result;
548 }
549
550 /**
551 * Combines this transformation with provided transformation.
552 * The combination is equivalent to multiplying the matrix of this
553 * transformation with the matrix of provided transformation.
554 *
555 * @param transformation transformation to be combined with.
556 */
557 public void combine(final EuclideanTransformation2D transformation) {
558 combine(transformation, this);
559 }
560
561 /**
562 * Combines this transformation with provided transformation and returns
563 * the result as a new transformation instance.
564 * The combination is equivalent to multiplying the matrix of this
565 * transformation with the matrix of provided transformation.
566 *
567 * @param transformation transformation to be combined with.
568 * @return a new transformation resulting of the combination with this
569 * transformation and provided transformation.
570 */
571 public EuclideanTransformation2D combineAndReturnNew(final EuclideanTransformation2D transformation) {
572
573 final var result = new EuclideanTransformation2D();
574 combine(transformation, result);
575 return result;
576 }
577
578 /**
579 * Estimates this transformation internal parameters by using 3
580 * corresponding original and transformed points.
581 *
582 * @param inputPoint1 1st input point.
583 * @param inputPoint2 2nd input point.
584 * @param inputPoint3 3rd input point.
585 * @param outputPoint1 1st transformed point corresponding to 1st input
586 * point.
587 * @param outputPoint2 2nd transformed point corresponding to 2nd input
588 * point.
589 * @param outputPoint3 3rd transformed point corresponding to 3rd input
590 * point.
591 * @throws CoincidentPointsException raised if transformation cannot be
592 * estimated for some reason (point configuration degeneracy, duplicate
593 * points or numerical instabilities).
594 */
595 public void setTransformationFromPoints(
596 final Point2D inputPoint1, final Point2D inputPoint2, final Point2D inputPoint3, final Point2D outputPoint1,
597 final Point2D outputPoint2, final Point2D outputPoint3) throws CoincidentPointsException {
598 internalSetTransformationFromPoints(inputPoint1, inputPoint2, inputPoint3, outputPoint1, outputPoint2,
599 outputPoint3);
600 }
601
602 /**
603 * Computes the inverse of this transformation and stores the result in
604 * provided instance.
605 *
606 * @param result instance where inverse transformation will be stored.
607 */
608 protected void inverse(final EuclideanTransformation2D result) {
609 // Transformation is as follows: x' = R* x + t
610 // Then inverse transformation is: R* x' = R' * R * x + R'*t = x + R'*t
611 // --> x = R'*x' - R'*t
612
613 // reverse rotation
614 result.rotation = rotation.inverseRotation();
615
616 // reverse translation
617 final var t = Matrix.newFromArray(translation, true);
618 t.multiplyByScalar(-1.0);
619 final var invRot = result.rotation.asInhomogeneousMatrix();
620 try {
621 invRot.multiply(t);
622 } catch (final WrongSizeException ignore) {
623 // never happens
624 }
625
626 result.translation = invRot.toArray();
627 }
628
629 /**
630 * Combines this transformation with provided input transformation and
631 * stores the result into provided output transformation.
632 * The combination is equivalent to multiplying the matrix of this
633 * transformation with the matrix of provided input transformation.
634 *
635 * @param inputTransformation transformation to be combined with.
636 * @param outputTransformation transformation where result will be stored.
637 */
638 private void combine(
639 final EuclideanTransformation2D inputTransformation, final EuclideanTransformation2D outputTransformation) {
640 // combination in matrix representation is:
641 // [R1 t1] * [R2 t2] = [R1*R2 + t1*0T R1*t2 + t1*1] = [R1*R2 R1*t2 + t1]
642 // [0T 1 ] [0T 1 ] [0T*R2 + 1*0T 0T*t2 + 1*1 ] [0T 1 ]
643
644 try {
645 // we do translation first, because this.rotation might change later
646 final var r1 = this.rotation.asInhomogeneousMatrix();
647 final var t2 = Matrix.newFromArray(inputTransformation.translation, true);
648 // this is R1 * t2
649 r1.multiply(t2);
650
651 ArrayUtils.sum(r1.toArray(), this.translation, outputTransformation.translation);
652
653 outputTransformation.rotation = this.rotation.combineAndReturnNew(inputTransformation.rotation);
654
655 } catch (final WrongSizeException ignore) {
656 // never happens
657 }
658 }
659
660 /**
661 * Estimates this transformation internal parameters by using 3
662 * corresponding original and transformed points.
663 *
664 * @param inputPoint1 1st input point.
665 * @param inputPoint2 2nd input point.
666 * @param inputPoint3 3rd input point.
667 * @param outputPoint1 1st transformed point corresponding to 1st input
668 * point.
669 * @param outputPoint2 2nd transformed point corresponding to 2nd input
670 * point.
671 * @param outputPoint3 3rd transformed point corresponding to 3rd input
672 * point.
673 * @throws CoincidentPointsException raised if transformation cannot be
674 * estimated for some reason (point configuration degeneracy, duplicate
675 * points or numerical instabilities).
676 */
677 private void internalSetTransformationFromPoints(
678 final Point2D inputPoint1, final Point2D inputPoint2, final Point2D inputPoint3, final Point2D outputPoint1,
679 final Point2D outputPoint2, final Point2D outputPoint3) throws CoincidentPointsException {
680 final var inputPoints = new ArrayList<Point2D>();
681 inputPoints.add(inputPoint1);
682 inputPoints.add(inputPoint2);
683 inputPoints.add(inputPoint3);
684
685 final var outputPoints = new ArrayList<Point2D>();
686 outputPoints.add(outputPoint1);
687 outputPoints.add(outputPoint2);
688 outputPoints.add(outputPoint3);
689
690 final var estimator = new EuclideanTransformation2DEstimator(inputPoints, outputPoints);
691
692 try {
693 estimator.estimate(this);
694 } catch (final LockedException | NotReadyException ignore) {
695 // never thrown
696 }
697 }
698 }