Salome HOME
[TetraIntersect] Avoid multiple divisions in calculatePolygonBarycenter
[tools/medcoupling.git] / src / INTERP_KERNEL / TransformedTriangleMath.cxx
1 // Copyright (C) 2007-2024  CEA, EDF
2 //
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Lesser General Public
5 // License as published by the Free Software Foundation; either
6 // version 2.1 of the License, or (at your option) any later version.
7 //
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // Lesser General Public License for more details.
12 //
13 // You should have received a copy of the GNU Lesser General Public
14 // License along with this library; if not, write to the Free Software
15 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 //
17 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 //
19
20 #include "TransformedTriangle.hxx"
21 #include <iostream>
22 #include <fstream>
23 #include <cassert>
24 #include <cmath>
25 #include <limits>
26 #include <map>
27 #include <utility>
28
29 #include "VectorUtils.hxx"
30
31 namespace INTERP_KERNEL
32 {
33   
34   // ----------------------------------------------------------------------------------
35   //  Tables                                                       
36   // ----------------------------------------------------------------------------------
37
38   /// Table with first coordinate (a) used to calculate double product c^pq_ab = p_a * q_b - p_b * q_a (index to be used : DoubleProduct)
39   const int TransformedTriangle::DP_OFFSET_1[8] = {1, 2, 0, 2, 0, 1, 4, 1};
40
41   /// Table with second coordinate (b) used to calculate double product c^pq_ab = p_a * q_b - p_b * q_a (index to be used : DoubleProduct)
42   const int TransformedTriangle::DP_OFFSET_2[8] = {2, 0, 1, 3, 3, 3, 0, 4};
43
44   /// Coordinates used to calculate triple products by the expanding one of the three rows of the determinant (index to be used : 3*Corner + row)
45   const int TransformedTriangle::COORDINATE_FOR_DETERMINANT_EXPANSION[12] =
46     {
47       // row 1, 2, 3
48       0, 1, 2, // O
49       3, 1, 2, // X
50       0, 3, 2, // Y
51       0, 1, 3  // Z
52     };
53   
54   /// Double products used to calculate triple products by expanding one of the three rows of the determinant (index to be used : 3*Corner + row)
55   const TransformedTriangle::DoubleProduct TransformedTriangle::DP_FOR_DETERMINANT_EXPANSION[12] = 
56     {
57       // row 1, 2, 3
58       C_YZ, C_ZX, C_XY, // O
59       C_YZ, C_ZH, C_YH, // X
60       C_ZH, C_ZX, C_XH, // Y
61       C_YH, C_XH, C_XY  // Z
62     };
63   
64   /// The machine epsilon, used in precision corrections
65   const double TransformedTriangle::MACH_EPS = std::numeric_limits<double>::epsilon();
66   
67   /// 4.0 * the machine epsilon, represents the precision of multiplication when performing corrections corrections ( f in Grandy )
68   const double TransformedTriangle::MULT_PREC_F = 4.0 * TransformedTriangle::MACH_EPS;
69
70   /// Threshold for resetting double and triple products to zero; ( F / f in Grandy )
71   const double TransformedTriangle::THRESHOLD_F = 100.0;
72
73   /// Threshold for what is considered a small enough angle to warrant correction of triple products by Grandy, [57]
74   const double TransformedTriangle::TRIPLE_PRODUCT_ANGLE_THRESHOLD = 0.1;
75
76
77   // Handle cases where one of the segment (or all) is (almost) in XYZ plane.
78   // We follow Grandy's suggestion and perturb slightly to have exactly h=0 for the segment (Grandy p.447)
79   // Note that if PQR is == to the upper facet of the unit tetra (XYZ), the tetra-corner-inclusion test should take it in,
80   // thanks to Grandy [21] and the fact that S_x test is "<=0" (not <0)
81   // After that, we also snap P,Q,R to the corners if they're very close.
82   void TransformedTriangle::handleDegenerateCases()
83   {
84     static const TriCorner PT_SEG_MAP[] = {
85       P, Q,
86       Q, R,
87       R, P
88     };
89
90     const double eps = THRESHOLD_F*TransformedTriangle::MULT_PREC_F;
91     for (TriSegment seg = PQ; seg <= RP; seg = TriSegment(seg+1))
92       {
93         // Is h coordinate for both end of segment small enough?
94         int pt1 = PT_SEG_MAP[2*seg], pt2 = PT_SEG_MAP[2*seg+1];
95         if (fabs(_coords[5*pt1+3]) < eps && fabs(_coords[5*pt2+3]) < eps)
96           {
97             // If so, perturb x,y and z to reset h to exactly zero.
98             for (auto pt: {pt1, pt2})  // thx C++17
99               {
100                 const double correc = _coords[pt*5+3]/3.; // this should be really small!
101                 _coords[pt*5+0] += correc;
102                 _coords[pt*5+1] += correc;
103                 _coords[pt*5+2] += correc;
104                 // And then, if x,y or z very close to 0 or 1, snap exactly to tetra corner:
105                 for(int d=0; d < 3; d++)
106                   {
107                     if (fabs(_coords[5*pt+d]) < eps)    _coords[5*pt+d] = 0.0;
108                     if (fabs(_coords[5*pt+d]-1) < eps)  _coords[5*pt+d] = 1.0;
109                   }
110                 _coords[pt*5+3] = 0.0;
111               }
112           }
113       }
114   }
115   
116   // ----------------------------------------------------------------------------------
117   //  Double and triple product calculations                           
118   // ----------------------------------------------------------------------------------
119   
120   /**
121    * Pre-calculates all double products for this triangle, and stores
122    * them internally. This method makes compensation for precision errors,
123    * and it is thus the "stable" double products that are stored.
124    *
125    */
126   void TransformedTriangle::preCalculateDoubleProducts()
127   {
128     if(_is_double_products_calculated)
129       return;
130
131     // -- calculate all unstable double products -- store in _doubleProducts
132     for(TriSegment seg = PQ ; seg <= RP ; seg = TriSegment(seg + 1))
133       {
134         for(DoubleProduct dp = C_YZ ; dp <= C_10 ; dp = DoubleProduct(dp + 1))
135           {
136             const int idx = 8*seg + dp;
137             _doubleProducts[idx] = calcUnstableC(seg, dp, _deltas[idx]);
138           }
139       }
140
141     std::map<double, TetraCorner> distances;
142
143     // -- (1) for each segment : check that double products satisfy Grandy, [46]
144     // -- and make corrections if not
145     for(TriSegment seg = PQ ; seg <= RP ; seg = TriSegment(seg + 1))
146       {
147         if(!areDoubleProductsConsistent(seg))
148           {
149             LOG(4, "inconsistent! ");
150             for(TetraCorner corner = O ; corner <= Z ; corner = TetraCorner(corner + 1))
151               {
152                 // calculate distance corner - segment axis
153                 const double dist = calculateDistanceCornerSegment(corner, seg);
154                 distances.insert( std::make_pair( dist, corner ) );
155               }
156
157             // first element -> minimum distance (because map is sorted)
158             const TetraCorner minCorner = distances.begin()->second;
159             resetDoubleProducts(seg, minCorner);
160             distances.clear();
161           }
162       }
163
164     // -- (2) check that each double product satisfies Grandy, [47], else set to 0
165     for(TriSegment seg = PQ ; seg <= RP ; seg = TriSegment(seg + 1))
166       {
167         for(DoubleProduct dp = C_YZ ; dp <=  C_10 ; dp = DoubleProduct(dp + 1))
168           {
169             const int idx = 8*seg+dp;
170
171             if( epsilonEqual(_doubleProducts[idx], 0.0, THRESHOLD_F * MULT_PREC_F * _deltas[idx]))
172               {
173                 // debug output
174 #if LOG_LEVEL >= 5
175                 if(_doubleProducts[8*seg + dp] != 0.0)
176                   {
177                     LOG(5, "Double product for (seg,dp) = (" << seg << ", " << dp << ") = " );
178                     LOG(5, std::fabs(_doubleProducts[8*seg + dp]) << " is imprecise, reset to 0.0" );
179                   }
180 #endif 
181
182                 _doubleProducts[idx] = 0.0;
183               }
184           }
185       }
186
187     _is_double_products_calculated = true;
188   }
189
190   /**
191    * Checks if the double products for a given segment are consistent, as defined by
192    * Grandy, [46]. 
193    *
194    * @param   seg Segment for which to check consistency of double products
195    * @return  true if the double products are consistent, false if not
196    */
197   bool TransformedTriangle::areDoubleProductsConsistent(const TriSegment seg) const
198   {
199     // Careful! Here doubleProducts have not yet been corrected for roundoff errors!
200     // So we need to epsilon-adjust to correctly identify zeros:
201     static const DoubleProduct DP_LST[6] = {C_YZ, C_XH,
202                                             C_ZX, C_YH,
203                                             C_XY, C_ZH};
204     double dps[6];
205     for (int i = 0; i < 6; i++)
206       {
207         const double dp = _doubleProducts[8*seg + DP_LST[i]];
208         dps[i] = dp;
209       }
210
211     const double term1 = dps[0] * dps[1];
212     const double term2 = dps[2] * dps[3];
213     const double term3 = dps[4] * dps[5];
214
215     LOG(2, "for seg " << seg << " consistency " << term1 + term2 + term3 );
216     LOG(2, "term1 :" << term1 << " term2 :" << term2 << " term3: " << term3 );
217
218     // Test for "== 0.0" here is OK since doubleProduct has been fixed for rounding to zero already.
219     const int num_zero = (term1 == 0.0 ? 1 : 0) + (term2 == 0.0 ? 1 : 0) + (term3 == 0.0 ? 1 : 0);
220     const int num_neg = (term1 < 0.0 ? 1 : 0) + (term2 < 0.0 ? 1 : 0) + (term3 < 0.0 ? 1 : 0);
221     const int num_pos = (term1 > 0.0 ? 1 : 0) + (term2 > 0.0 ? 1 : 0) + (term3 > 0.0 ? 1 : 0);
222
223     assert( num_zero + num_neg + num_pos == 3 );
224
225     // Calculated geometry is inconsistent if we have one of the following cases
226     // * one term zero and the other two of the same sign
227     // * two terms zero
228     // * all terms positive
229     // * all terms negative
230     const bool inconsist = (num_zero == 1 && num_neg != 1) ||
231                            num_zero == 2 ||
232                            (num_neg == 0 && num_zero != 3) ||
233                            num_neg == 3;
234     if(inconsist)  {
235         LOG(4, "inconsistent dp found" );
236       }
237     return !inconsist;
238   }
239
240   /**
241    * Calculate the shortest distance between a tetrahedron corner and a triangle segment.
242    * 
243    * @param  corner corner of the tetrahedron
244    * @param  seg    segment of the triangle
245    * @return shortest distance from the corner to the segment
246    */
247   double TransformedTriangle::calculateDistanceCornerSegment(const TetraCorner corner, const TriSegment seg) const
248   {
249     // NB uses fact that TriSegment <=> TriCorner that is first point of segment (PQ <=> P)
250     const TriCorner ptP_idx = TriCorner(seg);
251     const TriCorner ptQ_idx = TriCorner( (seg + 1) % 3);
252     
253     const double ptP[3] = { _coords[5*ptP_idx], _coords[5*ptP_idx + 1], _coords[5*ptP_idx + 2]  };
254     const double ptQ[3] = { _coords[5*ptQ_idx], _coords[5*ptQ_idx + 1], _coords[5*ptQ_idx + 2]  };
255     
256     // coordinates of corner
257     const double ptTetCorner[3] = 
258       { 
259         COORDS_TET_CORNER[3*corner    ],
260         COORDS_TET_CORNER[3*corner + 1],
261         COORDS_TET_CORNER[3*corner + 2]
262       };
263     
264     // dist^2 = ( PQ x CP )^2 / |PQ|^2 where C is the corner point
265     
266     // difference vectors
267     const double diffPQ[3] = { ptQ[0] - ptP[0], ptQ[1] - ptP[1], ptQ[2] - ptP[2] };
268     const double diffCornerP[3] = { ptP[0] - ptTetCorner[0], ptP[1] - ptTetCorner[1], ptP[2] - ptTetCorner[2] };
269     
270     // cross product of difference vectors
271     double crossProd[3];
272     cross(diffPQ, diffCornerP, crossProd);
273     
274     const double cross_squared = dot(crossProd, crossProd);
275     const double norm_diffPQ_squared = dot(diffPQ, diffPQ);
276     
277     assert(norm_diffPQ_squared != 0.0);
278     
279     return cross_squared / norm_diffPQ_squared;
280   }
281
282   /**
283    * Pre-calculates all triple products for the tetrahedron with respect to
284    * this triangle, and stores them internally. This method takes into account
285    * the problem of errors due to cancellation.
286    *
287    */
288   void TransformedTriangle::preCalculateTripleProducts()
289   {
290     if(_is_triple_products_calculated)
291       return;
292
293     // find edge / row to use -> that whose edge makes the smallest angle to the triangle
294     // use a map to find the minimum
295     std::map<double, int> anglesForRows;
296
297     LOG(4, "Precalculating triple products" );
298     for(TetraCorner corner = O ; corner <= Z ; corner = TetraCorner(corner + 1))
299       {
300         LOG(6, "- Triple product for corner " << corner );
301
302         for(int row = 1 ; row < 4 ; ++row) 
303           {
304             const DoubleProduct dp = DP_FOR_DETERMINANT_EXPANSION[3*corner + (row - 1)];
305
306             // get edge by using correspondence between Double Product and Edge
307             TetraEdge edge = TetraEdge(dp);
308
309             // use edge only if it is surrounded by the surface
310             if( _triangleSurroundsEdgeCache[edge] )
311                 {
312                   // -- calculate angle between edge and PQR
313                   const double angle = calculateAngleEdgeTriangle(edge);
314                   anglesForRows.insert(std::make_pair(angle, row));
315                 }
316           }
317
318         if(anglesForRows.size() != 0) // we have found a good row
319           {
320             const double minAngle = anglesForRows.begin()->first;
321             const int minRow = anglesForRows.begin()->second;
322
323             if(minAngle < TRIPLE_PRODUCT_ANGLE_THRESHOLD)
324               _tripleProducts[corner] = calcTByDevelopingRow(corner, minRow, true);
325             else 
326               _tripleProducts[corner] = calcTByDevelopingRow(corner, minRow, false);
327
328             _validTP[corner] = true;
329           }
330         else
331           {
332             // this value will not be used - we set it to whatever
333             LOG(6, "Triple product not calculated for corner " << corner );
334             _tripleProducts[corner] = std::nan("triplep");
335             _validTP[corner] = false;
336           }
337         anglesForRows.clear();
338       }
339     _is_triple_products_calculated = true;
340   }
341
342   /**
343    * Calculates the angle between an edge of the tetrahedron and the triangle
344    *
345    * @param  edge edge of the tetrahedron
346    * @return angle between triangle and edge
347    */
348   double TransformedTriangle::calculateAngleEdgeTriangle(const TetraEdge edge) const
349   {
350     // find normal to PQR - cross PQ and PR
351     const double pq[3] = 
352       { 
353         _coords[5*Q]     - _coords[5*P], 
354         _coords[5*Q + 1] - _coords[5*P + 1],
355         _coords[5*Q + 2] - _coords[5*P + 2]
356       };
357     
358     const double pr[3] = 
359       { 
360         _coords[5*R]     - _coords[5*P], 
361         _coords[5*R + 1] - _coords[5*P + 1],
362         _coords[5*R + 2] - _coords[5*P + 2]
363       };
364     
365     double normal[3];
366
367     cross(pq, pr, normal);
368     
369     static const double EDGE_VECTORS[18] =
370       {
371         1.0, 0.0, 0.0, // OX
372         0.0, 1.0, 0.0, // OY
373         0.0, 0.0, 1.0, // OZ
374         -1.0, 1.0, 0.0, // XY
375         0.0,-1.0, 1.0, // YZ
376         1.0, 0.0,-1.0 // ZX
377       };
378     
379     const double edgeVec[3] = { 
380       EDGE_VECTORS[3*edge],
381       EDGE_VECTORS[3*edge + 1],
382       EDGE_VECTORS[3*edge + 2],
383     };
384
385     //return angleBetweenVectors(normal, edgeVec);
386
387     const double lenNormal = norm(normal);
388     const double lenEdgeVec = norm(edgeVec);
389     const double dotProd = dot(normal, edgeVec);
390     
391     //? is this more stable? -> no subtraction
392     //    return asin( dotProd / ( lenNormal * lenEdgeVec ) ) + 3.141592625358979 / 2.0;
393     const double tmp=dotProd / ( lenNormal * lenEdgeVec );
394     const double safe_tmp=std::max(std::min(tmp,1.),-1.);
395     return M_PI - std::acos(safe_tmp);
396   }
397
398   /**
399    * Calculates triple product associated with the given corner of tetrahedron, developing
400    * the determinant by the given row. The triple product gives the signed volume of 
401    * the tetrahedron between this corner and the triangle PQR. If the flag project is true, 
402    * one coordinate is projected out in order to eliminate errors in the intersection point
403    * calculation due to cancellation.
404    * 
405    * Consistency with the double product computation and potential cancellation is also done here.
406    *
407    *
408    * @pre            double products have already been calculated
409    * @param corner   corner for which the triple product is calculated
410    * @param row      row (1 <= row <= 3) used to calculate the determinant
411    * @param project  indicates whether or not to perform projection as inidicated in Grandy, p.446
412    * @return        triple product associated with corner (see Grandy, [50]-[52])
413    */
414   double TransformedTriangle::calcTByDevelopingRow(const TetraCorner corner, const int row, const bool project) const
415   {
416     
417     // OVERVIEW OF CALCULATION
418     // --- sign before the determinant
419     // the sign used depends on the sign in front of the triple product (Grandy, [15]),
420     // and the convention used in the definition of the double products
421   
422     // the sign in front of the determinant gives the following schema for the three terms (I): 
423     // corner/row    1    2   3
424     // O (sign:+)    +    -   +
425     // X (sign:-)    -    +   -
426     // Y (sign:-)    -    +   -
427     // Z (sign:-)    -    +   -
428
429     // the 2x2 determinants are the following (C_AB <=> A_p*B_q - B_p*A_q, etc)
430     // corner/row    1       2     3
431     // O (sign:+)   C_YZ   C_XZ  C_XY
432     // X (sign:-)   C_YZ   C_HZ  C_HY
433     // Y (sign:-)   C_HZ   C_XZ  C_XH
434     // Z (sign:-)   C_YH   C_XH  C_XY
435
436     // these are represented in DP_FOR_DETERMINANT_EXPANSION,
437     // except for the fact that certain double products are inversed (C_AB <-> C_BA)
438
439     // comparing with the DOUBLE_PRODUCTS and using the fact that C_AB = -C_BA
440     // we deduce the following schema (II) :
441     // corner/row    1    2   3
442     // O (sign:+)    +    -   +
443     // X (sign:-)    +    -   -
444     // Y (sign:-)    -    -   +
445     // Z (sign:-)    +    +   +
446
447     // comparing the two schemas (I) and (II) gives us the following matrix of signs,
448     // putting 1 when the signs in (I) and (II) are equal and -1 when they are different :
449
450     static const int SIGNS[12] = 
451       {
452         1, 1, 1,
453         -1,-1, 1,
454         1,-1,-1,
455         -1, 1,-1
456       };
457
458     // find the offsets of the rows of the determinant
459     const int offset = COORDINATE_FOR_DETERMINANT_EXPANSION[3 * corner + (row - 1)];
460   
461     const DoubleProduct dp = DP_FOR_DETERMINANT_EXPANSION[3 * corner + (row - 1)];
462
463     const int sign = SIGNS[3 * corner + (row - 1)];
464
465     const double cQR = calcStableC(QR, dp);
466     const double cRP = calcStableC(RP, dp);
467     const double cPQ = calcStableC(PQ, dp);
468
469     double alpha = 0.0;
470
471     // coordinate to use for projection (Grandy, [57]) with edges
472     // OX, OY, OZ, XY, YZ, ZX in order : 
473     // (y, z, x, h, h, h)
474     // for the first three we could also use {2, 0, 1}
475     static const int PROJECTION_COORDS[6] = { 1, 2, 0, 3, 3, 3 } ;
476
477     const int coord = PROJECTION_COORDS[ dp ];
478
479     // coordinate values for P, Q and R
480     const double coordValues[3] = { _coords[5*P + coord], _coords[5*Q + coord], _coords[5*R + coord] };
481
482     if(project)
483       {
484         // products coordinate values with corresponding double product
485         const double coordDPProd[3] = { coordValues[0] * cQR, coordValues[1] * cRP, coordValues[2] * cPQ };
486
487         const double sumDPProd = coordDPProd[0] + coordDPProd[1] + coordDPProd[2];
488         const double sumDPProdSq = dot(coordDPProd, coordDPProd);
489
490         //       alpha = sumDPProd / sumDPProdSq;
491         alpha = (sumDPProdSq != 0.0) ? sumDPProd / sumDPProdSq : 0.0;
492       }
493
494     const double cQRbar = cQR * (1.0 - alpha * coordValues[0] * cQR);
495     const double cRPbar = cRP * (1.0 - alpha * coordValues[1] * cRP);
496     const double cPQbar = cPQ * (1.0 - alpha * coordValues[2] * cPQ);
497
498     // [ABN] Triple product cancellation logic:
499     // This part is not well described in Grandy (end of p.446) :
500     //     "We use a method analogous to (47) to remove imprecise triple products,..."
501     //
502     // Our algo for cancelling a triple product:
503     //   - retrieve the deltas associated with each DP involved (because a DP itself is a sum of two terms - see [42]
504     //   - multiply them by the coordinate coming from the determinant expansion
505     //   - and finally sum the 3 corresponding terms of the developement
506     //
507     // Using directly the DP (as was done before here) leads to issues, since this DP might have been cancelled
508     // already earlier on, and we lost the delta information -> doing this, we risk not cancelling the triple prod
509     // when we should have.
510     const double cQRbar_delta = _deltas[8*QR + dp],
511                  cRPbar_delta = _deltas[8*RP + dp],
512                  cPQbar_delta = _deltas[8*PQ + dp];
513
514     // check sign of barred products - should not change
515     //    assert(cQRbar * cQR >= 0.0);
516     //assert(cRPbar * cRP >= 0.0);
517     //assert(cPQbar * cPQ >= 0.0);
518
519     const double p_term = _coords[5*P + offset] * cQRbar;
520     const double q_term = _coords[5*Q + offset] * cRPbar;
521     const double r_term = _coords[5*R + offset] * cPQbar;
522
523     const double p_delta = std::fabs(_coords[5*P + offset] * cQRbar_delta),
524                  q_delta = std::fabs(_coords[5*Q + offset] * cRPbar_delta),
525                  r_delta = std::fabs(_coords[5*R + offset] * cPQbar_delta);
526
527     const double delta = p_delta + q_delta + r_delta;
528
529     if( epsilonEqual( p_term + q_term + r_term, 0.0, THRESHOLD_F * MULT_PREC_F * delta) )
530       {
531         LOG(4, "Reset imprecise triple product for corner " << corner << " to zero" ); 
532         return 0.0;
533       }
534     else
535       {
536         // NB : using plus also for the middle term compensates for a double product
537         // which is inversely ordered
538         LOG(6, "Triple product for corner " << corner << ", row " << row << " = " << sign*( p_term + q_term + r_term ) );
539         return sign*( p_term + q_term + r_term );
540       }
541
542   }
543
544 }