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.WrongSizeException;
21  import com.irurueta.geometry.estimators.LockedException;
22  import com.irurueta.geometry.estimators.MetricTransformation2DEstimator;
23  import com.irurueta.geometry.estimators.NotReadyException;
24  
25  import java.io.Serializable;
26  import java.util.ArrayList;
27  
28  /**
29   * This class performs metric transformations on 2D space.
30   * Metric transformations include transformations related to rotations,
31   * translations and scale.
32   */
33  public class MetricTransformation2D extends EuclideanTransformation2D implements Serializable {
34  
35      /**
36       * Default scale factor, which leaves objects with the same scale.
37       */
38      public static final double DEFAULT_SCALE = 1.0;
39  
40      /**
41       * Scale factor. Negative values mean that objects get reversed. Values
42       * greater than 1.0 means that objects get enlarged and values between 0.0
43       * and 1.0 means that objects get reduced.
44       */
45      private double scale;
46  
47      /**
48       * Empty constructor.
49       * Creates transformation that has no effect.
50       */
51      public MetricTransformation2D() {
52          super();
53          scale = DEFAULT_SCALE;
54      }
55  
56      /**
57       * Creates transformation with provided rotation.
58       *
59       * @param rotation a 2D rotation.
60       * @throws NullPointerException raised if provided rotation is null.
61       */
62      public MetricTransformation2D(final Rotation2D rotation) {
63          super(rotation);
64          scale = DEFAULT_SCALE;
65      }
66  
67      /**
68       * Creates transformation with provided 2D translation.
69       *
70       * @param translation array indicating 2D translation using inhomogeneous
71       *                    coordinates.
72       * @throws NullPointerException     raised if provided array is null.
73       * @throws IllegalArgumentException raised if length of array is not equal
74       *                                  to NUM_TRANSLATION_COORDS.
75       */
76      public MetricTransformation2D(final double[] translation) {
77          super(translation);
78          scale = DEFAULT_SCALE;
79      }
80  
81      /**
82       * Creates transformation with provided scale value.
83       *
84       * @param scale scale value. Values between 0.0 and 1.0 reduce objects,
85       *              values greater than 1.0 enlarge objects and negative values reverse
86       *              objects.
87       */
88      public MetricTransformation2D(final double scale) {
89          super();
90          this.scale = scale;
91      }
92  
93      /**
94       * Creates transformation with provided rotation, translation and scale
95       * value.
96       *
97       * @param rotation    a 2D rotation.
98       * @param translation array indicating 2D translation using inhomogeneous
99       *                    coordinates.
100      * @param scale       scale value. Values between 0.0 and 1.0 reduce objects,
101      *                    values greater than 1.0 enlarge objects and negative values reverse
102      *                    objects.
103      * @throws NullPointerException     raised if provided array is null or if
104      *                                  rotation is null.
105      * @throws IllegalArgumentException raised if length of array is not equal
106      *                                  to NUM_TRANSLATION_COORDS.
107      */
108     public MetricTransformation2D(final Rotation2D rotation, final double[] translation, final double scale) {
109         super(rotation, translation);
110         this.scale = scale;
111     }
112 
113     /**
114      * Creates transformation by estimating its internal values using provided 3
115      * corresponding original and transformed points.
116      *
117      * @param inputPoint1  1st input point.
118      * @param inputPoint2  2nd input point.
119      * @param inputPoint3  3rd input point.
120      * @param outputPoint1 1st output point.
121      * @param outputPoint2 2nd output point.
122      * @param outputPoint3 3rd output point.
123      * @throws CoincidentPointsException if points are in a degenerate configuration.
124      */
125     public MetricTransformation2D(
126             final Point2D inputPoint1, final Point2D inputPoint2, final Point2D inputPoint3, final Point2D outputPoint1,
127             final Point2D outputPoint2, final Point2D outputPoint3) throws CoincidentPointsException {
128         internalSetMetricTransformationFromPoints(inputPoint1, inputPoint2, inputPoint3, outputPoint1, outputPoint2,
129                 outputPoint3);
130     }
131 
132     /**
133      * Returns scale of this transformation.
134      * A value between 0.0 and 1.0 indicates that objects will be reduced,
135      * a value greater than 1.0 indicates that objects will be enlarged, and
136      * a negative value indicates that objects will be reversed.
137      *
138      * @return scale.
139      */
140     public double getScale() {
141         return scale;
142     }
143 
144     /**
145      * Sets scale of this transformation.
146      *
147      * @param scale scale value to be set. A value between 0.0 and 1.0 indicates
148      *              that objects will be reduced, a value greater than 1.0 indicates that
149      *              objects will be enlarged, and a negative value indicates that objects
150      *              will be reversed.
151      */
152     public void setScale(final double scale) {
153         this.scale = scale;
154     }
155 
156     /**
157      * Represents this transformation as a 3x3 matrix and stores the result in
158      * provided instance.
159      *
160      * @param m instance where transformation matrix will be stored.
161      * @throws IllegalArgumentException raised if provided instance is not a 3x3
162      *                                  matrix.
163      */
164     @Override
165     public void asMatrix(final Matrix m) {
166         if (m.getRows() != HOM_COORDS || m.getColumns() != HOM_COORDS) {
167             throw new IllegalArgumentException();
168         }
169 
170         // set rotation
171         final var rot = getRotation().asInhomogeneousMatrix();
172         rot.multiplyByScalar(scale);
173 
174         m.setSubmatrix(0, 0,
175                 Rotation2D.ROTATION2D_INHOM_MATRIX_ROWS - 1,
176                 Rotation2D.ROTATION2D_INHOM_MATRIX_COLS - 1, rot);
177 
178         final var translation = getTranslation();
179 
180         // set translation
181         m.setSubmatrix(0, HOM_COORDS - 1, translation.length - 1,
182                 HOM_COORDS - 1, translation);
183 
184         // set last element
185         m.setElementAt(HOM_COORDS - 1, HOM_COORDS - 1, 1.0);
186     }
187 
188     /**
189      * Transforms input point using this transformation and stores the result in
190      * provided output points.
191      *
192      * @param inputPoint  point to be transformed.
193      * @param outputPoint instance where transformed point data will be stored.
194      */
195     @Override
196     public void transform(final Point2D inputPoint, final Point2D outputPoint) {
197 
198         inputPoint.normalize();
199         getRotation().rotate(inputPoint, outputPoint);
200 
201         final var translation = getTranslation();
202 
203         outputPoint.setInhomogeneousCoordinates(scale * outputPoint.getInhomX() + translation[0],
204                 scale * outputPoint.getInhomY() + translation[1]);
205     }
206 
207     /**
208      * Inverses this transformation.
209      */
210     @Override
211     public void inverse() {
212         inverse(this);
213     }
214 
215     /**
216      * Computes the inverse of this transformation and returns the result as a
217      * new transformation instance.
218      *
219      * @return inverse transformation.
220      */
221     @Override
222     public Transformation2D inverseAndReturnNew() {
223         final var result = new MetricTransformation2D();
224         inverse(result);
225         return result;
226     }
227 
228     /**
229      * Computes the inverse of this transformation and stores the result in
230      * provided instance.
231      *
232      * @param result instance where inverse transformation will be stored.
233      */
234     protected void inverse(final MetricTransformation2D result) {
235         // Transformation is as follows: x' = s*R* x + t
236         // Then inverse transformation is: (1/s)*R* x' = (1/s) * R' * s * R * x +
237         // (1/s) * R'*t = x + (1/s) * R'*t
238         // --> x = (1/s) * R'*x' - (1/s) * R'*t
239         super.inverse(result);
240         final var translation = result.getTranslation();
241         final var invScale = 1.0 / scale;
242         ArrayUtils.multiplyByScalar(translation, invScale, translation);
243         result.scale = invScale;
244     }
245 
246     /**
247      * Converts this transformation into a metric transformation.
248      * Because this method is inherited, and this instance is already metric,
249      * this method just returns a copy of this transformation.
250      *
251      * @return this transformation converted into a metric transformation.
252      */
253     @Override
254     public MetricTransformation2D toMetric() {
255         return new MetricTransformation2D(getRotation(), getTranslation(), scale);
256     }
257 
258     /**
259      * Converts this transformation into an affine transformation.
260      *
261      * @return this transformation converted into an affine transformation.
262      */
263     public AffineTransformation2D toAffine() {
264         return new AffineTransformation2D(scale, getRotation(), getTranslation());
265     }
266 
267     /**
268      * Combines this transformation with provided transformation.
269      * The combination is equivalent to multiplying the matrix of this
270      * transformation with the matrix of provided transformation.
271      *
272      * @param transformation transformation to be combined with.
273      */
274     @Override
275     public void combine(final EuclideanTransformation2D transformation) {
276         combine(transformation.toMetric(), this);
277     }
278 
279     /**
280      * Combines this transformation with provided transformation and returns
281      * the result as a new transformation instance.
282      * The combination is equivalent to multiplying the matrix of this
283      * transformation with the matrix of provided transformation.
284      *
285      * @param transformation transformation to be combined with.
286      * @return a new transformation resulting of the combination with this
287      * transformation and provided transformation.
288      */
289     @Override
290     public MetricTransformation2D combineAndReturnNew(final EuclideanTransformation2D transformation) {
291         final var result = new MetricTransformation2D();
292         combine(transformation.toMetric(), result);
293         return result;
294     }
295 
296     /**
297      * Combines this transformation with provided transformation.
298      * The combination is equivalent to multiplying the matrix of this
299      * transformation with the matrix of provided transformation.
300      *
301      * @param transformation transformation to be combined with.
302      */
303     public void combine(final MetricTransformation2D transformation) {
304         combine(transformation, this);
305     }
306 
307     /**
308      * Combines this transformation with provided transformation and returns
309      * the result as a new transformation instance.
310      * The combination is equivalent to multiplying the matrix of this
311      * transformation with the matrix of provided transformation.
312      *
313      * @param transformation transformation to be combined with.
314      * @return a new transformation resulting of the combination with this
315      * transformation and provided transformation.
316      */
317     public MetricTransformation2D combineAndReturnNew(final MetricTransformation2D transformation) {
318         final var result = new MetricTransformation2D();
319         combine(transformation, result);
320         return result;
321     }
322 
323     /**
324      * Estimates this transformation internal parameters by using 3
325      * corresponding original and transformed points.
326      *
327      * @param inputPoint1  1st input point.
328      * @param inputPoint2  2nd input point.
329      * @param inputPoint3  3rd input point.
330      * @param outputPoint1 1st transformed point corresponding to 1st input
331      *                     point.
332      * @param outputPoint2 2nd transformed point corresponding to 2nd input
333      *                     point.
334      * @param outputPoint3 3rd transformed point corresponding to 3rd input
335      *                     point.
336      * @throws CoincidentPointsException raised if transformation cannot be
337      *                                   estimated for some reason (point configuration degeneracy, duplicate
338      *                                   points or numerical instabilities).
339      */
340     @Override
341     public void setTransformationFromPoints(
342             final Point2D inputPoint1, final Point2D inputPoint2, final Point2D inputPoint3, final Point2D outputPoint1,
343             final Point2D outputPoint2, final Point2D outputPoint3) throws CoincidentPointsException {
344         internalSetMetricTransformationFromPoints(inputPoint1, inputPoint2, inputPoint3, outputPoint1, outputPoint2,
345                 outputPoint3);
346     }
347 
348     /**
349      * Combines this transformation with provided input transformation and
350      * stores the result into provided output transformation.
351      * The combination is equivalent to multiplying the matrix of this
352      * transformation with the matrix of provided input transformation.
353      *
354      * @param inputTransformation  transformation to be combined with.
355      * @param outputTransformation transformation where result will be stored.
356      */
357     @SuppressWarnings("DuplicatedCode")
358     private void combine(
359             final MetricTransformation2D inputTransformation, final MetricTransformation2D outputTransformation) {
360         // combination in matrix representation is:
361         // [s1*R1 t1] * [s2*R2 t2] = [s1*s2*R1*R2 + t1*0T  s1*R1*t2 + t1*1] = [s1*s2*R1*R2  s1*R1*t2 + t1]
362         // [0T   1 ]    [0T    1 ]   [0T*s2*R2 + 1*0T      0T*t2 + 1*1    ]   [0T           1            ]
363 
364         try {
365             // we do translation first, because this.rotation might change later
366             final var r1 = getRotation().asInhomogeneousMatrix();
367             final var t2 = Matrix.newFromArray(inputTransformation.getTranslation(),
368                     true);
369             // this is R1 * t2
370             r1.multiply(t2);
371             r1.multiplyByScalar(this.scale);
372 
373             ArrayUtils.sum(r1.toArray(), this.getTranslation(), outputTransformation.getTranslation());
374 
375             outputTransformation.setRotation(this.getRotation().combineAndReturnNew(inputTransformation.getRotation()));
376 
377             outputTransformation.scale = this.scale * inputTransformation.scale;
378 
379         } catch (final WrongSizeException ignore) {
380             // never happens
381         }
382     }
383 
384     /**
385      * Estimates this transformation internal parameters by using 3
386      * corresponding original and transformed points.
387      *
388      * @param inputPoint1  1st input point.
389      * @param inputPoint2  2nd input point.
390      * @param inputPoint3  3rd input point.
391      * @param outputPoint1 1st transformed point corresponding to 1st input
392      *                     point.
393      * @param outputPoint2 2nd transformed point corresponding to 2nd input
394      *                     point.
395      * @param outputPoint3 3rd transformed point corresponding to 3rd input
396      *                     point.
397      * @throws CoincidentPointsException raised if transformation cannot be
398      *                                   estimated for some reason (point configuration degeneracy, duplicate
399      *                                   points or numerical instabilities).
400      */
401     @SuppressWarnings("DuplicatedCode")
402     private void internalSetMetricTransformationFromPoints(
403             final Point2D inputPoint1, final Point2D inputPoint2, final Point2D inputPoint3, final Point2D outputPoint1,
404             final Point2D outputPoint2, final Point2D outputPoint3) throws CoincidentPointsException {
405         final var inputPoints = new ArrayList<Point2D>();
406         inputPoints.add(inputPoint1);
407         inputPoints.add(inputPoint2);
408         inputPoints.add(inputPoint3);
409 
410         final var outputPoints = new ArrayList<Point2D>();
411         outputPoints.add(outputPoint1);
412         outputPoints.add(outputPoint2);
413         outputPoints.add(outputPoint3);
414 
415         final var estimator = new MetricTransformation2DEstimator(inputPoints, outputPoints);
416 
417         try {
418             estimator.estimate(this);
419         } catch (final LockedException | NotReadyException ignore) {
420             // never thrown
421         }
422     }
423 }