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 }