View Javadoc
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 }