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.AlgebraException;
19  import com.irurueta.algebra.ArrayUtils;
20  import com.irurueta.algebra.LUDecomposer;
21  import com.irurueta.algebra.Matrix;
22  import com.irurueta.algebra.RQDecomposer;
23  import com.irurueta.algebra.SingularValueDecomposer;
24  import com.irurueta.algebra.Utils;
25  import com.irurueta.algebra.WrongSizeException;
26  
27  import java.io.Serializable;
28  import java.util.Arrays;
29  
30  /**
31   * This class performs projective transformations on 2D space.
32   * Projective transformations include any possible transformation that can be
33   * applied to 3D points.
34   */
35  @SuppressWarnings("DuplicatedCode")
36  public class ProjectiveTransformation3D 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 indicating the number of projective parameters that can be set
45       * in projective parameters array.
46       */
47      public static final int NUM_PROJECTIVE_PARAMS = 4;
48  
49      /**
50       * Constant defining number of inhomogeneous coordinates in 3D space
51       */
52      public static final int INHOM_COORDS = 3;
53  
54      /**
55       * Constant defining number of homogeneous coordinates in 3D space
56       */
57      public static final int HOM_COORDS = 4;
58  
59      /**
60       * Machine precision
61       */
62      public static final double EPS = 1e-12;
63  
64      /**
65       * Constant defining a large threshold to consider a matrix valid as
66       * rotation
67       */
68      private static final double LARGE_ROTATION_MATRIX_THRESHOLD = 1.0;
69  
70      /**
71       * Internal 4x4 matrix containing transformation.
72       */
73      private Matrix t;
74  
75      /**
76       * Indicates whether internal matrix is normalized.
77       */
78      private boolean normalized;
79  
80      /**
81       * Empty constructor.
82       * Creates transformation that has no effect.
83       */
84      public ProjectiveTransformation3D() {
85          super();
86          try {
87              t = Matrix.identity(HOM_COORDS, HOM_COORDS);
88          } catch (final WrongSizeException ignore) {
89              // never happens
90          }
91          normalize();
92      }
93  
94      /**
95       * Creates transformation with provided internal matrix.
96       * Notice that provided matrix should usually be invertible, otherwise the
97       * transformation will be degenerate and its inverse will not be available.
98       *
99       * @param t Internal 4x4 matrix.
100      * @throws NullPointerException     raised if provided matrix is null.
101      * @throws IllegalArgumentException raised if provided matrix is not 4x4.
102      */
103     public ProjectiveTransformation3D(final Matrix t) {
104         setT(t);
105         normalize();
106     }
107 
108     /**
109      * Creates transformation with provided scale value.
110      *
111      * @param scale Scale value. Values between 0.0 and 1.0 reduce objects,
112      *              values greater than 1.0 enlarge objects and negative values reverse
113      *              objects.
114      */
115     public ProjectiveTransformation3D(final double scale) {
116         final var diag = new double[HOM_COORDS];
117         Arrays.fill(diag, scale);
118         // set last element to 1.0
119         diag[HOM_COORDS - 1] = 1.0;
120         t = Matrix.diagonal(diag);
121         normalize();
122     }
123 
124     /**
125      * Creates transformation with provided rotation.
126      *
127      * @param rotation a 3D rotation.
128      * @throws NullPointerException raised if provided rotation is null.
129      */
130     public ProjectiveTransformation3D(final Rotation3D rotation) {
131         t = rotation.asHomogeneousMatrix();
132         normalize();
133     }
134 
135     /**
136      * Creates transformation with provided scale and rotation.
137      *
138      * @param scale    Scale value. Values between 0.0 and 1.0 reduce objects,
139      *                 values greater than 1.0 enlarge objects and negative values reverse
140      *                 objects.
141      * @param rotation a 3D rotation.
142      * @throws NullPointerException raised if provided rotation is null.
143      */
144     public ProjectiveTransformation3D(final double scale, final Rotation3D rotation) {
145         try {
146             final var diag = new double[INHOM_COORDS];
147             Arrays.fill(diag, scale);
148             final var a = Matrix.diagonal(diag);
149             a.multiply(rotation.asInhomogeneousMatrix());
150             t = Matrix.identity(HOM_COORDS, HOM_COORDS);
151             t.setSubmatrix(0, 0, INHOM_COORDS - 1,
152                     INHOM_COORDS - 1, a);
153         } catch (final WrongSizeException ignore) {
154             // never happens
155         }
156         normalize();
157     }
158 
159     /**
160      * Creates transformation with provided affine parameters and rotation.
161      *
162      * @param params   affine parameters including x,y, z scaling and skewness
163      *                 of axes.
164      * @param rotation a 3D rotation.
165      * @throws NullPointerException raised if provided parameters are null or
166      *                              if provided rotation is null.
167      */
168     public ProjectiveTransformation3D(final AffineParameters3D params, final Rotation3D rotation) {
169         try {
170             final var a = params.asMatrix();
171             a.multiply(rotation.asInhomogeneousMatrix());
172             t = Matrix.identity(HOM_COORDS, HOM_COORDS);
173             t.setSubmatrix(0, 0, INHOM_COORDS - 1, INHOM_COORDS - 1, a);
174         } catch (final WrongSizeException ignore) {
175             // never happens
176         }
177         normalize();
178     }
179 
180     /**
181      * Creates transformation with provided 3D translation.
182      *
183      * @param translation array indicating 3D translation using inhomogeneous
184      *                    coordinates.
185      * @throws NullPointerException     raised if provided array is null.
186      * @throws IllegalArgumentException raised if length of array is not equal
187      *                                  to NUM_TRANSLATION_COORDS.
188      */
189     public ProjectiveTransformation3D(final double[] translation) {
190         if (translation.length != NUM_TRANSLATION_COORDS) {
191             throw new IllegalArgumentException();
192         }
193 
194         try {
195             t = Matrix.identity(HOM_COORDS, HOM_COORDS);
196             t.setSubmatrix(0, 3, 2, 3, translation);
197         } catch (final WrongSizeException ignore) {
198             // never happens
199         }
200         normalize();
201     }
202 
203     /**
204      * Creates transformation with provided affine linear mapping and
205      * translation.
206      *
207      * @param a           affine linear mapping.
208      * @param translation array indicating 3D translation using inhomogeneous
209      *                    coordinates.
210      * @throws NullPointerException     raised if provided array is null or if
211      *                                  affine linear mapping is null.
212      * @throws IllegalArgumentException raised if length of array is not equal
213      *                                  to NUM_TRANSLATION_COORDS.
214      */
215     public ProjectiveTransformation3D(final Matrix a, final double[] translation) {
216         if (translation.length != NUM_TRANSLATION_COORDS) {
217             throw new IllegalArgumentException();
218         }
219 
220         try {
221             t = Matrix.identity(HOM_COORDS, HOM_COORDS);
222             t.setSubmatrix(0, 0, INHOM_COORDS - 1,
223                     INHOM_COORDS - 1, a);
224             t.setSubmatrix(0, HOM_COORDS - 1, translation.length - 1,
225                     HOM_COORDS - 1, translation);
226         } catch (final WrongSizeException ignore) {
227             // never happens
228         }
229         normalize();
230     }
231 
232     /**
233      * Creates transformation with provided scale and translation.
234      *
235      * @param scale       scale value. Values between 0.0 and 1.0 reduce objects,
236      *                    values greater than 1.0 enlarge objects and negative values reverse
237      *                    objects.
238      * @param translation array indicating 3D translation using inhomogeneous
239      *                    coordinates.
240      * @throws NullPointerException     raised if provided translation is null.
241      * @throws IllegalArgumentException Raised if provided translation does not
242      *                                  have length 3.
243      */
244     public ProjectiveTransformation3D(final double scale, final double[] translation) {
245         if (translation.length != NUM_TRANSLATION_COORDS) {
246             throw new IllegalArgumentException();
247         }
248 
249         final var diag = new double[HOM_COORDS];
250         Arrays.fill(diag, scale);
251         // set last element to 1.0
252         diag[HOM_COORDS - 1] = 1.0;
253         t = Matrix.diagonal(diag);
254 
255         // set translation
256         t.setSubmatrix(0, HOM_COORDS - 1, translation.length - 1,
257                 HOM_COORDS - 1, translation);
258         normalize();
259     }
260 
261     /**
262      * Creates transformation with provided rotation and translation.
263      *
264      * @param rotation    a 3D rotation.
265      * @param translation array indicating 3D translation using inhomogeneous
266      *                    coordinates.
267      * @throws NullPointerException     raised if provided rotation or translation
268      *                                  is null.
269      * @throws IllegalArgumentException raised if provided translation does not
270      *                                  have length 3.
271      */
272     public ProjectiveTransformation3D(final Rotation3D rotation, final double[] translation) {
273         if (translation.length != NUM_TRANSLATION_COORDS) {
274             throw new IllegalArgumentException();
275         }
276 
277         t = rotation.asHomogeneousMatrix();
278 
279         // set translation
280         t.setSubmatrix(0, HOM_COORDS - 1, translation.length - 1,
281                 HOM_COORDS - 1, translation);
282         normalize();
283     }
284 
285     /**
286      * Creates transformation with provided scale, rotation and translation.
287      *
288      * @param scale       scale value. Values between 0.0 and 1.0 reduce objects,
289      *                    values greater than 1.0 enlarge objects and negative values reverse
290      *                    objects.
291      * @param rotation    a 3D rotation.
292      * @param translation array indicating 3D translation using inhomogeneous
293      *                    coordinates.
294      * @throws NullPointerException     raised if provided rotation or translation
295      *                                  is null.
296      * @throws IllegalArgumentException raised if provided translation does not
297      *                                  have length 3.
298      */
299     public ProjectiveTransformation3D(final double scale, final Rotation3D rotation, final double[] translation) {
300         if (translation.length != NUM_TRANSLATION_COORDS) {
301             throw new IllegalArgumentException();
302         }
303 
304         try {
305             final var diag = new double[INHOM_COORDS];
306             Arrays.fill(diag, scale);
307             final var a = Matrix.diagonal(diag);
308             a.multiply(rotation.asInhomogeneousMatrix());
309 
310             t = Matrix.identity(HOM_COORDS, HOM_COORDS);
311             // set A
312             t.setSubmatrix(0, 0, INHOM_COORDS - 1,
313                     INHOM_COORDS - 1, a);
314             // set translation
315             t.setSubmatrix(0, HOM_COORDS - 1, translation.length - 1,
316                     HOM_COORDS - 1, translation);
317         } catch (final WrongSizeException ignore) {
318             // never happens
319         }
320         normalize();
321     }
322 
323     /**
324      * Creates transformation with provided scale, rotation and translation.
325      *
326      * @param scale                scale value. Values between 0.0 and 1.0 reduce objects,
327      *                             values greater than 1.0 enlarge objects and negative values reverse
328      *                             objects.
329      * @param rotation             a 3D rotation.
330      * @param translation          array indicating 3D translation using inhomogeneous
331      *                             coordinates.
332      * @param projectiveParameters array of length 4 containing projective
333      *                             parameters.
334      * @throws NullPointerException     raised if provided rotation or translation
335      *                                  is null.
336      * @throws IllegalArgumentException raised if provided translation does not
337      *                                  have length 3 or if projective parameters array doesn't have length 4.
338      */
339     public ProjectiveTransformation3D(final double scale, final Rotation3D rotation, final double[] translation,
340                                       final double[] projectiveParameters) {
341         if (translation.length != NUM_TRANSLATION_COORDS) {
342             throw new IllegalArgumentException();
343         }
344         if (projectiveParameters.length != HOM_COORDS) {
345             throw new IllegalArgumentException();
346         }
347 
348         try {
349             final var value = projectiveParameters[HOM_COORDS - 1];
350             final var diag = new double[INHOM_COORDS];
351             Arrays.fill(diag, scale);
352             final var a = Matrix.diagonal(diag);
353             a.multiply(rotation.asInhomogeneousMatrix());
354 
355             t = Matrix.identity(HOM_COORDS, HOM_COORDS);
356             // set A
357             t.setSubmatrix(0, 0, INHOM_COORDS - 1,
358                     INHOM_COORDS - 1, a);
359             // set translation
360             t.setSubmatrix(0, HOM_COORDS - 1, translation.length - 1,
361                     HOM_COORDS - 1, translation);
362             t.multiplyByScalar(value);
363 
364             t.setSubmatrix(HOM_COORDS - 1, 0, HOM_COORDS - 1,
365                     HOM_COORDS - 1, projectiveParameters);
366         } catch (final WrongSizeException ignore) {
367             // never happens
368         }
369         normalize();
370     }
371 
372     /**
373      * Creates transformation with provided parameters, rotation and
374      * translation.
375      *
376      * @param params      affine parameters including horizontal scaling, vertical
377      *                    scaling and skewness.
378      * @param rotation    a 3D rotation.
379      * @param translation array indicating 3D translation using inhomogeneous
380      *                    coordinates.
381      * @throws NullPointerException     raised if provided parameters, rotation or
382      *                                  translation is null.
383      * @throws IllegalArgumentException raised if provided translation does not
384      *                                  have length 3.
385      */
386     public ProjectiveTransformation3D(final AffineParameters3D params, final Rotation3D rotation,
387                                       final double[] translation) {
388         if (translation.length != NUM_TRANSLATION_COORDS) {
389             throw new IllegalArgumentException();
390         }
391 
392         try {
393             final var a = params.asMatrix();
394             a.multiply(rotation.asInhomogeneousMatrix());
395             t = Matrix.identity(HOM_COORDS, HOM_COORDS);
396             // set A
397             t.setSubmatrix(0, 0, INHOM_COORDS - 1,
398                     INHOM_COORDS - 1, a);
399             // set translation
400             t.setSubmatrix(0, HOM_COORDS - 1, translation.length - 1,
401                     HOM_COORDS - 1, translation);
402         } catch (final WrongSizeException ignore) {
403             // never happens
404         }
405         normalize();
406     }
407 
408     /**
409      * Creates transformation with provided parameters, rotation and
410      * translation.
411      *
412      * @param params               affine parameters including horizontal scaling, vertical
413      *                             scaling and skewness.
414      * @param rotation             a 3D rotation.
415      * @param translation          array indicating 3D translation using inhomogeneous
416      *                             coordinates.
417      * @param projectiveParameters array of length 4 containing projective
418      *                             parameters.
419      * @throws NullPointerException     raised if provided parameters, rotation or
420      *                                  translation is null.
421      * @throws IllegalArgumentException raised if provided translation does not
422      *                                  have length 3 or if projective parameters array doesn't have length 4.
423      */
424     public ProjectiveTransformation3D(final AffineParameters3D params, final Rotation3D rotation,
425                                       final double[] translation, final double[] projectiveParameters) {
426         if (translation.length != NUM_TRANSLATION_COORDS) {
427             throw new IllegalArgumentException();
428         }
429         if (projectiveParameters.length != HOM_COORDS) {
430             throw new IllegalArgumentException();
431         }
432 
433         try {
434             final var a = params.asMatrix();
435             a.multiply(rotation.asInhomogeneousMatrix());
436             t = Matrix.identity(HOM_COORDS, HOM_COORDS);
437             // set A
438             t.setSubmatrix(0, 0, INHOM_COORDS - 1,
439                     INHOM_COORDS - 1, a);
440             // set translation
441             t.setSubmatrix(0, HOM_COORDS - 1, translation.length - 1,
442                     HOM_COORDS - 1, translation);
443             final var value = projectiveParameters[HOM_COORDS - 1];
444             t.multiplyByScalar(value);
445 
446             t.setSubmatrix(HOM_COORDS - 1, 0, HOM_COORDS - 1,
447                     HOM_COORDS - 1, projectiveParameters);
448         } catch (final WrongSizeException ignore) {
449             // never happens
450         }
451         normalize();
452     }
453 
454     /**
455      * Creates transformation by estimating its internal matrix by providing 5
456      * corresponding original and transformed points.
457      *
458      * @param inputPoint1  1st input point.
459      * @param inputPoint2  2nd input point.
460      * @param inputPoint3  3rd input point.
461      * @param inputPoint4  4th input point.
462      * @param inputPoint5  5th input point.
463      * @param outputPoint1 1st transformed point corresponding to 1st input
464      *                     point.
465      * @param outputPoint2 2nd transformed point corresponding to 2nd input
466      *                     point.
467      * @param outputPoint3 3rd transformed point corresponding to 3rd input
468      *                     point.
469      * @param outputPoint4 4th transformed point corresponding to 4th input
470      *                     point.
471      * @param outputPoint5 5th transformed point corresponding to 5th input
472      *                     point.
473      * @throws CoincidentPointsException raised if transformation cannot be
474      *                                   estimated for some reason (point configuration degeneracy, duplicate
475      *                                   points or numerical instabilities).
476      */
477     public ProjectiveTransformation3D(
478             final Point3D inputPoint1, final Point3D inputPoint2, final Point3D inputPoint3, final Point3D inputPoint4,
479             final Point3D inputPoint5, final Point3D outputPoint1, final Point3D outputPoint2,
480             final Point3D outputPoint3, final Point3D outputPoint4, final Point3D outputPoint5)
481             throws CoincidentPointsException {
482         try {
483             t = new Matrix(HOM_COORDS, HOM_COORDS);
484         } catch (final WrongSizeException ignore) {
485             // never happens
486         }
487         setTransformationFromPoints(inputPoint1, inputPoint2, inputPoint3, inputPoint4, inputPoint5, outputPoint1,
488                 outputPoint2, outputPoint3, outputPoint4, outputPoint5);
489     }
490 
491     /**
492      * Creates transformation by estimating its internal matrix by providing 5
493      * corresponding original and transformed planes.
494      *
495      * @param inputPlane1  1st input plane.
496      * @param inputPlane2  2nd input plane.
497      * @param inputPlane3  3rd input plane.
498      * @param inputPlane4  4th input plane.
499      * @param inputPlane5  5th input plane.
500      * @param outputPlane1 1st transformed plane corresponding to 1st input
501      *                     plane.
502      * @param outputPlane2 2nd transformed plane corresponding to 2nd input
503      *                     plane.
504      * @param outputPlane3 3rd transformed plane corresponding to 3rd input
505      *                     plane.
506      * @param outputPlane4 4th transformed plane corresponding to 4th input
507      *                     plane.
508      * @param outputPlane5 5th transformed plane corresponding to 5th input
509      *                     plane.
510      * @throws CoincidentPlanesException raised if transformation cannot be
511      *                                   estimated for some reason (plane configuration degeneracy, duplicate
512      *                                   planes or numerical instabilities).
513      */
514     public ProjectiveTransformation3D(
515             final Plane inputPlane1, final Plane inputPlane2, final Plane inputPlane3, final Plane inputPlane4,
516             final Plane inputPlane5, final Plane outputPlane1, final Plane outputPlane2, final Plane outputPlane3,
517             final Plane outputPlane4, final Plane outputPlane5) throws CoincidentPlanesException {
518         setTransformationFromPlanes(inputPlane1, inputPlane2, inputPlane3, inputPlane4, inputPlane5, outputPlane1,
519                 outputPlane2, outputPlane3, outputPlane4, outputPlane5);
520     }
521 
522     /**
523      * Creates transformation by estimating its internal matrix by providing 3
524      * corresponding original and transformed lines.
525      *
526      * @param inputLine1  1st input line.
527      * @param inputLine2  2nd input line.
528      * @param inputLine3  3rd input line.
529      * @param outputLine1 1st transformed line corresponding to 1st input line.
530      * @param outputLine2 2nd transformed line corresponding to 2nd input line.
531      * @param outputLine3 3rd transformed line corresponding to 3rd input line.
532      * @throws CoincidentLinesException raised if transformation cannot be
533      *                                  estimated for some reason (line configuration degeneracy, duplicate lines
534      *                                  or numerical instabilities).
535      */
536     public ProjectiveTransformation3D(
537             final Line3D inputLine1, final Line3D inputLine2, final Line3D inputLine3, final Line3D outputLine1,
538             final Line3D outputLine2, final Line3D outputLine3) throws CoincidentLinesException {
539         setTransformationFromLines(inputLine1, inputLine2, inputLine3, outputLine1, outputLine2, outputLine3);
540     }
541 
542     /**
543      * Returns internal matrix containing this transformation data.
544      * Point transformation is computed as t * x, where x is a 3D point
545      * expressed using homogeneous coordinates.
546      * Usually the internal transformation matrix will be invertible.
547      * When this is not the case, the transformation is considered degenerate
548      * and its inverse will not be available.
549      *
550      * @return internal transformation matrix.
551      */
552     public Matrix getT() {
553         return t;
554     }
555 
556     /**
557      * Sets internal matrix containing this transformation data.
558      * Point transformation is computed as t * x, where x is a 3D point
559      * expressed using homogeneous coordinates.
560      * Usually provided matrix will be invertible, when this is not the case
561      * this transformation will become degenerate and its inverse will not be
562      * available.
563      * This method does not check whether provided matrix is invertible or not.
564      *
565      * @param t transformation matrix.
566      * @throws NullPointerException     raised if provided matrix is null.
567      * @throws IllegalArgumentException raised if provided matrix is not 4x4.
568      */
569     public final void setT(final Matrix t) {
570         if (t.getRows() != HOM_COORDS || t.getColumns() != HOM_COORDS) {
571             throw new IllegalArgumentException();
572         }
573 
574         this.t = t;
575         normalized = false;
576     }
577 
578     /**
579      * Returns boolean indicating whether provided matrix will produce a
580      * degenerate projective transformation or not.
581      *
582      * @param t a 4x4 matrix to be used as the internal matrix of a projective
583      *          transformation.
584      * @return true if matrix will produce a degenerate transformation, false
585      * otherwise.
586      * @throws IllegalArgumentException raised if provided matrix is not 4x4.
587      */
588     public static boolean isDegenerate(final Matrix t) {
589         if (t.getRows() != HOM_COORDS || t.getColumns() != HOM_COORDS) {
590             throw new IllegalArgumentException();
591         }
592 
593         try {
594             final var decomposer = new LUDecomposer(t);
595             decomposer.decompose();
596             return decomposer.isSingular();
597         } catch (final AlgebraException e) {
598             // if decomposition fails, assume that matrix is degenerate because
599             // of numerical instabilities
600             return true;
601         }
602     }
603 
604     /**
605      * Indicates whether this transformation is degenerate.
606      * When a transformation is degenerate, its inverse cannot be computed.
607      *
608      * @return true if transformation is degenerate, false otherwise.
609      */
610     public boolean isDegenerate() {
611         return isDegenerate(t);
612     }
613 
614     /**
615      * Returns affine linear mapping matrix.
616      *
617      * @return linear mapping matrix.
618      * @see AffineTransformation3D
619      */
620     public Matrix getA() {
621         final var a = t.getSubmatrix(0, 0, INHOM_COORDS - 1,
622                 INHOM_COORDS - 1);
623         a.multiplyByScalar(1.0 / t.getElementAt(HOM_COORDS - 1, HOM_COORDS - 1));
624         return a;
625     }
626 
627     /**
628      * Sets affine linear mapping matrix.
629      *
630      * @param a Linear mapping matrix.
631      * @throws NullPointerException     raised if provided matrix is null.
632      * @throws IllegalArgumentException raised if provided matrix does not have
633      *                                  size 3x3.
634      * @see AffineTransformation3D
635      */
636     public final void setA(final Matrix a) {
637         if (a == null) {
638             throw new NullPointerException();
639         }
640         if (a.getRows() != INHOM_COORDS || a.getColumns() != INHOM_COORDS) {
641             throw new IllegalArgumentException();
642         }
643 
644         t.setSubmatrix(0, 0, INHOM_COORDS - 1,
645                 INHOM_COORDS - 1, a.multiplyByScalarAndReturnNew(t.getElementAt(HOM_COORDS - 1,
646                         HOM_COORDS - 1)));
647         normalized = false;
648     }
649 
650     /**
651      * Normalizes current matrix instance.
652      */
653     public final void normalize() {
654         if (!normalized) {
655             final var norm = Utils.normF(t);
656             if (norm > EPS) {
657                 t.multiplyByScalar(1.0 / norm);
658             }
659             normalized = true;
660         }
661     }
662 
663     /**
664      * Returns the 3D rotation component associated to this transformation.
665      * Note: if this rotation instance is modified, its changes won't be
666      * reflected on this transformation until rotation is set again.
667      *
668      * @return 3D rotation.
669      * @throws AlgebraException if for some reason rotation cannot be estimated
670      *                          (usually because of numerical instability).
671      */
672     public Rotation3D getRotation() throws AlgebraException {
673         // Use QR decomposition to retrieve rotation component of this
674         // transformation
675         normalize();
676         final var decomposer = new RQDecomposer(t.getSubmatrix(0, 0,
677                 INHOM_COORDS - 1, INHOM_COORDS - 1));
678         try {
679             decomposer.decompose();
680             //a large threshold is used because Q matrix is always assumed to be orthonormal
681             return new MatrixRotation3D(decomposer.getQ(), LARGE_ROTATION_MATRIX_THRESHOLD);
682         } catch (final InvalidRotationMatrixException ignore) {
683             return null;
684         }
685     }
686 
687     /**
688      * Sets 3D rotation for this transformation.
689      *
690      * @param rotation a 3D rotation.
691      * @throws NullPointerException raised if provided rotation is null.
692      * @throws AlgebraException     raised if for numerical reasons rotation cannot
693      *                              be set (usually because of numerical instability in parameters of this
694      *                              transformation).
695      */
696     public void setRotation(final Rotation3D rotation) throws AlgebraException {
697         final var rotMatrix = rotation.asInhomogeneousMatrix();
698 
699         // Use QR decomposition to retrieve parameters matrix
700         final var decomposer = new RQDecomposer(t.getSubmatrix(0, 0,
701                 INHOM_COORDS - 1, INHOM_COORDS - 1));
702         decomposer.decompose();
703         // retrieves params matrix
704         final var localA = decomposer.getR();
705         localA.multiply(rotMatrix);
706         t.setSubmatrix(0, 0, INHOM_COORDS - 1, INHOM_COORDS - 1,
707                 localA);
708         normalized = false;
709     }
710 
711     /**
712      * Adds provided rotation to current rotation assigned to this
713      * transformation.
714      *
715      * @param rotation 3D rotation to be added.
716      * @throws AlgebraException raised if for numerical reasons rotation cannot
717      *                          be set (usually because of numerical instability in parameters of this
718      *                          transformation).
719      */
720     public void addRotation(final Rotation3D rotation) throws AlgebraException {
721         final var localRotation = getRotation();
722         localRotation.combine(rotation);
723         setRotation(localRotation);
724     }
725 
726     /**
727      * Sets scale of this transformation.
728      *
729      * @param scale scale value to be set. A value between 0.0 and 1.0 indicates
730      *              that objects will be reduced, a value greater than 1.0 indicates that
731      *              objects will be enlarged, and a negative value indicates that objects
732      *              will be reversed.
733      * @throws AlgebraException Raised if for numerical reasons scale cannot
734      *                          be set (usually because of numerical instability in parameters of this
735      *                          transformation).
736      */
737     public void setScale(final double scale) throws AlgebraException {
738         normalize();
739         final var value = t.getElementAt(HOM_COORDS - 1, HOM_COORDS - 1);
740         final var decomposer = new RQDecomposer(t.getSubmatrix(0, 0,
741                 INHOM_COORDS - 1, INHOM_COORDS - 1));
742         decomposer.decompose();
743         // params
744         final var localA = decomposer.getR();
745         localA.setElementAt(0, 0, scale * value);
746         localA.setElementAt(1, 1, scale * value);
747         localA.setElementAt(2, 2, scale * value);
748         localA.multiply(decomposer.getQ());
749         t.setSubmatrix(0, 0, INHOM_COORDS - 1, INHOM_COORDS - 1,
750                 localA);
751         normalized = false;
752     }
753 
754     /**
755      * Gets affine parameters of associated to this instance.
756      * Affine parameters contain horizontal scale, vertical scale and skewness
757      * of axes.
758      *
759      * @return affine parameters.
760      * @throws AlgebraException raised if for numerical reasons affine.
761      *                          parameters cannot be retrieved (usually because of numerical instability
762      *                          of the internal matrix of this instance).
763      */
764     public AffineParameters3D getAffineParameters() throws AlgebraException {
765         final var parameters = new AffineParameters3D();
766         getAffineParameters(parameters);
767         return parameters;
768     }
769 
770     /**
771      * Computes affine parameters associated to this instance and stores the
772      * result in provided instance.
773      * Affine parameters contain horizontal scale, vertical scale and skewness
774      * of axes.
775      *
776      * @param result instance where affine parameters will be stored.
777      * @throws AlgebraException raised if for numerical reasons affine
778      *                          parameters cannot be retrieved (usually because of numerical instability
779      *                          of the internal matrix of this instance).
780      */
781     public void getAffineParameters(final AffineParameters3D result) throws AlgebraException {
782         normalize();
783         final var value = t.getElementAt(HOM_COORDS - 1, HOM_COORDS - 1);
784         final var decomposer = new RQDecomposer(t.getSubmatrix(0, 0,
785                 INHOM_COORDS - 1, INHOM_COORDS - 1));
786         decomposer.decompose();
787         final var r = decomposer.getR();
788         r.multiplyByScalar(1.0 / value);
789         result.fromMatrix(r);
790     }
791 
792     /**
793      * Sets affine parameters associated to this instance.
794      * Affine parameters contain horizontal scale, vertical scale and skewness
795      * of axes.
796      *
797      * @param parameters affine parameters to be set.
798      * @throws AlgebraException raised if for numerical reasons affine
799      *                          parameters cannot be set (usually because of numerical instability of
800      *                          the internal matrix of this instance).
801      */
802     public void setAffineParameters(final AffineParameters3D parameters) throws AlgebraException {
803         normalize();
804         final var value = t.getElementAt(HOM_COORDS - 1, HOM_COORDS - 1);
805         final var decomposer = new RQDecomposer(t.getSubmatrix(0, 0,
806                 INHOM_COORDS - 1, INHOM_COORDS - 1));
807         decomposer.decompose();
808         final var params = parameters.asMatrix();
809         final var rotation = decomposer.getQ();
810 
811         // params is equivalent to A because it
812         // has been multiplied by rotation
813         params.multiply(rotation);
814         // normalize
815         params.multiplyByScalar(value);
816         t.setSubmatrix(0, 0, INHOM_COORDS - 1, INHOM_COORDS - 1,
817                 params);
818         normalized = false;
819     }
820 
821     /**
822      * Returns the projective parameters associated to this instance.
823      * These parameters are the located in the last row of the internal
824      * transformation matrix.
825      * For affine, metric or Euclidean transformations this last row is always
826      * [0, 0, 0, 1] (taking into account that transformation matrix is defined
827      * up to scale).
828      *
829      * @return Projective parameters returned as the array containing the values
830      * of the last row of the internal transformation matrix.
831      */
832     public double[] getProjectiveParameters() {
833         // return last row of matrix t
834         return t.getSubmatrixAsArray(HOM_COORDS - 1, 0, HOM_COORDS - 1,
835                 HOM_COORDS - 1, true);
836     }
837 
838     /**
839      * Sets the projective parameters associated to this instance.
840      * These parameters will be set in the last row of the internal
841      * transformation matrix.
842      * For affine, matrix or Euclidean transformations parameters are always
843      * [0, 0, 0, 1] (taking into account that transformation matrix is defined
844      * up to scale).
845      *
846      * @param params projective parameters to be set. It must be an array of
847      *               length 4.
848      * @throws IllegalArgumentException raised if provided array does not have
849      *                                  length 4.
850      */
851     public final void setProjectiveParameters(final double[] params) {
852         if (params.length != HOM_COORDS) {
853             throw new IllegalArgumentException();
854         }
855 
856         t.setSubmatrix(HOM_COORDS - 1, 0, HOM_COORDS - 1,
857                 HOM_COORDS - 1, params);
858         normalized = false;
859     }
860 
861     /**
862      * Returns 3D translation assigned to this transformation as a new array
863      * expressed in inhomogeneous coordinates.
864      * Note: Updating the values of the returned array will not update the
865      * translation of this instance. To do so, translation needs to be set
866      * again.
867      *
868      * @return 3D translation array.
869      */
870     public double[] getTranslation() {
871         normalize();
872         final var translation = t.getSubmatrixAsArray(0, HOM_COORDS - 1,
873                 INHOM_COORDS - 1, HOM_COORDS - 1);
874         final var value = t.getElementAt(HOM_COORDS - 1, HOM_COORDS - 1);
875         ArrayUtils.multiplyByScalar(translation, 1.0 / value, translation);
876         return translation;
877     }
878 
879     /**
880      * Obtains 3D translation assigned to this transformation and stores result
881      * into provided array.
882      * Note: Updating the values of the returned array will not update the
883      * translation of this instance. To do so, translation needs to be set
884      * again.
885      *
886      * @param out array where translation values will be stored.
887      * @throws WrongSizeException if provided array does not have length 3.
888      */
889     public void getTranslation(final double[] out) throws WrongSizeException {
890         t.getSubmatrixAsArray(0, HOM_COORDS - 1, INHOM_COORDS - 1,
891                 HOM_COORDS - 1, out);
892         final var value = t.getElementAt(HOM_COORDS - 1, HOM_COORDS - 1);
893         ArrayUtils.multiplyByScalar(out, 1.0 / value, out);
894     }
895 
896     /**
897      * Sets 3D translation assigned to this transformation as an array expressed
898      * in inhomogeneous coordinates.
899      *
900      * @param translation 3D translation array.
901      * @throws IllegalArgumentException raised if provided array does not have
902      *                                  length equal to NUM_TRANSLATION_COORDS.
903      */
904     public void setTranslation(final double[] translation) {
905         if (translation.length != NUM_TRANSLATION_COORDS) {
906             throw new IllegalArgumentException();
907         }
908 
909         final var value = t.getElementAt(HOM_COORDS - 1, HOM_COORDS - 1);
910         final var translation2 = ArrayUtils.multiplyByScalarAndReturnNew(translation, value);
911         t.setSubmatrix(0, HOM_COORDS - 1, translation2.length - 1,
912                 HOM_COORDS - 1, translation2);
913         normalized = false;
914     }
915 
916     /**
917      * Adds provided translation to current translation on this transformation.
918      * Provided translation must be expressed as an array of inhomogeneous
919      * coordinates.
920      *
921      * @param translation 3D translation array.
922      * @throws IllegalArgumentException raised if provided array does not have
923      *                                  length equal to NUM_TRANSLATION_COORDS.
924      */
925     public void addTranslation(final double[] translation) {
926         final var currentTranslation = getTranslation();
927         ArrayUtils.sum(currentTranslation, translation, currentTranslation);
928         setTranslation(currentTranslation);
929     }
930 
931     /**
932      * Returns current x coordinate translation assigned to this transformation.
933      *
934      * @return X coordinate translation.
935      */
936     public double getTranslationX() {
937         normalize();
938         return t.getElementAt(0, HOM_COORDS - 1) / t.getElementAt(
939                 HOM_COORDS - 1, HOM_COORDS - 1);
940     }
941 
942     /**
943      * Sets x coordinate translation to be made by this transformation.
944      *
945      * @param translationX X coordinate translation to be set.
946      */
947     public void setTranslationX(final double translationX) {
948         t.setElementAt(0, HOM_COORDS - 1, translationX * t.getElementAt(
949                 HOM_COORDS - 1, HOM_COORDS - 1));
950         normalized = false;
951     }
952 
953     /**
954      * Returns current y coordinate translation assigned to this transformation.
955      *
956      * @return Y coordinate translation.
957      */
958     public double getTranslationY() {
959         normalize();
960         return t.getElementAt(1, HOM_COORDS - 1) / t.getElementAt(
961                 HOM_COORDS - 1, HOM_COORDS - 1);
962     }
963 
964     /**
965      * Sets y coordinate translation to be made by this transformation.
966      *
967      * @param translationY Y coordinate translation to be set.
968      */
969     public void setTranslationY(final double translationY) {
970         t.setElementAt(1, HOM_COORDS - 1, translationY * t.getElementAt(
971                 HOM_COORDS - 1, HOM_COORDS - 1));
972         normalized = false;
973     }
974 
975     /**
976      * Returns current z coordinate translation assigned to this transformation.
977      *
978      * @return Z coordinate translation.
979      */
980     public double getTranslationZ() {
981         normalize();
982         return t.getElementAt(2, HOM_COORDS - 1) / t.getElementAt(
983                 HOM_COORDS - 1, HOM_COORDS - 1);
984     }
985 
986     /**
987      * Sets z coordinate translation to be made by this transformation.
988      *
989      * @param translationZ z coordinate translation to be set.
990      */
991     public void setTranslationZ(final double translationZ) {
992         t.setElementAt(2, HOM_COORDS - 1, translationZ * t.getElementAt(
993                 HOM_COORDS - 1, HOM_COORDS - 1));
994         normalized = false;
995     }
996 
997     /**
998      * Sets x, y, z coordinates of translation to be made by this
999      * transformation.
1000      *
1001      * @param translationX translation x coordinate to be set.
1002      * @param translationY translation y coordinate to be set.
1003      * @param translationZ translation z coordinate to be set.
1004      */
1005     public void setTranslation(final double translationX, final double translationY, final double translationZ) {
1006         setTranslationX(translationX);
1007         setTranslationY(translationY);
1008         setTranslationZ(translationZ);
1009     }
1010 
1011     /**
1012      * Sets x, y, z coordinates of translation to be made by this
1013      * transformation.
1014      *
1015      * @param translation translation to be set.
1016      */
1017     public void setTranslation(final Point3D translation) {
1018         setTranslation(translation.getInhomX(), translation.getInhomY(), translation.getInhomZ());
1019     }
1020 
1021     /**
1022      * Gets x, y, z coordinates of translation to be made by this transformation
1023      * as a new point.
1024      *
1025      * @return a new point containing translation coordinates.
1026      */
1027     public Point3D getTranslationPoint() {
1028         final var out = Point3D.create();
1029         getTranslationPoint(out);
1030         return out;
1031     }
1032 
1033     /**
1034      * Gets x, y, z coordinates of translation to be made by this transformation
1035      * and stores them into provided point.
1036      *
1037      * @param out point where translation coordinates will be stored.
1038      */
1039     public void getTranslationPoint(final Point3D out) {
1040         out.setInhomogeneousCoordinates(getTranslationX(), getTranslationY(), getTranslationZ());
1041     }
1042 
1043     /**
1044      * Adds provided x coordinate to current translation assigned to this
1045      * transformation.
1046      *
1047      * @param translationX X coordinate to be added to current translation.
1048      */
1049     public void addTranslationX(final double translationX) {
1050         setTranslationX(getTranslationX() + translationX);
1051     }
1052 
1053     /**
1054      * Adds provided y coordinate to current translation assigned to this
1055      * transformation.
1056      *
1057      * @param translationY Y coordinate to be added to current translation.
1058      */
1059     public void addTranslationY(final double translationY) {
1060         setTranslationY(getTranslationY() + translationY);
1061     }
1062 
1063     /**
1064      * Adds provided z coordinate to current translation assigned to this
1065      * transformation.
1066      *
1067      * @param translationZ Z coordinate to be added to current translation.
1068      */
1069     public void addTranslationZ(final double translationZ) {
1070         setTranslationZ(getTranslationZ() + translationZ);
1071     }
1072 
1073     /**
1074      * Adds provided coordinates to current translation assigned to this
1075      * transformation.
1076      *
1077      * @param translationX x coordinate to be added to current translation.
1078      * @param translationY y coordinate to be added to current translation.
1079      * @param translationZ z coordinate to be added to current translation.
1080      */
1081     public void addTranslation(final double translationX, final double translationY, final double translationZ) {
1082         addTranslationX(translationX);
1083         addTranslationY(translationY);
1084         addTranslationZ(translationZ);
1085     }
1086 
1087     /**
1088      * Adds provided coordinates to current translation assigned to this
1089      * transformation.
1090      *
1091      * @param translation x, y, z coordinates to be added to current
1092      *                    translation.
1093      */
1094     public void addTranslation(final Point3D translation) {
1095         addTranslation(translation.getInhomX(), translation.getInhomY(), translation.getInhomZ());
1096     }
1097 
1098     /**
1099      * Represents this transformation as a 4x4 matrix.
1100      * A point can be transformed as t * p, where t is the transformation matrix
1101      * and p is a point expressed as an homogeneous vector.
1102      *
1103      * @return This transformation in matrix form.
1104      */
1105     @Override
1106     public Matrix asMatrix() {
1107         return new Matrix(t);
1108     }
1109 
1110     /**
1111      * Represents this transformation as a 4x4 matrix and stores the result in
1112      * provided instance.
1113      *
1114      * @param m Instance where transformation matrix will be stored.
1115      * @throws IllegalArgumentException Raised if provided instance is not a 4x4
1116      *                                  matrix.
1117      */
1118     @Override
1119     public void asMatrix(final Matrix m) {
1120         if (m.getRows() != HOM_COORDS || m.getColumns() != HOM_COORDS) {
1121             throw new IllegalArgumentException();
1122         }
1123 
1124         m.copyFrom(t);
1125     }
1126 
1127     /**
1128      * Transforms input point using this transformation and stores the result in
1129      * provided output points.
1130      *
1131      * @param inputPoint  point to be transformed.
1132      * @param outputPoint Instance where transformed point data will be stored.
1133      */
1134     @Override
1135     public void transform(final Point3D inputPoint, final Point3D outputPoint) {
1136         inputPoint.normalize();
1137         normalize();
1138         try {
1139             final var point = new Matrix(Point3D.POINT3D_HOMOGENEOUS_COORDINATES_LENGTH, 1);
1140             point.setElementAtIndex(0, inputPoint.getHomX());
1141             point.setElementAtIndex(1, inputPoint.getHomY());
1142             point.setElementAtIndex(2, inputPoint.getHomZ());
1143             point.setElementAtIndex(3, inputPoint.getHomW());
1144 
1145             final var transformedPoint = t.multiplyAndReturnNew(point);
1146 
1147             outputPoint.setHomogeneousCoordinates(
1148                     transformedPoint.getElementAtIndex(0),
1149                     transformedPoint.getElementAtIndex(1),
1150                     transformedPoint.getElementAtIndex(2),
1151                     transformedPoint.getElementAtIndex(3));
1152         } catch (final WrongSizeException ignore) {
1153             // never happens
1154         }
1155     }
1156 
1157     /**
1158      * Transforms a quadric using this transformation and stores the result into
1159      * provided output quadric.
1160      *
1161      * @param inputQuadric  quadric to be transformed.
1162      * @param outputQuadric instance where data of transformed quadric will be
1163      *                      stored.
1164      * @throws NonSymmetricMatrixException Raised if due to numerical precision
1165      *                                     the resulting output quadric matrix is not considered to be symmetric.
1166      * @throws AlgebraException            raised if transform cannot be computed because of
1167      *                                     numerical instabilities.
1168      */
1169     @Override
1170     public void transform(final Quadric inputQuadric, final Quadric outputQuadric) throws NonSymmetricMatrixException,
1171             AlgebraException {
1172         // point' * quadric * point = 0
1173         // point' * t' * transformedQuadric * t * point = 0
1174         // where:
1175         // - transformedPoint = t * point
1176 
1177         // Hence:
1178         // transformedQuadric = t^-1' * quadric * t^-1
1179 
1180         inputQuadric.normalize();
1181 
1182         final var q = inputQuadric.asMatrix();
1183         normalize();
1184 
1185         final var invT = inverseAndReturnNew().asMatrix();
1186         // normalize transformation matrix invT to increase accuracy
1187         var norm = Utils.normF(invT);
1188         invT.multiplyByScalar(1.0 / norm);
1189 
1190         final var m = invT.transposeAndReturnNew();
1191         try {
1192             m.multiply(q);
1193             m.multiply(invT);
1194         } catch (final WrongSizeException ignore) {
1195             // never happens
1196         }
1197 
1198         // normalize resulting m matrix to increase accuracy so that it can be
1199         // considered symmetric
1200         norm = Utils.normF(m);
1201         m.multiplyByScalar(1.0 / norm);
1202 
1203         outputQuadric.setParameters(m);
1204     }
1205 
1206     /**
1207      * Transforms a dual quadric using this transformation and stores the result
1208      * into provided output dual quadric.
1209      *
1210      * @param inputDualQuadric  dual quadric to be transformed.
1211      * @param outputDualQuadric instance where data of transformed dual quadric
1212      *                          will be stored.
1213      * @throws NonSymmetricMatrixException raised if due to numerical precision
1214      *                                     the resulting output dual quadric matrix is not considered to be
1215      *                                     symmetric.
1216      * @throws AlgebraException            raised if transform cannot be computed because
1217      *                                     of numerical instabilities.
1218      */
1219     @Override
1220     public void transform(final DualQuadric inputDualQuadric, final DualQuadric outputDualQuadric)
1221             throws NonSymmetricMatrixException, AlgebraException {
1222         // plane' * dualQuadric * plane = 0
1223         // plane' * t^-1 * t * dualQuadric * t' * t^-1'*plane
1224 
1225         // Hence:
1226         // transformed plane: t^-1'*plane
1227         // transformed dual quadric: t * dualQuadric * t'
1228 
1229         inputDualQuadric.normalize();
1230         normalize();
1231 
1232         final var dualQ = inputDualQuadric.asMatrix();
1233         final var transT = t.transposeAndReturnNew();
1234 
1235         final var m = t.multiplyAndReturnNew(dualQ);
1236         m.multiply(transT);
1237 
1238         // normalize resulting m matrix to increase accuracy so that it can be
1239         // considered symmetric
1240         final var norm = Utils.normF(m);
1241         m.multiplyByScalar(1.0 / norm);
1242 
1243         outputDualQuadric.setParameters(m);
1244     }
1245 
1246     /**
1247      * Transforms provided input plane using this transformation and stores the
1248      * result into provided output plane instance.
1249      *
1250      * @param inputPlane  plane to be transformed.
1251      * @param outputPlane instance where data of transformed plane will be
1252      *                    stored.
1253      * @throws AlgebraException raised if transform cannot be computed because
1254      *                          of numerical instabilities.
1255      */
1256     @Override
1257     public void transform(final Plane inputPlane, final Plane outputPlane) throws AlgebraException {
1258         // plane' * point = 0 --> plane' * t^-1 * t * point
1259         // (plane' * t^-1)*(t*point) = (t^-1'*plane)'*(t*point)
1260         // where:
1261         // - transformedPlane = t^-1'*plane
1262         // - transformedPoint = t*point
1263 
1264 
1265         inputPlane.normalize();
1266         normalize();
1267 
1268         final var invT = inverseAndReturnNew().asMatrix();
1269         final var l = Matrix.newFromArray(inputPlane.asArray());
1270 
1271         invT.transpose();
1272         invT.multiply(l);
1273 
1274         outputPlane.setParameters(invT.toArray());
1275     }
1276 
1277     /**
1278      * Transforms a camera using this transformation and stores the result into
1279      * provided output camera.
1280      *
1281      * @param inputCamera  camera to be transformed.
1282      * @param outputCamera instance where data of transformed camera will be
1283      *                     stored.
1284      * @throws AlgebraException raised if transform cannot be computed because
1285      *                          of numerical instabilities.
1286      */
1287     @Override
1288     public void transform(final PinholeCamera inputCamera, final PinholeCamera outputCamera) throws AlgebraException {
1289 
1290         inputCamera.normalize();
1291         normalize();
1292 
1293         final var invT = inverseAndReturnNew().asMatrix();
1294         final var c = inputCamera.getInternalMatrix();
1295         c.multiply(invT);
1296         outputCamera.setInternalMatrix(c);
1297     }
1298 
1299     /**
1300      * Inverses this transformation.
1301      *
1302      * @throws AlgebraException if inverse transform cannot be computed because
1303      *                          of numerical instabilities.
1304      */
1305     public void inverse() throws AlgebraException {
1306         inverse(this);
1307     }
1308 
1309     /**
1310      * Computes the inverse of this transformation and returns the result as a
1311      * new transformation instance.
1312      *
1313      * @return inverse transformation.
1314      * @throws AlgebraException if inverse transform cannot be computed because
1315      *                          of numerical instabilities.
1316      */
1317     public Transformation3D inverseAndReturnNew() throws AlgebraException {
1318         final var result = new ProjectiveTransformation3D();
1319         inverse(result);
1320         return result;
1321     }
1322 
1323     /**
1324      * Computes the inverse of this transformation and stores the result in
1325      * provided instance.
1326      *
1327      * @param result instance where inverse transformation will be stored.
1328      * @throws AlgebraException if inverse transformAndReturnNew cannot be
1329      *                          computed because of numerical instabilities.
1330      */
1331     protected void inverse(final ProjectiveTransformation3D result) throws AlgebraException {
1332         result.t = Utils.inverse(t);
1333         result.normalized = false;
1334     }
1335 
1336     /**
1337      * Combines this transformation with provided transformation.
1338      * The combination is equivalent to multiplying the matrix of this
1339      * transformation with the matrix of provided transformation.
1340      *
1341      * @param transformation transformation to be combined with.
1342      */
1343     public void combine(final ProjectiveTransformation3D transformation) {
1344         combine(transformation, this);
1345     }
1346 
1347     /**
1348      * Combines this transformation with provided transformation and returns
1349      * the result as a new transformation instance.
1350      * The combination is equivalent to multiplying the matrix of this
1351      * transformation with the matrix of provided transformation.
1352      *
1353      * @param transformation transformation to be combined with.
1354      * @return a new transformation resulting of the combination with this
1355      * transformation and provided transformation.
1356      */
1357     public ProjectiveTransformation3D combineAndReturnNew(final ProjectiveTransformation3D transformation) {
1358         final var result = new ProjectiveTransformation3D();
1359         combine(transformation, result);
1360         return result;
1361     }
1362 
1363     /**
1364      * Combines this transformation with provided input transformation and
1365      * stores the result into provided output transformation.
1366      * The combination is equivalent to multiplying the matrix of this
1367      * transformation with the matrix of provided input transformation.
1368      *
1369      * @param inputTransformation  transformation to be combined with.
1370      * @param outputTransformation transformation where result will be stored.
1371      */
1372     private void combine(final ProjectiveTransformation3D inputTransformation,
1373                          final ProjectiveTransformation3D outputTransformation) {
1374         // combination in matrix representation is: T1 * T2
1375 
1376         normalize();
1377         inputTransformation.normalize();
1378 
1379         try {
1380             outputTransformation.t = this.t.multiplyAndReturnNew(inputTransformation.t);
1381             outputTransformation.normalized = false;
1382         } catch (final WrongSizeException ignore) {
1383             // never happens
1384         }
1385     }
1386 
1387     /**
1388      * Estimates this transformation internal matrix by providing 5
1389      * corresponding original and transformed points.
1390      *
1391      * @param inputPoint1  1st input point.
1392      * @param inputPoint2  2nd input point.
1393      * @param inputPoint3  3rd input point.
1394      * @param inputPoint4  4th input point.
1395      * @param inputPoint5  5th input point.
1396      * @param outputPoint1 1st transformed point corresponding to 1st input
1397      *                     point.
1398      * @param outputPoint2 2nd transformed point corresponding to 2nd input
1399      *                     point.
1400      * @param outputPoint3 3rd transformed point corresponding to 3rd input
1401      *                     point.
1402      * @param outputPoint4 4th transformed point corresponding to 4th input
1403      *                     point.
1404      * @param outputPoint5 5th transformed point corresponding to 5th input
1405      *                     point.
1406      * @throws CoincidentPointsException Raised if transformation cannot be
1407      *                                   estimated for some reason (point configuration degeneracy, duplicate
1408      *                                   points or numerical instabilities).
1409      */
1410     public final void setTransformationFromPoints(
1411             final Point3D inputPoint1, final Point3D inputPoint2, final Point3D inputPoint3, final Point3D inputPoint4,
1412             final Point3D inputPoint5, final Point3D outputPoint1, final Point3D outputPoint2,
1413             final Point3D outputPoint3, final Point3D outputPoint4, final Point3D outputPoint5)
1414             throws CoincidentPointsException {
1415 
1416         // normalize points to increase accuracy
1417         inputPoint1.normalize();
1418         inputPoint2.normalize();
1419         inputPoint3.normalize();
1420         inputPoint4.normalize();
1421         inputPoint5.normalize();
1422 
1423         outputPoint1.normalize();
1424         outputPoint2.normalize();
1425         outputPoint3.normalize();
1426         outputPoint4.normalize();
1427         outputPoint5.normalize();
1428 
1429         // matrix of homogeneous linear system of equations.
1430         // There are 16 unknowns and 15 equations (3 for each pair of
1431         // corresponding points)
1432         Matrix m = null;
1433         try {
1434             // build matrix initialized to zero
1435             m = new Matrix(15, 16);
1436 
1437             // 1st pair of points
1438             var iX = inputPoint1.getHomX();
1439             var iY = inputPoint1.getHomY();
1440             var iZ = inputPoint1.getHomZ();
1441             var iW = inputPoint1.getHomW();
1442 
1443             var oX = outputPoint1.getHomX();
1444             var oY = outputPoint1.getHomY();
1445             var oZ = outputPoint1.getHomZ();
1446             var oW = outputPoint1.getHomW();
1447 
1448             var oWiX = oW * iX;
1449             var oWiY = oW * iY;
1450             var oWiZ = oW * iZ;
1451             var oWiW = oW * iW;
1452 
1453             var oXiX = oX * iX;
1454             var oXiY = oX * iY;
1455             var oXiZ = oX * iZ;
1456             var oXiW = oX * iW;
1457 
1458             var oYiX = oY * iX;
1459             var oYiY = oY * iY;
1460             var oYiZ = oY * iZ;
1461             var oYiW = oY * iW;
1462 
1463             var oZiX = oZ * iX;
1464             var oZiY = oZ * iY;
1465             var oZiZ = oZ * iZ;
1466             var oZiW = oZ * iW;
1467 
1468             var tmp = oWiX * oWiX + oWiY * oWiY + oWiZ * oWiZ + oWiW * oWiW;
1469             var norm = Math.sqrt(tmp + oXiX * oXiX + oXiY * oXiY + oXiZ * oXiZ + oXiW * oXiW);
1470 
1471             m.setElementAt(0, 0, oWiX / norm);
1472             m.setElementAt(0, 1, oWiY / norm);
1473             m.setElementAt(0, 2, oWiZ / norm);
1474             m.setElementAt(0, 3, oWiW / norm);
1475 
1476             m.setElementAt(0, 12, -oXiX / norm);
1477             m.setElementAt(0, 13, -oXiY / norm);
1478             m.setElementAt(0, 14, -oXiZ / norm);
1479             m.setElementAt(0, 15, -oXiW / norm);
1480 
1481             norm = Math.sqrt(tmp + oYiX * oYiX + oYiY * oYiY + oYiZ * oYiZ + oYiW * oYiW);
1482 
1483             m.setElementAt(1, 4, oWiX / norm);
1484             m.setElementAt(1, 5, oWiY / norm);
1485             m.setElementAt(1, 6, oWiZ / norm);
1486             m.setElementAt(1, 7, oWiW / norm);
1487 
1488             m.setElementAt(1, 12, -oYiX / norm);
1489             m.setElementAt(1, 13, -oYiY / norm);
1490             m.setElementAt(1, 14, -oYiZ / norm);
1491             m.setElementAt(1, 15, -oYiW / norm);
1492 
1493             norm = Math.sqrt(tmp + oZiX * oZiX + oZiY * oZiY + oZiZ * oZiZ + oZiW * oZiW);
1494 
1495             m.setElementAt(2, 8, oWiX / norm);
1496             m.setElementAt(2, 9, oWiY / norm);
1497             m.setElementAt(2, 10, oWiZ / norm);
1498             m.setElementAt(2, 11, oWiW / norm);
1499 
1500             m.setElementAt(2, 12, -oZiX / norm);
1501             m.setElementAt(2, 13, -oZiY / norm);
1502             m.setElementAt(2, 14, -oZiZ / norm);
1503             m.setElementAt(2, 15, -oZiW / norm);
1504 
1505             // 2nd pair of points
1506             iX = inputPoint2.getHomX();
1507             iY = inputPoint2.getHomY();
1508             iZ = inputPoint2.getHomZ();
1509             iW = inputPoint2.getHomW();
1510 
1511             oX = outputPoint2.getHomX();
1512             oY = outputPoint2.getHomY();
1513             oZ = outputPoint2.getHomZ();
1514             oW = outputPoint2.getHomW();
1515 
1516             oWiX = oW * iX;
1517             oWiY = oW * iY;
1518             oWiZ = oW * iZ;
1519             oWiW = oW * iW;
1520 
1521             oXiX = oX * iX;
1522             oXiY = oX * iY;
1523             oXiZ = oX * iZ;
1524             oXiW = oX * iW;
1525 
1526             oYiX = oY * iX;
1527             oYiY = oY * iY;
1528             oYiZ = oY * iZ;
1529             oYiW = oY * iW;
1530 
1531             oZiX = oZ * iX;
1532             oZiY = oZ * iY;
1533             oZiZ = oZ * iZ;
1534             oZiW = oZ * iW;
1535 
1536             tmp = oWiX * oWiX + oWiY * oWiY + oWiZ * oWiZ + oWiW * oWiW;
1537             norm = Math.sqrt(tmp + oXiX * oXiX + oXiY * oXiY + oXiZ * oXiZ + oXiW * oXiW);
1538 
1539             m.setElementAt(3, 0, oWiX / norm);
1540             m.setElementAt(3, 1, oWiY / norm);
1541             m.setElementAt(3, 2, oWiZ / norm);
1542             m.setElementAt(3, 3, oWiW / norm);
1543 
1544             m.setElementAt(3, 12, -oXiX / norm);
1545             m.setElementAt(3, 13, -oXiY / norm);
1546             m.setElementAt(3, 14, -oXiZ / norm);
1547             m.setElementAt(3, 15, -oXiW / norm);
1548 
1549             norm = Math.sqrt(tmp + oYiX * oYiX + oYiY * oYiY + oYiZ * oYiZ + oYiW * oYiW);
1550 
1551             m.setElementAt(4, 4, oWiX / norm);
1552             m.setElementAt(4, 5, oWiY / norm);
1553             m.setElementAt(4, 6, oWiZ / norm);
1554             m.setElementAt(4, 7, oWiW / norm);
1555 
1556             m.setElementAt(4, 12, -oYiX / norm);
1557             m.setElementAt(4, 13, -oYiY / norm);
1558             m.setElementAt(4, 14, -oYiZ / norm);
1559             m.setElementAt(4, 15, -oYiW / norm);
1560 
1561             norm = Math.sqrt(tmp + oZiX * oZiX + oZiY * oZiY + oZiZ * oZiZ + oZiW * oZiW);
1562 
1563             m.setElementAt(5, 8, oWiX / norm);
1564             m.setElementAt(5, 9, oWiY / norm);
1565             m.setElementAt(5, 10, oWiZ / norm);
1566             m.setElementAt(5, 11, oWiW / norm);
1567 
1568             m.setElementAt(5, 12, -oZiX / norm);
1569             m.setElementAt(5, 13, -oZiY / norm);
1570             m.setElementAt(5, 14, -oZiZ / norm);
1571             m.setElementAt(5, 15, -oZiW / norm);
1572 
1573             // 3rd pair of points
1574             iX = inputPoint3.getHomX();
1575             iY = inputPoint3.getHomY();
1576             iZ = inputPoint3.getHomZ();
1577             iW = inputPoint3.getHomW();
1578 
1579             oX = outputPoint3.getHomX();
1580             oY = outputPoint3.getHomY();
1581             oZ = outputPoint3.getHomZ();
1582             oW = outputPoint3.getHomW();
1583 
1584             oWiX = oW * iX;
1585             oWiY = oW * iY;
1586             oWiZ = oW * iZ;
1587             oWiW = oW * iW;
1588 
1589             oXiX = oX * iX;
1590             oXiY = oX * iY;
1591             oXiZ = oX * iZ;
1592             oXiW = oX * iW;
1593 
1594             oYiX = oY * iX;
1595             oYiY = oY * iY;
1596             oYiZ = oY * iZ;
1597             oYiW = oY * iW;
1598 
1599             oZiX = oZ * iX;
1600             oZiY = oZ * iY;
1601             oZiZ = oZ * iZ;
1602             oZiW = oZ * iW;
1603 
1604             tmp = oWiX * oWiX + oWiY * oWiY + oWiZ * oWiZ + oWiW * oWiW;
1605             norm = Math.sqrt(tmp + oXiX * oXiX + oXiY * oXiY + oXiZ * oXiZ + oXiW * oXiW);
1606 
1607             m.setElementAt(6, 0, oWiX / norm);
1608             m.setElementAt(6, 1, oWiY / norm);
1609             m.setElementAt(6, 2, oWiZ / norm);
1610             m.setElementAt(6, 3, oWiW / norm);
1611 
1612             m.setElementAt(6, 12, -oXiX / norm);
1613             m.setElementAt(6, 13, -oXiY / norm);
1614             m.setElementAt(6, 14, -oXiZ / norm);
1615             m.setElementAt(6, 15, -oXiW / norm);
1616 
1617             norm = Math.sqrt(tmp + oYiX * oYiX + oYiY * oYiY + oYiZ * oYiZ + oYiW * oYiW);
1618 
1619             m.setElementAt(7, 4, oWiX / norm);
1620             m.setElementAt(7, 5, oWiY / norm);
1621             m.setElementAt(7, 6, oWiZ / norm);
1622             m.setElementAt(7, 7, oWiW / norm);
1623 
1624             m.setElementAt(7, 12, -oYiX / norm);
1625             m.setElementAt(7, 13, -oYiY / norm);
1626             m.setElementAt(7, 14, -oYiZ / norm);
1627             m.setElementAt(7, 15, -oYiW / norm);
1628 
1629             norm = Math.sqrt(tmp + oZiX * oZiX + oZiY * oZiY + oZiZ * oZiZ + oZiW * oZiW);
1630 
1631             m.setElementAt(8, 8, oWiX / norm);
1632             m.setElementAt(8, 9, oWiY / norm);
1633             m.setElementAt(8, 10, oWiZ / norm);
1634             m.setElementAt(8, 11, oWiW / norm);
1635 
1636             m.setElementAt(8, 12, -oZiX / norm);
1637             m.setElementAt(8, 13, -oZiY / norm);
1638             m.setElementAt(8, 14, -oZiZ / norm);
1639             m.setElementAt(8, 15, -oZiW / norm);
1640 
1641             // 4th pair of points
1642             iX = inputPoint4.getHomX();
1643             iY = inputPoint4.getHomY();
1644             iZ = inputPoint4.getHomZ();
1645             iW = inputPoint4.getHomW();
1646 
1647             oX = outputPoint4.getHomX();
1648             oY = outputPoint4.getHomY();
1649             oZ = outputPoint4.getHomZ();
1650             oW = outputPoint4.getHomW();
1651 
1652             oWiX = oW * iX;
1653             oWiY = oW * iY;
1654             oWiZ = oW * iZ;
1655             oWiW = oW * iW;
1656 
1657             oXiX = oX * iX;
1658             oXiY = oX * iY;
1659             oXiZ = oX * iZ;
1660             oXiW = oX * iW;
1661 
1662             oYiX = oY * iX;
1663             oYiY = oY * iY;
1664             oYiZ = oY * iZ;
1665             oYiW = oY * iW;
1666 
1667             oZiX = oZ * iX;
1668             oZiY = oZ * iY;
1669             oZiZ = oZ * iZ;
1670             oZiW = oZ * iW;
1671 
1672             tmp = oWiX * oWiX + oWiY * oWiY + oWiZ * oWiZ + oWiW * oWiW;
1673             norm = Math.sqrt(tmp + oXiX * oXiX + oXiY * oXiY + oXiZ * oXiZ + oXiW * oXiW);
1674 
1675             m.setElementAt(9, 0, oWiX / norm);
1676             m.setElementAt(9, 1, oWiY / norm);
1677             m.setElementAt(9, 2, oWiZ / norm);
1678             m.setElementAt(9, 3, oWiW / norm);
1679 
1680             m.setElementAt(9, 12, -oXiX / norm);
1681             m.setElementAt(9, 13, -oXiY / norm);
1682             m.setElementAt(9, 14, -oXiZ / norm);
1683             m.setElementAt(9, 15, -oXiW / norm);
1684 
1685             norm = Math.sqrt(tmp + oYiX * oYiX + oYiY * oYiY + oYiZ * oYiZ + oYiW * oYiW);
1686 
1687             m.setElementAt(10, 4, oWiX / norm);
1688             m.setElementAt(10, 5, oWiY / norm);
1689             m.setElementAt(10, 6, oWiZ / norm);
1690             m.setElementAt(10, 7, oWiW / norm);
1691 
1692             m.setElementAt(10, 12, -oYiX / norm);
1693             m.setElementAt(10, 13, -oYiY / norm);
1694             m.setElementAt(10, 14, -oYiZ / norm);
1695             m.setElementAt(10, 15, -oYiW / norm);
1696 
1697             norm = Math.sqrt(tmp + oZiX * oZiX + oZiY * oZiY + oZiZ * oZiZ + oZiW * oZiW);
1698 
1699             m.setElementAt(11, 8, oWiX / norm);
1700             m.setElementAt(11, 9, oWiY / norm);
1701             m.setElementAt(11, 10, oWiZ / norm);
1702             m.setElementAt(11, 11, oWiW / norm);
1703 
1704             m.setElementAt(11, 12, -oZiX / norm);
1705             m.setElementAt(11, 13, -oZiY / norm);
1706             m.setElementAt(11, 14, -oZiZ / norm);
1707             m.setElementAt(11, 15, -oZiW / norm);
1708 
1709             // 5th pair of points
1710             iX = inputPoint5.getHomX();
1711             iY = inputPoint5.getHomY();
1712             iZ = inputPoint5.getHomZ();
1713             iW = inputPoint5.getHomW();
1714 
1715             oX = outputPoint5.getHomX();
1716             oY = outputPoint5.getHomY();
1717             oZ = outputPoint5.getHomZ();
1718             oW = outputPoint5.getHomW();
1719 
1720             oWiX = oW * iX;
1721             oWiY = oW * iY;
1722             oWiZ = oW * iZ;
1723             oWiW = oW * iW;
1724 
1725             oXiX = oX * iX;
1726             oXiY = oX * iY;
1727             oXiZ = oX * iZ;
1728             oXiW = oX * iW;
1729 
1730             oYiX = oY * iX;
1731             oYiY = oY * iY;
1732             oYiZ = oY * iZ;
1733             oYiW = oY * iW;
1734 
1735             oZiX = oZ * iX;
1736             oZiY = oZ * iY;
1737             oZiZ = oZ * iZ;
1738             oZiW = oZ * iW;
1739 
1740             tmp = oWiX * oWiX + oWiY * oWiY + oWiZ * oWiZ + oWiW * oWiW;
1741             norm = Math.sqrt(tmp + oXiX * oXiX + oXiY * oXiY + oXiZ * oXiZ + oXiW * oXiW);
1742 
1743             m.setElementAt(12, 0, oWiX / norm);
1744             m.setElementAt(12, 1, oWiY / norm);
1745             m.setElementAt(12, 2, oWiZ / norm);
1746             m.setElementAt(12, 3, oWiW / norm);
1747 
1748             m.setElementAt(12, 12, -oXiX / norm);
1749             m.setElementAt(12, 13, -oXiY / norm);
1750             m.setElementAt(12, 14, -oXiZ / norm);
1751             m.setElementAt(12, 15, -oXiW / norm);
1752 
1753             norm = Math.sqrt(tmp + oYiX * oYiX + oYiY * oYiY + oYiZ * oYiZ + oYiW * oYiW);
1754 
1755             m.setElementAt(13, 4, oWiX / norm);
1756             m.setElementAt(13, 5, oWiY / norm);
1757             m.setElementAt(13, 6, oWiZ / norm);
1758             m.setElementAt(13, 7, oWiW / norm);
1759 
1760             m.setElementAt(13, 12, -oYiX / norm);
1761             m.setElementAt(13, 13, -oYiY / norm);
1762             m.setElementAt(13, 14, -oYiZ / norm);
1763             m.setElementAt(13, 15, -oYiW / norm);
1764 
1765             norm = Math.sqrt(tmp + oZiX * oZiX + oZiY * oZiY + oZiZ * oZiZ + oZiW * oZiW);
1766 
1767             m.setElementAt(14, 8, oWiX / norm);
1768             m.setElementAt(14, 9, oWiY / norm);
1769             m.setElementAt(14, 10, oWiZ / norm);
1770             m.setElementAt(14, 11, oWiW / norm);
1771 
1772             m.setElementAt(14, 12, -oZiX / norm);
1773             m.setElementAt(14, 13, -oZiY / norm);
1774             m.setElementAt(14, 14, -oZiZ / norm);
1775             m.setElementAt(14, 15, -oZiW / norm);
1776         } catch (final WrongSizeException ignore) {
1777             // never happens
1778         }
1779 
1780         // use SVD to decompose matrix m
1781         Matrix v;
1782         try {
1783             final var decomposer = new SingularValueDecomposer(m);
1784             decomposer.decompose();
1785 
1786             // ensure that matrix m has enough rank and there is a unique
1787             // solution (up to scale)
1788             if (decomposer.getRank() < 15) {
1789                 throw new CoincidentPointsException();
1790             }
1791             v = decomposer.getV(); //V is 16x16
1792 
1793             // last column of V will contain parameters of transformation
1794             t.setSubmatrix(0, 0, HOM_COORDS - 1, HOM_COORDS - 1,
1795                     v.getSubmatrix(0, 15, 15, 15).toArray(), false);
1796             normalized = true; //because columns of V are normalized after SVD
1797 
1798         } catch (final AlgebraException e) {
1799             throw new CoincidentPointsException(e);
1800         }
1801     }
1802 
1803     /**
1804      * Estimates this transformation internal matrix by providing 4
1805      * corresponding original and transformed planes.
1806      *
1807      * @param inputPlane1  1st input plane.
1808      * @param inputPlane2  2nd input plane.
1809      * @param inputPlane3  3rd input plane.
1810      * @param inputPlane4  4th input plane.
1811      * @param inputPlane5  5th input plane.
1812      * @param outputPlane1 1st transformed plane corresponding to 1st input
1813      *                     plane.
1814      * @param outputPlane2 2nd transformed plane corresponding to 2nd input
1815      *                     plane.
1816      * @param outputPlane3 3rd transformed plane corresponding to 3rd input
1817      *                     plane.
1818      * @param outputPlane4 4th transformed plane corresponding to 4th input
1819      *                     plane.
1820      * @param outputPlane5 5th transformed plane corresponding to 4th input
1821      *                     plane.
1822      * @throws CoincidentPlanesException Raised if transformation cannot be
1823      *                                   estimated for some reason (plane configuration degeneracy, duplicate
1824      *                                   plane or numerical instabilities).
1825      */
1826     public final void setTransformationFromPlanes(
1827             final Plane inputPlane1, final Plane inputPlane2, final Plane inputPlane3, final Plane inputPlane4,
1828             final Plane inputPlane5, final Plane outputPlane1, final Plane outputPlane2, final Plane outputPlane3,
1829             final Plane outputPlane4, final Plane outputPlane5) throws CoincidentPlanesException {
1830 
1831         // normalize lines to increase accuracy
1832         inputPlane1.normalize();
1833         inputPlane2.normalize();
1834         inputPlane3.normalize();
1835         inputPlane4.normalize();
1836         inputPlane5.normalize();
1837 
1838         outputPlane1.normalize();
1839         outputPlane2.normalize();
1840         outputPlane3.normalize();
1841         outputPlane4.normalize();
1842         outputPlane5.normalize();
1843 
1844         // matrix of homogeneous linear system of equations.
1845         // There are 9 unknowns and 8 equations (2 for each pair of corresponding
1846         // points)
1847         Matrix m = null;
1848         try {
1849             // build matrix initialized to zero
1850             m = new Matrix(15, 16);
1851 
1852             // 1st pair of planes
1853             var iA = inputPlane1.getA();
1854             var iB = inputPlane1.getB();
1855             var iC = inputPlane1.getC();
1856             var iD = inputPlane1.getD();
1857 
1858             var oA = outputPlane1.getA();
1859             var oB = outputPlane1.getB();
1860             var oC = outputPlane1.getC();
1861             var oD = outputPlane1.getD();
1862 
1863             var oDiA = oD * iA;
1864             var oDiB = oD * iB;
1865             var oDiC = oD * iC;
1866             var oDiD = oD * iD;
1867 
1868             var oAiA = oA * iA;
1869             var oAiB = oA * iB;
1870             var oAiC = oA * iC;
1871             var oAiD = oA * iD;
1872 
1873             var oBiA = oB * iA;
1874             var oBiB = oB * iB;
1875             var oBiC = oB * iC;
1876             var oBiD = oB * iD;
1877 
1878             var oCiA = oC * iA;
1879             var oCiB = oC * iB;
1880             var oCiC = oC * iC;
1881             var oCiD = oC * iD;
1882 
1883             var tmp = oDiA * oDiA + oDiB * oDiB + oDiC * oDiC + oDiD * oDiD;
1884             var norm = Math.sqrt(tmp + oAiA * oAiA + oAiB * oAiB + oAiC * oAiC + oAiD * oAiD);
1885 
1886             m.setElementAt(0, 0, oDiA / norm);
1887             m.setElementAt(0, 1, oDiB / norm);
1888             m.setElementAt(0, 2, oDiC / norm);
1889             m.setElementAt(0, 3, oDiD / norm);
1890 
1891             m.setElementAt(0, 12, -oAiA / norm);
1892             m.setElementAt(0, 13, -oAiB / norm);
1893             m.setElementAt(0, 14, -oAiC / norm);
1894             m.setElementAt(0, 15, -oAiD / norm);
1895 
1896             norm = Math.sqrt(tmp + oBiA * oBiA + oBiB * oBiB + oBiC * oBiC + oBiD * oBiD);
1897 
1898             m.setElementAt(1, 4, oDiA / norm);
1899             m.setElementAt(1, 5, oDiB / norm);
1900             m.setElementAt(1, 6, oDiC / norm);
1901             m.setElementAt(1, 7, oDiD / norm);
1902 
1903             m.setElementAt(1, 12, -oBiA / norm);
1904             m.setElementAt(1, 13, -oBiB / norm);
1905             m.setElementAt(1, 14, -oBiC / norm);
1906             m.setElementAt(1, 15, -oBiD / norm);
1907 
1908             norm = Math.sqrt(tmp + oCiA * oCiA + oCiB * oCiB + oCiC * oCiC + oCiD * oCiD);
1909 
1910             m.setElementAt(2, 8, oDiA / norm);
1911             m.setElementAt(2, 9, oDiB / norm);
1912             m.setElementAt(2, 10, oDiC / norm);
1913             m.setElementAt(2, 11, oDiD / norm);
1914 
1915             m.setElementAt(2, 12, -oCiA / norm);
1916             m.setElementAt(2, 13, -oCiB / norm);
1917             m.setElementAt(2, 14, -oCiC / norm);
1918             m.setElementAt(2, 15, -oCiD / norm);
1919 
1920             // 2nd pair of planes
1921             iA = inputPlane2.getA();
1922             iB = inputPlane2.getB();
1923             iC = inputPlane2.getC();
1924             iD = inputPlane2.getD();
1925 
1926             oA = outputPlane2.getA();
1927             oB = outputPlane2.getB();
1928             oC = outputPlane2.getC();
1929             oD = outputPlane2.getD();
1930 
1931             oDiA = oD * iA;
1932             oDiB = oD * iB;
1933             oDiC = oD * iC;
1934             oDiD = oD * iD;
1935 
1936             oAiA = oA * iA;
1937             oAiB = oA * iB;
1938             oAiC = oA * iC;
1939             oAiD = oA * iD;
1940 
1941             oBiA = oB * iA;
1942             oBiB = oB * iB;
1943             oBiC = oB * iC;
1944             oBiD = oB * iD;
1945 
1946             oCiA = oC * iA;
1947             oCiB = oC * iB;
1948             oCiC = oC * iC;
1949             oCiD = oC * iD;
1950 
1951             tmp = oDiA * oDiA + oDiB * oDiB + oDiC * oDiC + oDiD * oDiD;
1952             norm = Math.sqrt(tmp + oAiA * oAiA + oAiB * oAiB + oAiC * oAiC + oAiD * oAiD);
1953 
1954             m.setElementAt(3, 0, oDiA / norm);
1955             m.setElementAt(3, 1, oDiB / norm);
1956             m.setElementAt(3, 2, oDiC / norm);
1957             m.setElementAt(3, 3, oDiD / norm);
1958 
1959             m.setElementAt(3, 12, -oAiA / norm);
1960             m.setElementAt(3, 13, -oAiB / norm);
1961             m.setElementAt(3, 14, -oAiC / norm);
1962             m.setElementAt(3, 15, -oAiD / norm);
1963 
1964             norm = Math.sqrt(tmp + oBiA * oBiA + oBiB * oBiB + oBiC * oBiC + oBiD * oBiD);
1965 
1966             m.setElementAt(4, 4, oDiA / norm);
1967             m.setElementAt(4, 5, oDiB / norm);
1968             m.setElementAt(4, 6, oDiC / norm);
1969             m.setElementAt(4, 7, oDiD / norm);
1970 
1971             m.setElementAt(4, 12, -oBiA / norm);
1972             m.setElementAt(4, 13, -oBiB / norm);
1973             m.setElementAt(4, 14, -oBiC / norm);
1974             m.setElementAt(4, 15, -oBiD / norm);
1975 
1976             norm = Math.sqrt(tmp + oCiA * oCiA + oCiB * oCiB + oCiC * oCiC + oCiD * oCiD);
1977 
1978             m.setElementAt(5, 8, oDiA / norm);
1979             m.setElementAt(5, 9, oDiB / norm);
1980             m.setElementAt(5, 10, oDiC / norm);
1981             m.setElementAt(5, 11, oDiD / norm);
1982 
1983             m.setElementAt(5, 12, -oCiA / norm);
1984             m.setElementAt(5, 13, -oCiB / norm);
1985             m.setElementAt(5, 14, -oCiC / norm);
1986             m.setElementAt(5, 15, -oCiD / norm);
1987 
1988             // 3rd pair of planes
1989             iA = inputPlane3.getA();
1990             iB = inputPlane3.getB();
1991             iC = inputPlane3.getC();
1992             iD = inputPlane3.getD();
1993 
1994             oA = outputPlane3.getA();
1995             oB = outputPlane3.getB();
1996             oC = outputPlane3.getC();
1997             oD = outputPlane3.getD();
1998 
1999             oDiA = oD * iA;
2000             oDiB = oD * iB;
2001             oDiC = oD * iC;
2002             oDiD = oD * iD;
2003 
2004             oAiA = oA * iA;
2005             oAiB = oA * iB;
2006             oAiC = oA * iC;
2007             oAiD = oA * iD;
2008 
2009             oBiA = oB * iA;
2010             oBiB = oB * iB;
2011             oBiC = oB * iC;
2012             oBiD = oB * iD;
2013 
2014             oCiA = oC * iA;
2015             oCiB = oC * iB;
2016             oCiC = oC * iC;
2017             oCiD = oC * iD;
2018 
2019             tmp = oDiA * oDiA + oDiB * oDiB + oDiC * oDiC + oDiD * oDiD;
2020             norm = Math.sqrt(tmp + oAiA * oAiA + oAiB * oAiB + oAiC * oAiC + oAiD * oAiD);
2021 
2022             m.setElementAt(6, 0, oDiA / norm);
2023             m.setElementAt(6, 1, oDiB / norm);
2024             m.setElementAt(6, 2, oDiC / norm);
2025             m.setElementAt(6, 3, oDiD / norm);
2026 
2027             m.setElementAt(6, 12, -oAiA / norm);
2028             m.setElementAt(6, 13, -oAiB / norm);
2029             m.setElementAt(6, 14, -oAiC / norm);
2030             m.setElementAt(6, 15, -oAiD / norm);
2031 
2032             norm = Math.sqrt(tmp + oBiA * oBiA + oBiB * oBiB + oBiC * oBiC + oBiD * oBiD);
2033 
2034             m.setElementAt(7, 4, oDiA / norm);
2035             m.setElementAt(7, 5, oDiB / norm);
2036             m.setElementAt(7, 6, oDiC / norm);
2037             m.setElementAt(7, 7, oDiD / norm);
2038 
2039             m.setElementAt(7, 12, -oBiA / norm);
2040             m.setElementAt(7, 13, -oBiB / norm);
2041             m.setElementAt(7, 14, -oBiC / norm);
2042             m.setElementAt(7, 15, -oBiD / norm);
2043 
2044             norm = Math.sqrt(tmp + oCiA * oCiA + oCiB * oCiB + oCiC * oCiC + oCiD * oCiD);
2045 
2046             m.setElementAt(8, 8, oDiA / norm);
2047             m.setElementAt(8, 9, oDiB / norm);
2048             m.setElementAt(8, 10, oDiC / norm);
2049             m.setElementAt(8, 11, oDiD / norm);
2050 
2051             m.setElementAt(8, 12, -oCiA / norm);
2052             m.setElementAt(8, 13, -oCiB / norm);
2053             m.setElementAt(8, 14, -oCiC / norm);
2054             m.setElementAt(8, 15, -oCiD / norm);
2055 
2056             // 4th pair of planes
2057             iA = inputPlane4.getA();
2058             iB = inputPlane4.getB();
2059             iC = inputPlane4.getC();
2060             iD = inputPlane4.getD();
2061 
2062             oA = outputPlane4.getA();
2063             oB = outputPlane4.getB();
2064             oC = outputPlane4.getC();
2065             oD = outputPlane4.getD();
2066 
2067             oDiA = oD * iA;
2068             oDiB = oD * iB;
2069             oDiC = oD * iC;
2070             oDiD = oD * iD;
2071 
2072             oAiA = oA * iA;
2073             oAiB = oA * iB;
2074             oAiC = oA * iC;
2075             oAiD = oA * iD;
2076 
2077             oBiA = oB * iA;
2078             oBiB = oB * iB;
2079             oBiC = oB * iC;
2080             oBiD = oB * iD;
2081 
2082             oCiA = oC * iA;
2083             oCiB = oC * iB;
2084             oCiC = oC * iC;
2085             oCiD = oC * iD;
2086 
2087             tmp = oDiA * oDiA + oDiB * oDiB + oDiC * oDiC + oDiD * oDiD;
2088             norm = Math.sqrt(tmp + oAiA * oAiA + oAiB * oAiB + oAiC * oAiC + oAiD * oAiD);
2089 
2090             m.setElementAt(9, 0, oDiA / norm);
2091             m.setElementAt(9, 1, oDiB / norm);
2092             m.setElementAt(9, 2, oDiC / norm);
2093             m.setElementAt(9, 3, oDiD / norm);
2094 
2095             m.setElementAt(9, 12, -oAiA / norm);
2096             m.setElementAt(9, 13, -oAiB / norm);
2097             m.setElementAt(9, 14, -oAiC / norm);
2098             m.setElementAt(9, 15, -oAiD / norm);
2099 
2100             norm = Math.sqrt(tmp + oBiA * oBiA + oBiB * oBiB + oBiC * oBiC + oBiD * oBiD);
2101 
2102             m.setElementAt(10, 4, oDiA / norm);
2103             m.setElementAt(10, 5, oDiB / norm);
2104             m.setElementAt(10, 6, oDiC / norm);
2105             m.setElementAt(10, 7, oDiD / norm);
2106 
2107             m.setElementAt(10, 12, -oBiA / norm);
2108             m.setElementAt(10, 13, -oBiB / norm);
2109             m.setElementAt(10, 14, -oBiC / norm);
2110             m.setElementAt(10, 15, -oBiD / norm);
2111 
2112             norm = Math.sqrt(tmp + oCiA * oCiA + oCiB * oCiB + oCiC * oCiC + oCiD * oCiD);
2113 
2114             m.setElementAt(11, 8, oDiA / norm);
2115             m.setElementAt(11, 9, oDiB / norm);
2116             m.setElementAt(11, 10, oDiC / norm);
2117             m.setElementAt(11, 11, oDiD / norm);
2118 
2119             m.setElementAt(11, 12, -oCiA / norm);
2120             m.setElementAt(11, 13, -oCiB / norm);
2121             m.setElementAt(11, 14, -oCiC / norm);
2122             m.setElementAt(11, 15, -oCiD / norm);
2123 
2124             // 5th pair of planes
2125             iA = inputPlane5.getA();
2126             iB = inputPlane5.getB();
2127             iC = inputPlane5.getC();
2128             iD = inputPlane5.getD();
2129 
2130             oA = outputPlane5.getA();
2131             oB = outputPlane5.getB();
2132             oC = outputPlane5.getC();
2133             oD = outputPlane5.getD();
2134 
2135             oDiA = oD * iA;
2136             oDiB = oD * iB;
2137             oDiC = oD * iC;
2138             oDiD = oD * iD;
2139 
2140             oAiA = oA * iA;
2141             oAiB = oA * iB;
2142             oAiC = oA * iC;
2143             oAiD = oA * iD;
2144 
2145             oBiA = oB * iA;
2146             oBiB = oB * iB;
2147             oBiC = oB * iC;
2148             oBiD = oB * iD;
2149 
2150             oCiA = oC * iA;
2151             oCiB = oC * iB;
2152             oCiC = oC * iC;
2153             oCiD = oC * iD;
2154 
2155             tmp = oDiA * oDiA + oDiB * oDiB + oDiC * oDiC + oDiD * oDiD;
2156             norm = Math.sqrt(tmp + oAiA * oAiA + oAiB * oAiB + oAiC * oAiC + oAiD * oAiD);
2157 
2158             m.setElementAt(12, 0, oDiA / norm);
2159             m.setElementAt(12, 1, oDiB / norm);
2160             m.setElementAt(12, 2, oDiC / norm);
2161             m.setElementAt(12, 3, oDiD / norm);
2162 
2163             m.setElementAt(12, 12, -oAiA / norm);
2164             m.setElementAt(12, 13, -oAiB / norm);
2165             m.setElementAt(12, 14, -oAiC / norm);
2166             m.setElementAt(12, 15, -oAiD / norm);
2167 
2168             norm = Math.sqrt(tmp + oBiA * oBiA + oBiB * oBiB + oBiC * oBiC + oBiD * oBiD);
2169 
2170             m.setElementAt(13, 4, oDiA / norm);
2171             m.setElementAt(13, 5, oDiB / norm);
2172             m.setElementAt(13, 6, oDiC / norm);
2173             m.setElementAt(13, 7, oDiD / norm);
2174 
2175             m.setElementAt(13, 12, -oBiA / norm);
2176             m.setElementAt(13, 13, -oBiB / norm);
2177             m.setElementAt(13, 14, -oBiC / norm);
2178             m.setElementAt(13, 15, -oBiD / norm);
2179 
2180             norm = Math.sqrt(tmp + oCiA * oCiA + oCiB * oCiB + oCiC * oCiC + oCiD * oCiD);
2181 
2182             m.setElementAt(14, 8, oDiA / norm);
2183             m.setElementAt(14, 9, oDiB / norm);
2184             m.setElementAt(14, 10, oDiC / norm);
2185             m.setElementAt(14, 11, oDiD / norm);
2186 
2187             m.setElementAt(14, 12, -oCiA / norm);
2188             m.setElementAt(14, 13, -oCiB / norm);
2189             m.setElementAt(14, 14, -oCiC / norm);
2190             m.setElementAt(14, 15, -oCiD / norm);
2191         } catch (final WrongSizeException ignore) {
2192             // never happens
2193         }
2194 
2195         // use SVD to decompose matrix m
2196         Matrix v;
2197         try {
2198             final var decomposer = new SingularValueDecomposer(m);
2199             decomposer.decompose();
2200 
2201             // ensure that matrix m has enough rank and there is a unique
2202             // solution (up to scale)
2203             if (decomposer.getRank() < 15) {
2204                 throw new CoincidentPlanesException();
2205             }
2206             // V is 16x16
2207             v = decomposer.getV();
2208 
2209             // last column of V will contain parameters of transformation
2210             final var transInvT = new Matrix(HOM_COORDS, HOM_COORDS);
2211             transInvT.setSubmatrix(0, 0, HOM_COORDS - 1,
2212                     HOM_COORDS - 1,
2213                     v.getSubmatrix(0, 15, 15, 15).toArray(),
2214                     false);
2215             // this is now invT
2216             transInvT.transpose();
2217             t = Utils.inverse(transInvT);
2218             // invT is normalized, but not t
2219             normalized = false;
2220 
2221         } catch (final AlgebraException e) {
2222             throw new CoincidentPlanesException(e);
2223         }
2224     }
2225 
2226     /**
2227      * Estimates this transformation internal matrix by providing 3
2228      * corresponding original and transformed lines.
2229      *
2230      * @param inputLine1  1st input line.
2231      * @param inputLine2  2nd input line.
2232      * @param inputLine3  3rd input line.
2233      * @param outputLine1 1st transformed line corresponding to 1st input line.
2234      * @param outputLine2 2nd transformed line corresponding to 2nd input line.
2235      * @param outputLine3 3rd transformed line corresponding to 3rd input line.
2236      * @throws CoincidentLinesException Raised if transformation cannot be
2237      *                                  estimated for some reason (line configuration degeneracy, duplicate lines
2238      *                                  or numerical instabilities).
2239      */
2240     public final void setTransformationFromLines(
2241             final Line3D inputLine1, final Line3D inputLine2, final Line3D inputLine3, final Line3D outputLine1,
2242             final Line3D outputLine2, final Line3D outputLine3) throws CoincidentLinesException {
2243         try {
2244             setTransformationFromPlanes(inputLine1.getPlane1(), inputLine1.getPlane2(), inputLine2.getPlane1(),
2245                     inputLine2.getPlane2(), inputLine3.getPlane1(), outputLine1.getPlane1(), outputLine1.getPlane2(),
2246                     outputLine2.getPlane1(), outputLine2.getPlane2(), outputLine3.getPlane1());
2247         } catch (final CoincidentPlanesException e) {
2248             throw new CoincidentLinesException(e);
2249         }
2250     }
2251 }