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.Matrix;
20  import com.irurueta.algebra.SingularValueDecomposer;
21  import com.irurueta.algebra.WrongSizeException;
22  
23  import java.io.Serializable;
24  
25  /**
26   * This class contains implementation of a dual quadric.
27   */
28  @SuppressWarnings("DuplicatedCode")
29  public class DualQuadric extends BaseQuadric implements Serializable {
30  
31      /**
32       * Constructor.
33       */
34      public DualQuadric() {
35          super();
36      }
37  
38      /**
39       * Constructor of this class. This constructor accepts every parameter
40       * describing a dual quadric (parameters a, b, c, d, e, f, g, h, i, j).
41       *
42       * @param a Parameter A of the quadric.
43       * @param b Parameter B of the quadric.
44       * @param c Parameter C of the quadric.
45       * @param d Parameter D of the quadric.
46       * @param e Parameter E of the quadric.
47       * @param f Parameter F of the quadric.
48       * @param g Parameter G of the quadric.
49       * @param h Parameter H of the quadric.
50       * @param i Parameter I of the quadric.
51       * @param j Parameter J of the quadric.
52       */
53      public DualQuadric(
54              final double a, final double b, final double c, final double d, final double e, final double f,
55              final double g, final double h, final double i, final double j) {
56          super(a, b, c, d, e, f, g, h, i, j);
57      }
58  
59      /**
60       * This method sets the matrix used to describe a dual quadric.
61       * This matrix must be 4x4 and symmetric.
62       *
63       * @param m 4x4 Matrix describing the quadric.
64       * @throws IllegalArgumentException    Raised when the size of the matrix is
65       *                                     not 4x4.
66       * @throws NonSymmetricMatrixException Raised when the quadric matrix is not
67       *                                     symmetric.
68       */
69      public DualQuadric(final Matrix m) throws NonSymmetricMatrixException {
70          super(m);
71      }
72  
73      /**
74       * Creates a dual matrix where provided planes are its locus, or in other
75       * words, provided planes are tangent to the quadric corresponding to the
76       * created dual quadric.
77       *
78       * @param plane1 1st plane.
79       * @param plane2 2nd plane.
80       * @param plane3 3rd plane.
81       * @param plane4 4th plane.
82       * @param plane5 5th plane.
83       * @param plane6 6th plane.
84       * @param plane7 7th plane.
85       * @param plane8 8th plane.
86       * @param plane9 9th plane.
87       * @throws CoincidentPlanesException if provided planes are in a
88       *                                   configuration where more than one plane is coincident, creating a
89       *                                   degeneracy.
90       */
91      public DualQuadric(
92              final Plane plane1, final Plane plane2, final Plane plane3, final Plane plane4, final Plane plane5,
93              final Plane plane6, final Plane plane7, final Plane plane8, final Plane plane9)
94              throws CoincidentPlanesException {
95          setParametersFromPlanes(plane1, plane2, plane3, plane4, plane5, plane6, plane7, plane8, plane9);
96      }
97  
98      /**
99       * Checks if provided plane is locus of this dual quadric, or in other
100      * words, checks whether provided plane lies within this quadric, or whether
101      * provided plane is tangent to the quadric corresponding to this dual
102      * quadric.
103      *
104      * @param plane     Plane to be tested.
105      * @param threshold Threshold of tolerance to determine whether this plane
106      *                  is locus or not. This is needed because of limited machine precision. If
107      *                  threshold is not provided, then DEFAULT_LOCUS_THRESHOLD is used instead.
108      * @return True if provided plane is locus of this dual quadric, false
109      * otherwise.
110      * @throws IllegalArgumentException Raised if provided threshold is negative.
111      */
112     public boolean isLocus(final Plane plane, final double threshold) {
113         if (threshold < MIN_THRESHOLD) {
114             throw new IllegalArgumentException();
115         }
116 
117         try {
118             normalize();
119             final var dualQ = asMatrix();
120             final var homPlane = new Matrix(Plane.PLANE_NUMBER_PARAMS, 1);
121             plane.normalize();
122             homPlane.setElementAt(0, 0, plane.getA());
123             homPlane.setElementAt(1, 0, plane.getB());
124             homPlane.setElementAt(2, 0, plane.getC());
125             homPlane.setElementAt(3, 0, plane.getD());
126             final var locusMatrix = homPlane.transposeAndReturnNew();
127             locusMatrix.multiply(dualQ);
128             locusMatrix.multiply(homPlane);
129 
130             return Math.abs(locusMatrix.getElementAt(0, 0)) < threshold;
131         } catch (final WrongSizeException ignore) {
132             return false;
133         }
134     }
135 
136     /**
137      * Checks if provided plane is locus of this dual quadric, or in other
138      * words, checks whether provided plane lies within this quadric, or whether
139      * provided plane is tangent to the quadric corresponding to this dual
140      * quadric.
141      *
142      * @param plane Plane to be tested.
143      * @return True if provided plane is locus of this dual quadric, false
144      * otherwise.
145      * @see #isLocus(Plane, double)
146      */
147     public boolean isLocus(final Plane plane) {
148         return isLocus(plane, DEFAULT_LOCUS_THRESHOLD);
149     }
150 
151     /**
152      * Computes the angle between two planes in radians.
153      *
154      * @param planeA First plane to be tested.
155      * @param planeB Second plane to be tested.
156      * @return Angle between the two provided planes in radians. This angle is
157      * equal to the angle of their corresponding director vectors in an
158      * Euclidean geometry, but it might not be the case for the geometry defined
159      * by this dual quadric.
160      */
161     public double angleBetweenPlanes(final Plane planeA, final Plane planeB) {
162         try {
163             // retrieve quadric as matrix
164             normalize();
165             final var dualQ = asMatrix();
166             final var transHomPlaneA = new Matrix(1, Plane.PLANE_NUMBER_PARAMS);
167             planeA.normalize();
168             transHomPlaneA.setElementAt(0, 0, planeA.getA());
169             transHomPlaneA.setElementAt(0, 1, planeA.getB());
170             transHomPlaneA.setElementAt(0, 2, planeA.getC());
171             transHomPlaneA.setElementAt(0, 3, planeA.getD());
172 
173             final var tmp = transHomPlaneA.multiplyAndReturnNew(dualQ);
174             tmp.multiply(transHomPlaneA.transposeAndReturnNew()); //This is
175             // homPlaneA' * dualQ * homPlaneA
176 
177             final var normA = tmp.getElementAt(0, 0);
178 
179             final var homPlaneB = new Matrix(Plane.PLANE_NUMBER_PARAMS, 1);
180             planeB.normalize();
181             homPlaneB.setElementAt(0, 0, planeB.getA());
182             homPlaneB.setElementAt(1, 0, planeB.getB());
183             homPlaneB.setElementAt(2, 0, planeB.getC());
184             homPlaneB.setElementAt(3, 0, planeB.getD());
185 
186             homPlaneB.transpose(tmp);
187             tmp.multiply(dualQ);
188             tmp.multiply(homPlaneB);
189 
190             final var normB = tmp.getElementAt(0, 0);
191 
192             transHomPlaneA.multiply(dualQ);
193             transHomPlaneA.multiply(homPlaneB);
194             // This is homPlaneA' * dualQ * homPlaneB
195 
196             final var angleNumerator = transHomPlaneA.getElementAt(0, 0);
197 
198             final var cosTheta = angleNumerator / Math.sqrt(normA * normB);
199             return Math.acos(cosTheta);
200         } catch (final WrongSizeException ignore) {
201             // This will never happen
202             return 0.0;
203         }
204     }
205 
206     /**
207      * Checks if two planes are perpendicular attending to the geometry defined
208      * by this dual quadric, or in other words, if lA' * dualQ * lB is zero.
209      *
210      * @param planeA    First plane to be checked.
211      * @param planeB    Second plane to be checked.
212      * @param threshold Threshold of tolerance to determine whether the planes
213      *                  are perpendicular or not. This is needed because of limited machine
214      *                  precision. If threshold is not provided, then
215      *                  DEFAULT_PERPENDICULAR_THRESHOLD is used instead.
216      * @return True if provided planes are perpendicular, false otherwise.
217      * @throws IllegalArgumentException Raised if provided threshold is
218      *                                  negative.
219      */
220     public boolean arePerpendicularPlanes(final Plane planeA, final Plane planeB, final double threshold) {
221         try {
222             // retrieve quadric as matrix
223             final var transHomPlaneA = new Matrix(1, Plane.PLANE_NUMBER_PARAMS);
224             planeA.normalize();
225             transHomPlaneA.setElementAt(0, 0, planeA.getA());
226             transHomPlaneA.setElementAt(0, 1, planeA.getB());
227             transHomPlaneA.setElementAt(0, 2, planeA.getC());
228             transHomPlaneA.setElementAt(0, 3, planeA.getD());
229 
230             final var homPlaneB = new Matrix(Point3D.POINT3D_HOMOGENEOUS_COORDINATES_LENGTH, 1);
231             planeB.normalize();
232             homPlaneB.setElementAt(0, 0, planeB.getA());
233             homPlaneB.setElementAt(1, 0, planeB.getB());
234             homPlaneB.setElementAt(2, 0, planeB.getC());
235             homPlaneB.setElementAt(3, 0, planeB.getD());
236 
237             normalize();
238             final var dualQ = asMatrix();
239             transHomPlaneA.multiply(dualQ);
240             transHomPlaneA.multiply(homPlaneB);
241             // This is homPlaneA' * dualQ * homPlaneB
242 
243             final var perpend = transHomPlaneA.getElementAt(0, 0);
244 
245             return Math.abs(perpend) < threshold;
246         } catch (final WrongSizeException ignore) {
247             // This will never happen
248             return false;
249         }
250     }
251 
252     /**
253      * Checks if two planes are perpendicular attending to the geometry defined
254      * by this dual quadric, or in other words, if lA' * dualQ * lB is zero.
255      *
256      * @param planeA First plane to be checked.
257      * @param planeB Second plane to be checked.
258      * @return True if provided planes are perpendicular, false otherwise.
259      */
260     public boolean arePerpendicularPlanes(final Plane planeA, final Plane planeB) {
261         return arePerpendicularPlanes(planeA, planeB, DEFAULT_PERPENDICULAR_THRESHOLD);
262     }
263 
264     /**
265      * Computes the quadric corresponding to this dual quadric.
266      *
267      * @return A new quadric instance of this dual quadric.
268      * @throws QuadricNotAvailableException Raised if the rank of the dual
269      *                                      quadric matrix is not complete due to wrong parameters or numerical
270      *                                      instability.
271      */
272     public Quadric getQuadric() throws QuadricNotAvailableException {
273         final var q = new Quadric();
274         quadric(q);
275         return q;
276     }
277 
278     /**
279      * Computes the quadric corresponding to this dual quadric and stores the
280      * result in provided instance.
281      *
282      * @param quadric Quadric where result is stored.
283      * @throws QuadricNotAvailableException Raised if the rank of the dual
284      *                                      quadric matrix is not complete due to wrong parameters or numerical
285      *                                      instability.
286      */
287     public void quadric(final Quadric quadric) throws QuadricNotAvailableException {
288         final var dualQuadricMatrix = asMatrix();
289         try {
290             final var invMatrix = com.irurueta.algebra.Utils.inverse(dualQuadricMatrix);
291 
292             final var a = invMatrix.getElementAt(0, 0);
293             final var b = invMatrix.getElementAt(1, 1);
294             final var c = invMatrix.getElementAt(2, 2);
295             final var d = 0.5 * (invMatrix.getElementAt(0, 1) + invMatrix.getElementAt(1, 0));
296             final var e = 0.5 * (invMatrix.getElementAt(2, 1) + invMatrix.getElementAt(1, 2));
297             final var f = 0.5 * (invMatrix.getElementAt(2, 0) + invMatrix.getElementAt(0, 2));
298             final var g = 0.5 * (invMatrix.getElementAt(3, 0) + invMatrix.getElementAt(0, 3));
299             final double h = 0.5 * (invMatrix.getElementAt(3, 1)
300                     + invMatrix.getElementAt(1, 3));
301             final var i = 0.5 * (invMatrix.getElementAt(3, 2) + invMatrix.getElementAt(2, 3));
302             final var j = invMatrix.getElementAt(3, 3);
303             quadric.setParameters(a, b, c, d, e, f, g, h, i, j);
304         } catch (final AlgebraException e) {
305             throw new QuadricNotAvailableException(e);
306         }
307     }
308 
309     /**
310      * Sets parameters of this dual quadric so that provided planes lie within
311      * it (are locus).
312      *
313      * @param plane1 1st plane.
314      * @param plane2 2nd plane.
315      * @param plane3 3rd plane.
316      * @param plane4 4th plane.
317      * @param plane5 5th plane.
318      * @param plane6 6th plane.
319      * @param plane7 7th plane.
320      * @param plane8 8th plane.
321      * @param plane9 9th plane.
322      * @throws CoincidentPlanesException Raised if planes are coincident or
323      *                                   produce a degenerated configuration.
324      */
325     public final void setParametersFromPlanes(
326             final Plane plane1, final Plane plane2, final Plane plane3, final Plane plane4, final Plane plane5,
327             final Plane plane6, final Plane plane7, final Plane plane8, final Plane plane9)
328             throws CoincidentPlanesException {
329 
330         // normalize planes to increase accuracy
331         plane1.normalize();
332         plane2.normalize();
333         plane3.normalize();
334         plane4.normalize();
335         plane5.normalize();
336         plane6.normalize();
337         plane7.normalize();
338         plane8.normalize();
339         plane9.normalize();
340 
341         try {
342             // each plane belonging to a dual quadric follows equation:
343             // p' * Q * p = 0 ==>
344             // pA^2 + pB^2 + pC^2 + 2*pA*pB + 2*pA*pC + 2*pB*pC + 2*pA*pD +
345             // 2*pB*pD + 2*pC*pD + pD^2 = 0
346 
347             final var m = new Matrix(9, 10);
348             var pA = plane1.getA();
349             var pB = plane1.getB();
350             var pC = plane1.getC();
351             var pD = plane1.getD();
352             m.setElementAt(0, 0, pA * pA);
353             m.setElementAt(0, 1, pB * pB);
354             m.setElementAt(0, 2, pC * pC);
355             m.setElementAt(0, 3, 2.0 * pA * pB);
356             m.setElementAt(0, 4, 2.0 * pA * pC);
357             m.setElementAt(0, 5, 2.0 * pB * pC);
358             m.setElementAt(0, 6, 2.0 * pA * pD);
359             m.setElementAt(0, 7, 2.0 * pB * pD);
360             m.setElementAt(0, 8, 2.0 * pC * pD);
361             m.setElementAt(0, 9, pD * pD);
362             pA = plane2.getA();
363             pB = plane2.getB();
364             pC = plane2.getC();
365             pD = plane2.getD();
366             m.setElementAt(1, 0, pA * pA);
367             m.setElementAt(1, 1, pB * pB);
368             m.setElementAt(1, 2, pC * pC);
369             m.setElementAt(1, 3, 2.0 * pA * pB);
370             m.setElementAt(1, 4, 2.0 * pA * pC);
371             m.setElementAt(1, 5, 2.0 * pB * pC);
372             m.setElementAt(1, 6, 2.0 * pA * pD);
373             m.setElementAt(1, 7, 2.0 * pB * pD);
374             m.setElementAt(1, 8, 2.0 * pC * pD);
375             m.setElementAt(1, 9, pD * pD);
376             pA = plane3.getA();
377             pB = plane3.getB();
378             pC = plane3.getC();
379             pD = plane3.getD();
380             m.setElementAt(2, 0, pA * pA);
381             m.setElementAt(2, 1, pB * pB);
382             m.setElementAt(2, 2, pC * pC);
383             m.setElementAt(2, 3, 2.0 * pA * pB);
384             m.setElementAt(2, 4, 2.0 * pA * pC);
385             m.setElementAt(2, 5, 2.0 * pB * pC);
386             m.setElementAt(2, 6, 2.0 * pA * pD);
387             m.setElementAt(2, 7, 2.0 * pB * pD);
388             m.setElementAt(2, 8, 2.0 * pC * pD);
389             m.setElementAt(2, 9, pD * pD);
390             pA = plane4.getA();
391             pB = plane4.getB();
392             pC = plane4.getC();
393             pD = plane4.getD();
394             m.setElementAt(3, 0, pA * pA);
395             m.setElementAt(3, 1, pB * pB);
396             m.setElementAt(3, 2, pC * pC);
397             m.setElementAt(3, 3, 2.0 * pA * pB);
398             m.setElementAt(3, 4, 2.0 * pA * pC);
399             m.setElementAt(3, 5, 2.0 * pB * pC);
400             m.setElementAt(3, 6, 2.0 * pA * pD);
401             m.setElementAt(3, 7, 2.0 * pB * pD);
402             m.setElementAt(3, 8, 2.0 * pC * pD);
403             m.setElementAt(3, 9, pD * pD);
404             pA = plane5.getA();
405             pB = plane5.getB();
406             pC = plane5.getC();
407             pD = plane5.getD();
408             m.setElementAt(4, 0, pA * pA);
409             m.setElementAt(4, 1, pB * pB);
410             m.setElementAt(4, 2, pC * pC);
411             m.setElementAt(4, 3, 2.0 * pA * pB);
412             m.setElementAt(4, 4, 2.0 * pA * pC);
413             m.setElementAt(4, 5, 2.0 * pB * pC);
414             m.setElementAt(4, 6, 2.0 * pA * pD);
415             m.setElementAt(4, 7, 2.0 * pB * pD);
416             m.setElementAt(4, 8, 2.0 * pC * pD);
417             m.setElementAt(4, 9, pD * pD);
418             pA = plane6.getA();
419             pB = plane6.getB();
420             pC = plane6.getC();
421             pD = plane6.getD();
422             m.setElementAt(5, 0, pA * pA);
423             m.setElementAt(5, 1, pB * pB);
424             m.setElementAt(5, 2, pC * pC);
425             m.setElementAt(5, 3, 2.0 * pA * pB);
426             m.setElementAt(5, 4, 2.0 * pA * pC);
427             m.setElementAt(5, 5, 2.0 * pB * pC);
428             m.setElementAt(5, 6, 2.0 * pA * pD);
429             m.setElementAt(5, 7, 2.0 * pB * pD);
430             m.setElementAt(5, 8, 2.0 * pC * pD);
431             m.setElementAt(5, 9, pD * pD);
432             pA = plane7.getA();
433             pB = plane7.getB();
434             pC = plane7.getC();
435             pD = plane7.getD();
436             m.setElementAt(6, 0, pA * pA);
437             m.setElementAt(6, 1, pB * pB);
438             m.setElementAt(6, 2, pC * pC);
439             m.setElementAt(6, 3, 2.0 * pA * pB);
440             m.setElementAt(6, 4, 2.0 * pA * pC);
441             m.setElementAt(6, 5, 2.0 * pB * pC);
442             m.setElementAt(6, 6, 2.0 * pA * pD);
443             m.setElementAt(6, 7, 2.0 * pB * pD);
444             m.setElementAt(6, 8, 2.0 * pC * pD);
445             m.setElementAt(6, 9, pD * pD);
446             pA = plane8.getA();
447             pB = plane8.getB();
448             pC = plane8.getC();
449             pD = plane8.getD();
450             m.setElementAt(7, 0, pA * pA);
451             m.setElementAt(7, 1, pB * pB);
452             m.setElementAt(7, 2, pC * pC);
453             m.setElementAt(7, 3, 2.0 * pA * pB);
454             m.setElementAt(7, 4, 2.0 * pA * pC);
455             m.setElementAt(7, 5, 2.0 * pB * pC);
456             m.setElementAt(7, 6, 2.0 * pA * pD);
457             m.setElementAt(7, 7, 2.0 * pB * pD);
458             m.setElementAt(7, 8, 2.0 * pC * pD);
459             m.setElementAt(7, 9, pD * pD);
460             pA = plane9.getA();
461             pB = plane9.getB();
462             pC = plane9.getC();
463             pD = plane9.getD();
464             m.setElementAt(8, 0, pA * pA);
465             m.setElementAt(8, 1, pB * pB);
466             m.setElementAt(8, 2, pC * pC);
467             m.setElementAt(8, 3, 2.0 * pA * pB);
468             m.setElementAt(8, 4, 2.0 * pA * pC);
469             m.setElementAt(8, 5, 2.0 * pB * pC);
470             m.setElementAt(8, 6, 2.0 * pA * pD);
471             m.setElementAt(8, 7, 2.0 * pB * pD);
472             m.setElementAt(8, 8, 2.0 * pC * pD);
473             m.setElementAt(8, 9, pD * pD);
474 
475             // normalize each row to increase accuracy
476             final var row = new double[10];
477             double rowNorm;
478             for (var j = 0; j < 9; j++) {
479                 m.getSubmatrixAsArray(j, 0, j, 9, row);
480                 rowNorm = com.irurueta.algebra.Utils.normF(row);
481                 for (var i = 0; i < 10; i++)
482                     m.setElementAt(j, i, m.getElementAt(j, i) / rowNorm);
483             }
484 
485             final var decomposer = new SingularValueDecomposer(m);
486             decomposer.decompose();
487 
488             if (decomposer.getRank() < 9) {
489                 throw new CoincidentPlanesException();
490             }
491 
492             // the right null-space of m contains the parameters a, b, c, d, e ,f
493             // of the conic
494             final var v = decomposer.getV();
495 
496             final var a = v.getElementAt(0, 9);
497             final var b = v.getElementAt(1, 9);
498             final var c = v.getElementAt(2, 9);
499             final var d = v.getElementAt(3, 9);
500 
501             final var f = v.getElementAt(4, 9);
502             final var e = v.getElementAt(5, 9);
503 
504             final var g = v.getElementAt(6, 9);
505             final var h = v.getElementAt(7, 9);
506             final var i = v.getElementAt(8, 9);
507             final var j = v.getElementAt(9, 9);
508 
509             setParameters(a, b, c, d, e, f, g, h, i, j);
510         } catch (final AlgebraException ex) {
511             throw new CoincidentPlanesException(ex);
512         }
513     }
514 
515     /**
516      * Creates a canonical instance of the dual absolute quadric in the metric
517      * stratum.
518      * In an ideal metric stratum, in order to preserve orthogonality, the dual
519      * absolute quadric is defined as a degenerate dual quadric (i.e. cannot be
520      * inverted to obtain a quadric) containing the canonical dual absolute
521      * conic (i.e. the identity) in its top left sub-matrix
522      *
523      * @return a canonical instance of the dual absolute quadric
524      */
525     public static DualQuadric createCanonicalDualAbsoluteQuadric() {
526         return new DualQuadric(1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
527     }
528 }