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.EuclideanTransformation3DEstimator;
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 3D space.
31   * Euclidean transformations include transformations related to rotations and
32   * translations.
33   * Scale cannot be modified on Euclidean transformation.
34   */
35  @SuppressWarnings("DuplicatedCode")
36  public class EuclideanTransformation3D extends Transformation3D implements Serializable {
37  
38      /**
39       * Constant indicating number of coordinates required in translation arrays.
40       */
41      public static final int NUM_TRANSLATION_COORDS = 3;
42  
43      /**
44       * Constant defining number of homogeneous coordinates in 3D space.
45       */
46      public static final int HOM_COORDS = 4;
47  
48      /**
49       * 3D rotation to be performed on geometric objects.
50       */
51      private Rotation3D rotation;
52  
53      /**
54       * 3D 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 EuclideanTransformation3D() {
64          rotation = Rotation3D.create();
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 EuclideanTransformation3D(final Rotation3D 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 3D translation.
85       *
86       * @param translation Array indicating 3D translation using in-homogenous
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 EuclideanTransformation3D(final double[] translation) {
93          if (translation.length != NUM_TRANSLATION_COORDS) {
94              throw new IllegalArgumentException();
95          }
96  
97          rotation = Rotation3D.create();
98          this.translation = translation;
99      }
100 
101     /**
102      * Creates transformation with provided 3D rotation and translation.
103      *
104      * @param rotation    A 3D rotation.
105      * @param translation Array indicating 3D 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 EuclideanTransformation3D(final Rotation3D 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 4
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 inputPoint4  4th input point.
131      * @param outputPoint1 1st transformed point corresponding to 1st input
132      *                     point.
133      * @param outputPoint2 2nd transformed point corresponding to 2nd input
134      *                     point.
135      * @param outputPoint3 3rd transformed point corresponding to 3rd input
136      *                     point.
137      * @param outputPoint4 4th transformed point corresponding to 4th input
138      *                     point.
139      * @throws CoincidentPointsException raised if transformation cannot be
140      *                                   estimated for some reason (point configuration degeneracy, duplicate
141      *                                   points or numerical instabilities).
142      */
143     public EuclideanTransformation3D(
144             final Point3D inputPoint1, final Point3D inputPoint2, final Point3D inputPoint3, final Point3D inputPoint4,
145             final Point3D outputPoint1, final Point3D outputPoint2, final Point3D outputPoint3,
146             final Point3D outputPoint4) throws CoincidentPointsException {
147         internalSetTransformationFromPoints(inputPoint1, inputPoint2, inputPoint3, inputPoint4, outputPoint1,
148                 outputPoint2, outputPoint3, outputPoint4);
149     }
150 
151     /**
152      * Returns 3D rotation assigned to this transformation.
153      *
154      * @return 3D rotation.
155      */
156     public Rotation3D getRotation() {
157         return rotation;
158     }
159 
160     /**
161      * Sets 3D rotation for this transformation.
162      *
163      * @param rotation A 3D rotation.
164      * @throws NullPointerException Raised if provided rotation is null.
165      */
166     public void setRotation(final Rotation3D rotation) {
167         if (rotation == null) {
168             throw new NullPointerException();
169         }
170         this.rotation = rotation;
171     }
172 
173     /**
174      * Adds provided rotation to current rotation assigned to this
175      * transformation.
176      *
177      * @param rotation 3D rotation to be added.
178      */
179     public void addRotation(final Rotation3D rotation) {
180         this.rotation.combine(rotation);
181     }
182 
183     /**
184      * Returns 3D translation assigned to this transformation as an array
185      * expressed in inhomogeneous coordinates.
186      *
187      * @return 3D translation array.
188      */
189     public double[] getTranslation() {
190         return translation;
191     }
192 
193     /**
194      * Sets 3D translation assigned to this transformation as an array expressed
195      * in inhomogeneous coordinates.
196      *
197      * @param translation 3D translation array.
198      * @throws IllegalArgumentException raised if provided array does not have
199      *                                  length equal to NUM_TRANSLATION_COORDS.
200      */
201     public void setTranslation(final double[] translation) {
202         if (translation.length != NUM_TRANSLATION_COORDS) {
203             throw new IllegalArgumentException();
204         }
205 
206         this.translation = translation;
207     }
208 
209     /**
210      * Adds provided translation to current translation on this transformation.
211      * Provided translation must be expressed as an array of inhomogeneous
212      * coordinates.
213      *
214      * @param translation 3D translation array.
215      * @throws IllegalArgumentException raised if provided array does not have
216      *                                  length equal to NUM_TRANSLATION_COORDS.
217      */
218     public void addTranslation(final double[] translation) {
219         ArrayUtils.sum(this.translation, translation, this.translation);
220     }
221 
222     /**
223      * Returns current x coordinate translation assigned to this transformation.
224      *
225      * @return X coordinate translation.
226      */
227     public double getTranslationX() {
228         return translation[0];
229     }
230 
231     /**
232      * Sets x coordinate translation to be made by this transformation.
233      *
234      * @param translationX X coordinate translation to be set.
235      */
236     public void setTranslationX(final double translationX) {
237         translation[0] = translationX;
238     }
239 
240     /**
241      * Returns current y coordinate translation assigned to this transformation.
242      *
243      * @return Y coordinate translation.
244      */
245     public double getTranslationY() {
246         return translation[1];
247     }
248 
249     /**
250      * Sets y coordinate translation to be made by this transformation.
251      *
252      * @param translationY Y coordinate translation to be set.
253      */
254     public void setTranslationY(final double translationY) {
255         translation[1] = translationY;
256     }
257 
258     /**
259      * Returns current z coordinate translation assigned to this transformation.
260      *
261      * @return Z coordinate translation.
262      */
263     public double getTranslationZ() {
264         return translation[2];
265     }
266 
267     /**
268      * Sets z coordinate translation to be made by this transformation.
269      *
270      * @param translationZ Z coordinate translation to be set.
271      */
272     public void setTranslationZ(final double translationZ) {
273         translation[2] = translationZ;
274     }
275 
276     /**
277      * Sets x, y, z coordinates of translation to be made by this
278      * transformation.
279      *
280      * @param translationX translation x coordinate to be set.
281      * @param translationY translation y coordinate to be set.
282      * @param translationZ translation z coordinate to be set.
283      */
284     public void setTranslation(
285             final double translationX, final double translationY, final double translationZ) {
286         translation[0] = translationX;
287         translation[1] = translationY;
288         translation[2] = translationZ;
289     }
290 
291     /**
292      * Sets x, y, z coordinates of translation to be made by this
293      * transformation.
294      *
295      * @param translation translation to be set.
296      */
297     public void setTranslation(final Point3D translation) {
298         setTranslation(translation.getInhomX(), translation.getInhomY(), translation.getInhomZ());
299     }
300 
301     /**
302      * Gets x, y, z coordinates of translation to be made by this transformation
303      * as a new point.
304      *
305      * @return a new point containing translation coordinates.
306      */
307     public Point3D getTranslationPoint() {
308         final var out = Point3D.create();
309         getTranslationPoint(out);
310         return out;
311     }
312 
313     /**
314      * Gets x, y, z coordinates of translation to be made by this transformation
315      * and stores them into provided point.
316      *
317      * @param out point where translation coordinates will be stored.
318      */
319     public void getTranslationPoint(final Point3D out) {
320         out.setInhomogeneousCoordinates(translation[0], translation[1], translation[2]);
321     }
322 
323     /**
324      * Adds provided x coordinate to current translation assigned to this
325      * transformation.
326      *
327      * @param translationX X coordinate to be added to current translation.
328      */
329     public void addTranslationX(final double translationX) {
330         translation[0] += translationX;
331     }
332 
333     /**
334      * Adds provided y coordinate to current translation assigned to this
335      * transformation.
336      *
337      * @param translationY Y coordinate to be added to current translation.
338      */
339     public void addTranslationY(final double translationY) {
340         translation[1] += translationY;
341     }
342 
343     /**
344      * Adds provided z coordinate to current translation assigned to this
345      * transformation.
346      *
347      * @param translationZ Z coordinate to be added to current translation.
348      */
349     public void addTranslationZ(final double translationZ) {
350         translation[2] += translationZ;
351     }
352 
353     /**
354      * Adds provided coordinates to current translation assigned to this
355      * transformation.
356      *
357      * @param translationX x coordinate to be added to current translation.
358      * @param translationY y coordinate to be added to current translation.
359      * @param translationZ z coordinate to be added to current translation.
360      */
361     public void addTranslation(
362             final double translationX, final double translationY, final double translationZ) {
363         translation[0] += translationX;
364         translation[1] += translationY;
365         translation[2] += translationZ;
366     }
367 
368     /**
369      * Adds provided coordinates to current translation assigned to this
370      * transformation.
371      *
372      * @param translation x, y, z coordinates to be added to current
373      *                    translation.
374      */
375     public void addTranslation(final Point3D translation) {
376         addTranslation(translation.getInhomX(), translation.getInhomY(), translation.getInhomZ());
377     }
378 
379     /**
380      * Represents this transformation as a 4x4 matrix.
381      * A point can be transformed as T * p, where T is the transformation matrix
382      * and p is a point expressed as an homogeneous vector.
383      *
384      * @return This transformation in matrix form.
385      */
386     @Override
387     public Matrix asMatrix() {
388         Matrix m = null;
389         try {
390             m = new Matrix(HOM_COORDS, HOM_COORDS);
391             asMatrix(m);
392         } catch (final WrongSizeException ignore) {
393             // never happens
394         }
395         return m;
396     }
397 
398     /**
399      * Represents this transformation as a 4x4 matrix and stores the result in
400      * provided instance.
401      *
402      * @param m instance where transformation matrix will be stored.
403      * @throws IllegalArgumentException raised if provided instance is not a 4x4
404      *                                  matrix.
405      */
406     @Override
407     public void asMatrix(final Matrix m) {
408         if (m.getRows() != HOM_COORDS || m.getColumns() != HOM_COORDS) {
409             throw new IllegalArgumentException();
410         }
411 
412         m.initialize(0.0);
413 
414         // set rotation
415         m.setSubmatrix(0, 0, Rotation3D.INHOM_COORDS - 1,
416                 Rotation3D.INHOM_COORDS - 1, rotation.asInhomogeneousMatrix());
417 
418         // set translation
419         m.setSubmatrix(0, HOM_COORDS - 1, translation.length - 1,
420                 HOM_COORDS - 1, translation);
421 
422         // set last element
423         m.setElementAt(HOM_COORDS - 1, HOM_COORDS - 1, 1.0);
424     }
425 
426     /**
427      * Transforms input point using this transformation and stores the result
428      * in provided output points.
429      *
430      * @param inputPoint  point to be transformed.
431      * @param outputPoint instance where transformed point data will be stored.
432      */
433     @Override
434     public void transform(final Point3D inputPoint, final Point3D outputPoint) {
435         inputPoint.normalize();
436         rotation.rotate(inputPoint, outputPoint);
437         outputPoint.setInhomogeneousCoordinates(outputPoint.getInhomX() + translation[0],
438                 outputPoint.getInhomY() + translation[1], outputPoint.getInhomZ() + translation[2]);
439     }
440 
441     /**
442      * Transforms a quadric using this transformation and stores the result into
443      * provided output quadric.
444      *
445      * @param inputQuadric  Quadric to be transformed.
446      * @param outputQuadric instance where data of transformed quadric will be
447      *                      stored.
448      * @throws NonSymmetricMatrixException raised if due to numerical precision
449      *                                     the resulting output quadric matrix is not considered to be symmetric.
450      */
451     @Override
452     public void transform(final Quadric inputQuadric, final Quadric outputQuadric) throws NonSymmetricMatrixException {
453         // point' * quadric * point = 0
454         // point' * T' * transformedQuadric * T * point = 0
455         // where:
456         // - transformedPoint = T * point
457 
458         // Hence:
459         // transformedQuadric = T^-1' * quadric * T^-1
460 
461         inputQuadric.normalize();
462 
463         final var q = inputQuadric.asMatrix();
464         final var invT = inverseAndReturnNew().asMatrix();
465         // normalize transformation matrix invT to increase accuracy
466         var norm = Utils.normF(invT);
467         invT.multiplyByScalar(1.0 / norm);
468 
469         final var m = invT.transposeAndReturnNew();
470         try {
471             m.multiply(q);
472             m.multiply(invT);
473         } catch (final WrongSizeException ignore) {
474             // never happens
475         }
476 
477         // normalize resulting m matrix to increase accuracy so that it can be
478         // considered symmetric
479         norm = Utils.normF(m);
480         m.multiplyByScalar(1.0 / norm);
481 
482         outputQuadric.setParameters(m);
483     }
484 
485     /**
486      * Transforms a dual quadric using this transformation and stores the result
487      * into provided output dual quadric.
488      *
489      * @param inputDualQuadric  dual quadric to be transformed.
490      * @param outputDualQuadric instance where data of transformed dual quadric
491      *                          will be stored.
492      * @throws NonSymmetricMatrixException raised if due to numerical precision.
493      *                                     the resulting output dual quadric matrix is not considered to be
494      *                                     symmetric.
495      */
496     @Override
497     public void transform(final DualQuadric inputDualQuadric, final DualQuadric outputDualQuadric)
498             throws NonSymmetricMatrixException {
499         // plane' * dualQuadric * plane = 0
500         // plane' * T^-1 * T * dualQuadric * T' * T^-1'*plane
501 
502         // Hence:
503         // transformed plane: T^-1'*plane
504         // transformed dual quadric: T * dualQuadric * T'
505 
506         inputDualQuadric.normalize();
507 
508         final var dualQ = inputDualQuadric.asMatrix();
509         final var t = asMatrix();
510         // normalize transformation matrix T to increase accuracy
511         var norm = Utils.normF(t);
512         t.multiplyByScalar(1.0 / norm);
513 
514         final var transT = t.transposeAndReturnNew();
515         try {
516             t.multiply(dualQ);
517             t.multiply(transT);
518         } catch (final WrongSizeException ignore) {
519             // never happens
520         }
521 
522         // normalize resulting m matrix to increase accuracy so that it can be
523         // considered symmetric
524         norm = Utils.normF(t);
525         t.multiplyByScalar(1.0 / norm);
526 
527         outputDualQuadric.setParameters(t);
528     }
529 
530     /**
531      * Transforms provided input plane using this transformation and stores the
532      * result into provided output plane instance.
533      *
534      * @param inputPlane  plane to be transformed.
535      * @param outputPlane instance where data of transformed plane will be
536      *                    stored.
537      */
538     @Override
539     public void transform(final Plane inputPlane, final Plane outputPlane) {
540         // plane' * point = 0 --> plane' * T^-1 * T * point
541         // (plane' * T^-1)*(T*point) = (T^-1'*plane)'*(T*point)
542         // where:
543         // - transformedPlane = T^-1'*plane
544         // - transformedPoint = T*point
545 
546         inputPlane.normalize();
547 
548         final var invT = inverseAndReturnNew().asMatrix();
549         final var plane = Matrix.newFromArray(inputPlane.asArray());
550 
551         // normalize transformation matrix T to increase accuracy
552         final var norm = Utils.normF(invT);
553         invT.multiplyByScalar(1.0 / norm);
554 
555         invT.transpose();
556         try {
557             invT.multiply(plane);
558         } catch (final WrongSizeException ignore) {
559             // never happens
560         }
561 
562         outputPlane.setParameters(invT.getBuffer());
563     }
564 
565     /**
566      * Transforms a camera using this transformation and stores the result into
567      * provided output camera.
568      *
569      * @param inputCamera  camera to be transformed.
570      * @param outputCamera instance where data of transformed camera will be
571      *                     stored.
572      */
573     @Override
574     public void transform(final PinholeCamera inputCamera, final PinholeCamera outputCamera) {
575         inputCamera.normalize();
576 
577         final var invT = inverseAndReturnNew().asMatrix();
578         final var c = inputCamera.getInternalMatrix();
579         try {
580             c.multiply(invT);
581             outputCamera.setInternalMatrix(c);
582         } catch (final WrongSizeException ignore) {
583             // never thrown
584         }
585     }
586 
587     /**
588      * Converts this transformation into a metric transformation.
589      *
590      * @return this transformation converted into a metric transformation.
591      */
592     public MetricTransformation3D toMetric() {
593         return new MetricTransformation3D(rotation, translation, MetricTransformation3D.DEFAULT_SCALE);
594     }
595 
596     /**
597      * Inverses this transformation.
598      */
599     public void inverse() {
600         inverse(this);
601     }
602 
603     /**
604      * Computes the inverse of this transformation and returns the result as a
605      * new transformation instance.
606      *
607      * @return inverse transformation.
608      */
609     public Transformation3D inverseAndReturnNew() {
610         final var result = new EuclideanTransformation3D();
611         inverse(result);
612         return result;
613     }
614 
615     /**
616      * Combines this transformation with provided transformation.
617      * The combination is equivalent to multiplying the matrix of this
618      * transformation with the matrix of provided transformation.
619      *
620      * @param transformation Transformation to be combined with.
621      */
622     public void combine(final EuclideanTransformation3D transformation) {
623         combine(transformation, this);
624     }
625 
626     /**
627      * Combines this transformation with provided transformation and returns
628      * the result as a new transformation instance.
629      * The combination is equivalent to multiplying the matrix of this
630      * transformation with the matrix pf provided transformation.
631      *
632      * @param transformation Transformation to be combined with.
633      * @return A new transformation resulting of the combination with this
634      * transformation and provided transformation.
635      */
636     public EuclideanTransformation3D combineAndReturnNew(final EuclideanTransformation3D transformation) {
637         final var result = new EuclideanTransformation3D();
638         combine(transformation, result);
639         return result;
640     }
641 
642     /**
643      * Estimates this transformation internal parameters by using 4
644      * corresponding original and transformed points.
645      *
646      * @param inputPoint1  1st input point.
647      * @param inputPoint2  2nd input point.
648      * @param inputPoint3  3rd input point.
649      * @param inputPoint4  4th input point.
650      * @param outputPoint1 1st transformed point corresponding to 1st input
651      *                     point.
652      * @param outputPoint2 2nd transformed point corresponding to 2nd input
653      *                     point.
654      * @param outputPoint3 3rd transformed point corresponding to 3rd input
655      *                     point.
656      * @param outputPoint4 4th transformed point corresponding to 4th input
657      *                     point.
658      * @throws CoincidentPointsException raised if transformation cannot be
659      *                                   estimated for some reason (point configuration degeneracy, duplicate
660      *                                   points or numerical instabilities).
661      */
662     public void setTransformationFromPoints(
663             final Point3D inputPoint1, final Point3D inputPoint2, final Point3D inputPoint3, final Point3D inputPoint4,
664             final Point3D outputPoint1, final Point3D outputPoint2, final Point3D outputPoint3,
665             final Point3D outputPoint4) throws CoincidentPointsException {
666         internalSetTransformationFromPoints(inputPoint1, inputPoint2, inputPoint3, inputPoint4, outputPoint1,
667                 outputPoint2, outputPoint3, outputPoint4);
668     }
669 
670     /**
671      * Computes the inverse of this transformation and stores the result in
672      * provided instance.
673      *
674      * @param result instance where inverse transformation will be stored.
675      */
676     protected void inverse(final EuclideanTransformation3D result) {
677         // Transformation is as follows: x' = R* x + t
678         // Then inverse transformation is: R'* x' = R' * R * x + R'*t = x + R'*t
679         // --> x = R'*x' - R'*t
680 
681         // reverse rotation
682         result.rotation = rotation.inverseRotationAndReturnNew();
683 
684         // reverse translation
685         final var t = Matrix.newFromArray(translation, true);
686         t.multiplyByScalar(-1.0);
687         final var invRot = result.rotation.asInhomogeneousMatrix();
688         try {
689             invRot.multiply(t);
690         } catch (final WrongSizeException ignore) {
691             // never happens
692         }
693 
694         result.translation = invRot.toArray();
695     }
696 
697     /**
698      * Combines this transformation with provided input transformation and
699      * stores the result into provided output transformation.
700      * The combination is equivalent to multiplying the matrix of this
701      * transformation with the matrix of provided input transformation.
702      *
703      * @param inputTransformation  transformation to be combined with.
704      * @param outputTransformation transformation where result will be stored.
705      */
706     private void combine(final EuclideanTransformation3D inputTransformation,
707                          final EuclideanTransformation3D outputTransformation) {
708         // combination in matrix representation is:
709         // [R1 t1] * [R2 t2] = [R1*R2 + t1*0T  R1*t2 + t1*1] = [R1*R2 R1*t2 + t1]
710         // [0T 1 ]   [0T 1 ]   [0T*R2 + 1*0T   0T*t2 + 1*1 ]   [0T    1         ]
711 
712         try {
713             // we do translation first, because this.rotation might change later
714             final var r1 = this.rotation.asInhomogeneousMatrix();
715             final var t2 = Matrix.newFromArray(inputTransformation.translation, true);
716             // this is R1 * t2
717             r1.multiply(t2);
718 
719             ArrayUtils.sum(r1.toArray(), this.translation, outputTransformation.translation);
720 
721             outputTransformation.rotation = this.rotation.combineAndReturnNew(inputTransformation.rotation);
722 
723         } catch (final WrongSizeException ignore) {
724             // never happens
725         }
726     }
727 
728     /**
729      * Estimates this transformation internal parameters by using 4
730      * corresponding original and transformed points.
731      *
732      * @param inputPoint1  1st input point.
733      * @param inputPoint2  2nd input point.
734      * @param inputPoint3  3rd input point.
735      * @param inputPoint4  4th input point.
736      * @param outputPoint1 1st transformed point corresponding to 1st input
737      *                     point.
738      * @param outputPoint2 2nd transformed point corresponding to 2nd input
739      *                     point.
740      * @param outputPoint3 3rd transformed point corresponding to 3rd input
741      *                     point.
742      * @param outputPoint4 4th transformed point corresponding to 4th input
743      *                     point.
744      * @throws CoincidentPointsException raised if transformation cannot be
745      *                                   estimated for some reason (point configuration degeneracy, duplicate
746      *                                   points or numerical instabilities).
747      */
748     private void internalSetTransformationFromPoints(
749             final Point3D inputPoint1, final Point3D inputPoint2, final Point3D inputPoint3, final Point3D inputPoint4,
750             final Point3D outputPoint1, final Point3D outputPoint2, final Point3D outputPoint3,
751             final Point3D outputPoint4) throws CoincidentPointsException {
752         final var inputPoints = new ArrayList<Point3D>();
753         inputPoints.add(inputPoint1);
754         inputPoints.add(inputPoint2);
755         inputPoints.add(inputPoint3);
756         inputPoints.add(inputPoint4);
757 
758         final var outputPoints = new ArrayList<Point3D>();
759         outputPoints.add(outputPoint1);
760         outputPoints.add(outputPoint2);
761         outputPoints.add(outputPoint3);
762         outputPoints.add(outputPoint4);
763 
764         final var estimator = new EuclideanTransformation3DEstimator(inputPoints, outputPoints);
765 
766         try {
767             estimator.estimate(this);
768         } catch (final LockedException | NotReadyException ignore) {
769             // never thrown
770         }
771     }
772 }