1 // Copyright (C) 2007-2024 CEA, EDF
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.
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.
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
17 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
20 #ifndef __TRANSFORMED_TRIANGLE_HXX__
21 #define __TRANSFORMED_TRIANGLE_HXX__
23 #include "INTERPKERNELDefines.hxx"
24 #include "VectorUtils.hxx"
30 // 1 - overview of algorithm + volume result
31 // 2 - algorithm detail
32 // 3 - intersection polygon results detail
33 // 4 - intersection polygon search detail
34 // higher -> misc. gory details of calculation
39 #pragma warning(disable:4251)
44 class TransformedTriangleTest;
45 class TransformedTriangleIntersectTest;
49 namespace INTERP_KERNEL
51 class TetraAffineTransform;
53 /** \class TransformedTriangle
54 * \brief Class representing one of the faces of the triangulated source polyhedron after having been transformed
55 * with the affine transform that takes the target tetrahedron to the unit tetrahedron. It contains the
56 * logic for calculating the volume of intersection between the triangle and the unit tetrahedron.
58 * \see TransformedTriangle.hxx
60 * Reference : J. Grandy, "Conservative Remapping and Region Overlays by Intersecting Arbitrary Polyhedra",
61 * Journal of Computational Physics (1999)
66 * OVERVIEW of how the class works : (details can be found in the documentation of each method)
69 * The constructor takes as arguments three pointers to double[3] vectors holding the transformed
70 * coordinates of the corners of the triangle. It copies their coordinates and then proceeds to pre-calculating certain
71 * entities used in the intersection calculation : the double products, triple products and the values of the function E
73 * It is also at this point in constructor that:
74 * - the special case of PQR included in the XYZ plane is treated
75 * - the inconsistencies between double products/triple products computation is handled
77 * calculateIntersectionVolume() :
78 * This is the only method in the public interface. It calculates the volume under the intersection polygons
79 * between the triangle and the unit tetrahedron, as described in Grandy, pp. 435-447. It does this by first calculating the
80 * intersection polygons A and B, with the method calculateIntersectionPolygons(). It then calculates the barycenter of each
81 * polygon in calculatePolygonBarycenter(), and sorts their points in a circular order around the barycenter in
82 * sortIntersecionPolygon(). The sorting is done with STL sort, using the order defined in the class
83 * ProjectedCentralCircularSortOrder. The volume under each polygon is then calculated with calculateVolumeUnderPolygon(), which
84 * implements formula [34] in Grandy.
86 * calculateIntersectionPolygons() :
87 * This method goes through all the possible ways in which the triangle can intersect the tetrahedron and tests for these
88 * types of intersections in accordance with the formulas described in Grandy. These tests are implemented in the test* - methods.
89 * The formulas in the article are stated for one case each only, while the calculation must take into account all cases.
90 * To this end, a number of tables, implemented as static const arrays of different types, are used. The tables
91 * mainly contain values of the different enumeration types described at the beginning of the class interface. For example,
92 * the formula Grandy gives for the segment-halfstrip intersection tests ([30]) is for use with the halfstrip above the zx edge.
93 * For the other two halfstrips (above the xy and yz edges), other double products are used, which
94 * are stored in the table DP_FOR_HALFSTRIP_INTERSECTION. This allows us to treat
95 * all the edges equally, avoiding switch() - statements. It is the careful choice of order of the enumeration types that makes this
96 * possible. Notably, there is a correspondence between the TetraEdge type and the DoubleProduct type (see Grandy, table III) that
97 * is used throughout the code, permitting statements such as DoubleProduct(some_edge) to work.
98 * When an intersection point has been detected it is calculated with a corresponding calc* - method in the cases where it
99 * is not known directly. It is then added to the polygon A and/or B as necessary.
102 class INTERPKERNEL_EXPORT TransformedTriangle
107 friend class INTERP_TEST::TransformedTriangleIntersectTest;
108 friend class INTERP_TEST::TransformedTriangleTest;
110 * Enumerations representing the different geometric elements of the unit tetrahedron
111 * and the triangle. The end element, NO_* gives the number of elements in the enumeration
112 * and can be used as end element in loops.
115 /// Corners of tetrahedron
116 enum TetraCorner { O = 0, X, Y, Z, NO_TET_CORNER };
118 /// Edges of tetrahedron
119 enum TetraEdge { OX = 0, OY, OZ, XY, YZ, ZX, H01, H10, NO_TET_EDGE };
121 /// Facets (faces) of tetrahedron
122 enum TetraFacet { OYZ = 0, OZX, OXY, XYZ, NO_TET_FACET };
124 /// Corners of triangle
125 enum TriCorner { P = 0, Q, R, NO_TRI_CORNER };
127 /// Segments (edges) of triangle
128 enum TriSegment { PQ = 0, QR, RP, NO_TRI_SEGMENT };
130 /// Intersection polygons
131 enum IntersectionPolygon{ A = 0, B, NO_INTERSECTION_POLYGONS };
134 /// NB : order corresponds to TetraEdges (Grandy, table III)
135 enum DoubleProduct { C_YZ = 0, C_ZX, C_XY, C_ZH, C_XH, C_YH, C_01, C_10, NO_DP };
137 TransformedTriangle(double* p, double* q, double* r);
138 ~TransformedTriangle();
140 double calculateIntersectionVolume();
141 double calculateIntersectionSurface(TetraAffineTransform* tat);
142 void dumpCoords() const;
144 // Queries of member values used by UnitTetraIntersectionBary
145 const double* getCorner(TriCorner corner) const { return _coords + 5*corner; }
146 const std::vector<double*>& getPolygonA() const { return _polygonA; }
147 double getVolume() const { return _volume; }
150 TransformedTriangle() { }
152 // ----------------------------------------------------------------------------------
153 // High-level methods called directly by calculateIntersectionVolume()
154 // ----------------------------------------------------------------------------------
155 void calculateIntersectionAndProjectionPolygons();
156 void calculatePolygonBarycenter(const IntersectionPolygon poly, double* barycenter);
157 void sortIntersectionPolygon(const IntersectionPolygon poly, const double* barycenter);
158 double calculateVolumeUnderPolygon(IntersectionPolygon poly, const double* barycenter);
160 // ----------------------------------------------------------------------------------
161 // High-level methods called directly by calculateIntersectionSurface()
162 // ----------------------------------------------------------------------------------
163 void calculateIntersectionPolygon();
164 double calculateSurfacePolygon();
166 // ----------------------------------------------------------------------------------
167 // Detection of degenerate triangles
168 // ----------------------------------------------------------------------------------
169 bool isTriangleInPlaneOfFacet(const TetraFacet facet) const;
170 bool isTriangleParallelToFacet(const TetraFacet facet) const;
171 int isTriangleInclinedToFacet(const TetraFacet facet) const;
172 bool isTriangleBelowTetraeder() const;
174 // ----------------------------------------------------------------------------------
175 // Intersection test methods and intersection point calculations
176 // ----------------------------------------------------------------------------------
177 inline bool testSurfaceEdgeIntersection(const TetraEdge edge) const;
178 void calcIntersectionPtSurfaceEdge(const TetraEdge edge, double* pt) const;
179 inline bool testSegmentFacetIntersection(const TriSegment seg, const TetraFacet facet) const;
180 void calcIntersectionPtSegmentFacet(const TriSegment seg, const TetraFacet facet, double* pt) const;
181 bool testSegmentEdgeIntersection(const TriSegment seg, const TetraEdge edge) const;
182 void calcIntersectionPtSegmentEdge(const TriSegment seg, const TetraEdge edge, double* pt) const ;
183 bool testSegmentCornerIntersection(const TriSegment seg, const TetraCorner corner) const ;
184 inline bool testSurfaceRayIntersection(const TetraCorner corner) const;
185 bool testSegmentHalfstripIntersection(const TriSegment seg, const TetraEdge edg);
186 void calcIntersectionPtSegmentHalfstrip(const TriSegment seg, const TetraEdge edge, double* pt) const;
187 bool testSegmentRayIntersection(const TriSegment seg, const TetraCorner corner) const;
188 inline bool testCornerInTetrahedron(const TriCorner corner) const;
189 inline bool testCornerOnXYZFacet(const TriCorner corner) const;
190 inline bool testCornerAboveXYZFacet(const TriCorner corner) const;
192 // ----------------------------------------------------------------------------------
193 // Utility methods used in intersection tests
194 // ----------------------------------------------------------------------------------
195 bool testTriangleSurroundsEdge(const TetraEdge edge) const;
196 inline bool testEdgeIntersectsTriangle(const TetraEdge edge) const;
197 inline bool testFacetSurroundsSegment(const TriSegment seg, const TetraFacet facet) const;
198 inline bool testSegmentIntersectsFacet(const TriSegment seg, const TetraFacet facet) const;
199 bool testSegmentIntersectsHPlane(const TriSegment seg) const;
200 bool testSurfaceAboveCorner(const TetraCorner corner) const;
201 bool testTriangleSurroundsRay(const TetraCorner corner) const;
203 // ----------------------------------------------------------------------------------
204 // Double and triple product calculations
205 // ----------------------------------------------------------------------------------
206 void handleDegenerateCases();
207 bool areDoubleProductsConsistent(const TriSegment seg) const;
208 void preCalculateDoubleProducts();
209 inline void resetDoubleProducts(const TriSegment seg, const TetraCorner corner);
210 double calculateDistanceCornerSegment(const TetraCorner corner, const TriSegment seg) const;
211 void preCalculateTripleProducts();
212 double calculateAngleEdgeTriangle(const TetraEdge edge) const;
213 inline double calcStableC(const TriSegment seg, const DoubleProduct dp) const;
214 inline double calcStableT(const TetraCorner corner) const;
215 inline double calcUnstableC(const TriSegment seg, const DoubleProduct dp, double & delta) const;
216 double calcTByDevelopingRow(const TetraCorner corner, const int row, const bool project) const;
218 // ----------------------------------------------------------------------------------
220 // ----------------------------------------------------------------------------------
221 inline const std::string& strTC(TetraCorner tc) const;
222 inline const std::string& strTE(TetraEdge te) const;
223 inline const std::string& strTF(TetraFacet tf) const;
224 inline const std::string& strTriC(TriCorner tc) const;
225 inline const std::string& strTriS(TriSegment tc) const;
227 // ----------------------------------------------------------------------------------
229 // ----------------------------------------------------------------------------------
232 /// Array holding the coordinates of the triangle's three corners
234 /// [ p_x, p_y, p_z, p_h, p_H, q_x, q_y, q_z, q_h, q_H, r_x, r_y, r_z, r_h, r_H ]
237 /// Flag showing whether the double products have been calculated yet
238 bool _is_double_products_calculated;
240 /// Flag showing whether the triple products have been calculated yet
241 bool _is_triple_products_calculated;
243 /// Array containing the 24 double products.
244 /// order : c^PQ_YZ, ... ,cPQ_10, ... c^QR_YZ, ... c^RP_YZ
245 /// following order in enumeration DoubleProduct
246 double _doubleProducts[24];
250 /// Array containing the 4 triple products.
251 /// order : t_O, t_X, t_Y, t_Z
252 /// For example t_O represent the signed volume of the tetrahedron OPQR, and is positive if PQR is oriented clockwise
253 // when seen from the vertex O.
254 double _tripleProducts[4];
256 /// Vector holding the points of the intersection polygon A.
257 /// these points are allocated in calculateIntersectionPolygons() and liberated in the destructor
258 std::vector<double*> _polygonA;
260 /// Vector holding the points of the intersection polygon B.
261 /// These points are allocated in calculateIntersectionPolygons() and liberated in the destructor
262 std::vector<double*> _polygonB;
264 /// Array holding the coordinates of the barycenter of the polygon A
265 /// This point is calculated in calculatePolygonBarycenter
266 double _barycenterA[3];
268 /// Array holding the coordinates of the barycenter of the polygon B
269 /// This point is calculated in calculatePolygonBarycenter
270 //double _barycenterB[3];
272 /// Array of flags indicating which of the four triple products have been correctly calculated.
273 /// Used for asserts in debug mode
276 /// calculated volume for use of UnitTetraIntersectionBary
280 * Calls TransformedTriangle::testTriangleSurroundsEdge for edges OX to ZX and stores the result in
281 * member variable array_triangleSurroundsEdgeCache.
284 void preCalculateTriangleSurroundsEdge();
286 /// Array holding results of the test testTriangleSurroundsEdge() for all the edges.
287 /// These are calculated in preCalculateTriangleSurroundsEdge().
288 bool _triangleSurroundsEdgeCache[NO_TET_EDGE];
290 // ----------------------------------------------------------------------------------
292 // ----------------------------------------------------------------------------------
294 // offsets : 0 -> x, 1 -> y, 2 -> z, 3 -> h, 4 -> H
295 // corresponds to order of double products in DoubleProduct
296 // so that offset[C_*] gives the right coordinate
297 static const int DP_OFFSET_1[8];
298 static const int DP_OFFSET_2[8];
300 // the coordinates used in the expansion of triple products by a given row
301 // in constellation (corner, row-1)
302 // (0,1,2,3) <=> (x,y,z,h)
303 static const int COORDINATE_FOR_DETERMINANT_EXPANSION[12];
305 // contains the edge of the double product used when
306 // expanding the triple product determinant associated with each corner
308 static const DoubleProduct DP_FOR_DETERMINANT_EXPANSION[12];
310 // values used to decide how/when imprecise the double products
311 // should be to set them to 0.0
312 static const double MACH_EPS; // machine epsilon
313 static const double MULT_PREC_F; // precision of multiplications (Grandy : f)
314 static const double THRESHOLD_F; // threshold for zeroing (Grandy : F/f)
316 static const double TRIPLE_PRODUCT_ANGLE_THRESHOLD;
318 // correspondence facet - double product
320 static const DoubleProduct DP_FOR_SEG_FACET_INTERSECTION[12];
322 // signs associated with entries in DP_FOR_SEGMENT_FACET_INTERSECTION
323 static const double SIGN_FOR_SEG_FACET_INTERSECTION[12];
325 // coordinates of corners of tetrahedron
326 static const double COORDS_TET_CORNER[12];
328 // indices to use in tables DP_FOR_SEG_FACET_INTERSECTION and SIGN_FOR_SEG_FACET_INTERSECTION
329 // for the calculation of the coordinates (x,y,z) of the intersection points
330 // for Segment-Facet and Segment-Edge intersections
331 static const int DP_INDEX[12];
333 // correspondence edge - corners
334 static const TetraCorner CORNERS_FOR_EDGE[12];
336 // correspondence edge - facets
337 // facets shared by each edge
338 static const TetraFacet FACET_FOR_EDGE[12];
340 // correspondence edge - corners
341 static const TetraEdge EDGES_FOR_CORNER[12];
343 // double products used in segment-halfstrip test
344 static const DoubleProduct DP_FOR_HALFSTRIP_INTERSECTION[12];
346 // double products used in segment - ray test
347 static const DoubleProduct DP_SEGMENT_RAY_INTERSECTION[21];
351 inline void TransformedTriangle::preCalculateTriangleSurroundsEdge()
353 for(TetraEdge edge = OX ; edge <= ZX ; edge = TetraEdge(edge + 1))
355 _triangleSurroundsEdgeCache[edge] = testTriangleSurroundsEdge(edge);
360 // ----------------------------------------------------------------------------------
361 // TransformedTriangle_math.cxx
362 // ----------------------------------------------------------------------------------
364 inline void TransformedTriangle::resetDoubleProducts(const TriSegment seg, const TetraCorner corner)
366 // set the three corresponding double products to 0.0
367 static const DoubleProduct DOUBLE_PRODUCTS[12] =
369 C_YZ, C_ZX, C_XY, // O
370 C_YZ, C_ZH, C_YH, // X
371 C_ZX, C_ZH, C_XH, // Y
372 C_XY, C_YH, C_XH // Z
375 for(int i = 0 ; i < 3 ; ++i) {
376 const DoubleProduct dp = DOUBLE_PRODUCTS[3*corner + i];
378 LOG(6, std::endl << "resetting inconsistent dp :" << dp << " for corner " << corner);
379 _doubleProducts[8*seg + dp] = 0.0;
383 inline double TransformedTriangle::calcStableC(const TriSegment seg, const DoubleProduct dp) const
385 return _doubleProducts[8*seg + dp];
388 inline double TransformedTriangle::calcStableT(const TetraCorner corner) const
390 assert(_validTP[corner]);
391 return _tripleProducts[corner];
394 inline double TransformedTriangle::calcUnstableC(const TriSegment seg, const DoubleProduct dp, double& delta) const
396 // find the points of the triangle
397 // 0 -> P, 1 -> Q, 2 -> R
399 const int pt2 = (seg + 1) % 3;
402 const int off1 = DP_OFFSET_1[dp];
403 const int off2 = DP_OFFSET_2[dp];
405 const double prd1 = _coords[5*pt1 + off1] * _coords[5*pt2 + off2],
406 prd2 = _coords[5*pt1 + off2] * _coords[5*pt2 + off1];
407 delta = std::fabs(prd1) + std::fabs(prd2);
411 // ----------------------------------------------------------------------------------
412 // TransformedTriangle_intersect.cxx
413 // ----------------------------------------------------------------------------------
414 inline bool TransformedTriangle::testSurfaceEdgeIntersection(const TetraEdge edge) const
416 return _triangleSurroundsEdgeCache[edge] && testEdgeIntersectsTriangle(edge);
419 inline bool TransformedTriangle::testSegmentFacetIntersection(const TriSegment seg, const TetraFacet facet) const
421 return testFacetSurroundsSegment(seg, facet) && testSegmentIntersectsFacet(seg, facet);
424 inline bool TransformedTriangle::testSurfaceRayIntersection(const TetraCorner corner) const
426 return testTriangleSurroundsRay( corner ) && testSurfaceAboveCorner( corner );
429 inline bool TransformedTriangle::testCornerInTetrahedron(const TriCorner corner) const
433 _coords[5*corner], // x
434 _coords[5*corner + 1], // y
435 _coords[5*corner + 2], // z
436 _coords[5*corner + 3] // z
439 for(int i = 0 ; i < 4 ; ++i)
441 if(pt[i] < 0.0 || pt[i] > 1.0)
449 inline bool TransformedTriangle::testCornerOnXYZFacet(const TriCorner corner) const
454 _coords[5*corner], // x
455 _coords[5*corner + 1], // y
456 _coords[5*corner + 2], // z
457 _coords[5*corner + 3] // h
460 const double* pt = &_coords[5*corner];
467 for(int i = 0 ; i < 3 ; ++i)
469 if(pt[i] < 0.0 || pt[i] > 1.0)
477 inline bool TransformedTriangle::testCornerAboveXYZFacet(const TriCorner corner) const
479 const double x = _coords[5*corner];
480 const double y = _coords[5*corner + 1];
481 const double h = _coords[5*corner + 3];
482 const double H = _coords[5*corner + 4];
484 return h < 0.0 && H >= 0.0 && x >= 0.0 && y >= 0.0;
488 inline bool TransformedTriangle::testEdgeIntersectsTriangle(const TetraEdge edge) const
490 // correspondence edge - triple products for edges OX, ..., ZX (Grandy, table III)
491 static const TetraCorner TRIPLE_PRODUCTS[12] =
502 const double t1 = calcStableT(TRIPLE_PRODUCTS[2*edge]);
503 const double t2 = calcStableT(TRIPLE_PRODUCTS[2*edge + 1]);
505 // [ABN] Okayyy: if either t1 or t2 exactly equal zero, then it can mean two things:
506 // - either PQR is very close to the corner -> this is OK, further computation of intersection point between
507 // surface and edge will produce a correct result
508 // - or, if the other triple prod is also very small, then this is a degenerate case: the edge is almost in PQR -> this is bad
509 // and leads to weird intersection point computation -> we avoid this.
510 // PS : here was written "// tuleap26461" -> whoo this helps :-)
511 if (t1 == 0.0 || t2 == 0.0)
512 if (std::fabs(t1+t2) < THRESHOLD_F*MULT_PREC_F)
515 return (t1*t2 <= 0.0) && !epsilonEqual(t1,t2, MULT_PREC_F);
518 inline bool TransformedTriangle::testFacetSurroundsSegment(const TriSegment seg, const TetraFacet facet) const
521 const double signs[3] =
523 SIGN_FOR_SEG_FACET_INTERSECTION[3*facet],
524 SIGN_FOR_SEG_FACET_INTERSECTION[3*facet + 1],
525 SIGN_FOR_SEG_FACET_INTERSECTION[3*facet + 2]
529 const double* signs = &SIGN_FOR_SEG_FACET_INTERSECTION[3*facet];
530 const double c1 = signs[0]*calcStableC(seg, DP_FOR_SEG_FACET_INTERSECTION[3*facet]);
531 const double c2 = signs[1]*calcStableC(seg, DP_FOR_SEG_FACET_INTERSECTION[3*facet + 1]);
532 const double c3 = signs[2]*calcStableC(seg, DP_FOR_SEG_FACET_INTERSECTION[3*facet + 2]);
534 return (c1*c3 > 0.0) && (c2*c3 > 0.0);
537 inline bool TransformedTriangle::testSegmentIntersectsFacet(const TriSegment seg, const TetraFacet facet) const
539 // use correspondence facet a = 0 <=> offset for coordinate a in _coords
540 // and also correspondence segment AB => corner A
541 const double coord1 = _coords[5*seg + facet];
542 const double coord2 = _coords[5*( (seg + 1) % 3) + facet];
544 //? should we use epsilon-equality here in second test?
545 LOG(5, "coord1 : " << coord1 << " coord2 : " << coord2 );
547 return (coord1*coord2 <= 0.0) && (coord1 != coord2);
550 inline bool TransformedTriangle::testSegmentIntersectsHPlane(const TriSegment seg) const
552 // get the H - coordinates
553 const double coord1 = _coords[5*seg + 4];
554 const double coord2 = _coords[5*( (seg + 1) % 3) + 4];
555 //? should we use epsilon-equality here in second test?
556 LOG(5, "coord1 : " << coord1 << " coord2 : " << coord2 );
558 return (coord1*coord2 <= 0.0) && (coord1 != coord2);
561 inline bool TransformedTriangle::testSurfaceAboveCorner(const TetraCorner corner) const
563 // There is an error in Grandy -> it should be C_XY instead of C_YZ in [28].
565 // Idea: the nz value (Grandy [28] corrected!) can be interpreted as a special variant of the triple product t_O
566 // where the z coordinate has been set to 1. It represents the signed volume of the tet OP'Q'R' where P', Q' and R' are the
567 // projection of P, Q, R on the z=1 plane.
568 // Comparing the sign of this triple product with t_X (or t_Y, ... dep on the corner) indicates whether the corner is in the
569 // half-space above or below the PQR triangle, similarly to what is explained in [16].
570 // (this works even for the Z corner, since we could have chosen z=24 (instead of z=1) this would not have changed the final sign test).
571 const double nz = calcStableC(PQ, C_XY) + calcStableC(QR, C_XY) + calcStableC(RP, C_XY);
573 // Triple product might have not been computed, but here we need one:
574 const double tp = _validTP[corner] ? calcStableT(corner) : calcTByDevelopingRow(corner, 1, false);
579 inline bool TransformedTriangle::testTriangleSurroundsRay(const TetraCorner corner) const
581 // double products to use for the possible corners
582 static const DoubleProduct DP_FOR_RAY_INTERSECTION[4] =
584 DoubleProduct(0), // O - only here to fill out and make indices match
590 const DoubleProduct dp = DP_FOR_RAY_INTERSECTION[corner];
592 const double cPQ = calcStableC(PQ, dp);
593 const double cQR = calcStableC(QR, dp);
594 const double cRP = calcStableC(RP, dp);
596 return ( cPQ*cQR > 0.0 ) && ( cPQ*cRP > 0.0 );
599 inline const std::string& TransformedTriangle::strTC(TetraCorner tc) const
601 static const std::map<TetraCorner, std::string> m = {{O, "O"}, {X, "X"}, {Y, "Y"}, {Z, "Z"}};
605 inline const std::string& TransformedTriangle::strTE(TetraEdge te) const
607 static const std::map<TetraEdge, std::string> m = {{OX, "OX"}, {OY, "OY"}, {OZ, "OZ"}, {XY, "XY"}, {YZ, "YZ"},
608 {ZX, "ZX"}, {H01, "H01"}, {H10, "H10"}};
612 inline const std::string& TransformedTriangle::strTF(TetraFacet tf) const
614 static const std::map<TetraFacet, std::string> m = {{OYZ, "OYZ"}, {OZX, "OZX"}, {OXY, "OXY"}, {XYZ, "XYZ"}};
618 inline const std::string& TransformedTriangle::strTriC(TriCorner tc) const
620 static const std::map<TriCorner, std::string> m = {{P, "P"}, {Q, "Q"}, {R, "R"}};
624 inline const std::string& TransformedTriangle::strTriS(TriSegment ts) const
626 static const std::map<TriSegment, std::string> m = {{PQ, "PQ"}, {QR, "QR"}, {RP, "RP"}};