1 // Copyright (c) 2017-2021, Lawrence Livermore National Security, LLC and
2 // other Axom Project Developers. See the top-level LICENSE file for details.
3 //
4 // SPDX-License-Identifier: (BSD-3-Clause)
5 
6 // Axom includes
7 #include "axom/quest/MeshTester.hpp"
8 
9 namespace axom
10 {
11 namespace quest
12 {
compute_bounds(detail::UMesh * mesh)13 inline detail::SpatialBoundingBox compute_bounds(detail::UMesh* mesh)
14 {
15   SLIC_ASSERT(mesh != nullptr);
16 
17   detail::SpatialBoundingBox meshBB;
18   detail::Point3 pt;
19 
20   const double* x = mesh->getCoordinateArray(mint::X_COORDINATE);
21   const double* y = mesh->getCoordinateArray(mint::Y_COORDINATE);
22   const double* z = mesh->getCoordinateArray(mint::Z_COORDINATE);
23 
24   const axom::IndexType numNodes = mesh->getNumberOfNodes();
25   for(axom::IndexType i = 0; i < numNodes; ++i)
26   {
27     pt[0] = x[i];
28     pt[1] = y[i];
29     pt[2] = z[i];
30 
31     meshBB.addPoint(pt);
32   }  // END for all nodes
33 
34   if(numNodes > 0)
35   {
36     SLIC_ASSERT(meshBB.isValid());
37   }
38 
39   return meshBB;
40 }
41 
areTriangleIndicesDistinct(axom::IndexType * indices)42 inline bool areTriangleIndicesDistinct(axom::IndexType* indices)
43 {
44   SLIC_ASSERT(indices != nullptr);
45 
46   return indices[0] != indices[1] && indices[1] != indices[2] &&
47     indices[2] != indices[0];
48 }
49 
50 /* Find and report self-intersections and degenerate triangles
51  * in a triangle surface mesh using a Uniform Grid. */
findTriMeshIntersections(detail::UMesh * surface_mesh,std::vector<std::pair<int,int>> & intersections,std::vector<int> & degenerateIndices,int spatialIndexResolution,double intersectionThreshold)52 void findTriMeshIntersections(detail::UMesh* surface_mesh,
53                               std::vector<std::pair<int, int>>& intersections,
54                               std::vector<int>& degenerateIndices,
55                               int spatialIndexResolution,
56                               double intersectionThreshold)
57 {
58   detail::Triangle3 t1 {};
59   detail::Triangle3 t2 {};
60   SLIC_INFO("Running mesh_tester with UniformGrid index");
61 
62   // Create a bounding box around mesh to find the minimum point
63   detail::SpatialBoundingBox meshBB = compute_bounds(surface_mesh);
64   const detail::Point3& minBBPt = meshBB.getMin();
65   const detail::Point3& maxBBPt = meshBB.getMax();
66 
67   const int ncells = surface_mesh->getNumberOfCells();
68 
69   // find the specified resolution.  If we're passed a number less than one,
70   // use the cube root of the number of triangles.
71   if(spatialIndexResolution < 1)
72   {
73     spatialIndexResolution = (int)(1 + std::pow(ncells, 1 / 3.));
74   }
75   int resolutions[3] = {spatialIndexResolution,
76                         spatialIndexResolution,
77                         spatialIndexResolution};
78 
79   SLIC_INFO("Building UniformGrid index...");
80   detail::UniformGrid3 ugrid(minBBPt.data(), maxBBPt.data(), resolutions);
81   std::vector<int> nondegenerateIndices;
82   nondegenerateIndices.reserve(ncells);
83 
84   for(int i = 0; i < ncells; i++)
85   {
86     t1 = getMeshTriangle(i, surface_mesh);
87 
88     if(t1.degenerate())
89     {
90       degenerateIndices.push_back(i);
91     }
92     else
93     {
94       nondegenerateIndices.push_back(i);
95 
96       detail::SpatialBoundingBox triBB = compute_bounding_box(t1);
97       ugrid.insert(triBB, i);
98     }
99   }
100 
101   // Iterate through triangle indices *idx.
102   // Check against each other triangle with index greater than the index *idx
103   // that also shares a UniformGrid bin.
104   SLIC_INFO("Checking mesh with a total of " << ncells << " cells.");
105 
106   std::vector<int>::iterator idx = nondegenerateIndices.begin(),
107                              ndgend = nondegenerateIndices.end();
108   for(; idx != ndgend; ++idx)
109   {
110     // Retrieve the triangle at *idx and construct a bounding box around it
111     t1 = getMeshTriangle(*idx, surface_mesh);
112     detail::SpatialBoundingBox triBB2 = compute_bounding_box(t1);
113 
114     // Get a list of all triangles in bins this triangle will touch,
115     // whose indices are greater than this triangle's index
116     std::vector<int> neighborTriangles;
117     const std::vector<int> binsToCheck = ugrid.getBinsForBbox(triBB2);
118     size_t checkcount = binsToCheck.size();
119     for(size_t curbin = 0; curbin < checkcount; ++curbin)
120     {
121       std::vector<int> ntlist = ugrid.getBinContents(binsToCheck[curbin]);
122       std::vector<int>::iterator ntlit = ntlist.begin(), ntlend = ntlist.end();
123       for(; ntlit != ntlend; ++ntlit)
124       {
125         if(*ntlit > *idx)
126         {
127           neighborTriangles.push_back(*ntlit);
128         }
129       }
130     }
131 
132     std::sort(neighborTriangles.begin(), neighborTriangles.end());
133     std::vector<int>::iterator nend =
134       std::unique(neighborTriangles.begin(), neighborTriangles.end());
135     std::vector<int>::iterator nit = neighborTriangles.begin();
136 
137     // test any remaining neighbor tris for intersection
138     while(nit != nend)
139     {
140       t2 = getMeshTriangle(*nit, surface_mesh);
141       if(primal::intersect(t1, t2, false, intersectionThreshold))
142       {
143         intersections.push_back(std::make_pair(*idx, *nit));
144       }
145       ++nit;
146     }
147   }
148 }
149 
150 /* Check a surface mesh for holes using its face relation. */
isSurfaceMeshWatertight(detail::UMesh * surface_mesh)151 WatertightStatus isSurfaceMeshWatertight(detail::UMesh* surface_mesh)
152 {
153   // Make sure the mesh is reasonable
154   SLIC_ASSERT_MSG(surface_mesh != nullptr,
155                   "surface_mesh must be a valid pointer to a triangle mesh");
156 
157   // Calculate the face relations---this can take awhile
158   bool success = surface_mesh->initializeFaceConnectivity();
159 
160   if(!success)
161   {
162     return WatertightStatus::CHECK_FAILED;
163   }
164 
165   WatertightStatus retval = WatertightStatus::WATERTIGHT;
166 
167   constexpr int ON_BOUNDARY = 1;
168   constexpr int INTERNAL = 0;
169 
170   // Create fields to store boundary
171   int* bndry_face =
172     surface_mesh->createField<int>("bndry_face", mint::FACE_CENTERED);
173   int* boundary = surface_mesh->createField<int>("boundary", mint::CELL_CENTERED);
174 
175   // Mark boundary faces
176   const IndexType numFaces = surface_mesh->getNumberOfFaces();
177   for(IndexType iface = 0; iface < numFaces; ++iface)
178   {
179     IndexType c1, c2;
180     surface_mesh->getFaceCellIDs(iface, c1, c2);
181     SLIC_ASSERT(c1 != static_cast<IndexType>(mint::UNDEFINED_CELL));
182 
183     if(c2 == static_cast<IndexType>(mint::UNDEFINED_CELL))
184     {
185       bndry_face[iface] = ON_BOUNDARY;
186       retval = WatertightStatus::NOT_WATERTIGHT;
187     }
188     else
189     {
190       bndry_face[iface] = INTERNAL;
191     }
192   }  // END for all faces
193 
194   if(retval == WatertightStatus::WATERTIGHT)
195   {
196     /* short-circuit */
197     const IndexType numCells = surface_mesh->getNumberOfCells();
198     std::memset(boundary, INTERNAL, sizeof(int) * numCells);
199     return retval;
200   }
201 
202   // Mark boundary cells
203   const IndexType numCells = surface_mesh->getNumberOfCells();
204   for(IndexType icell = 0; icell < numCells; ++icell)
205   {
206     // NOTE: this check currently assumes triangles
207     SLIC_ASSERT(surface_mesh->getNumberOfCellFaces(icell) == 3);
208     const IndexType* faceids = surface_mesh->getCellFaceIDs(icell);
209 
210     if((bndry_face[faceids[0]] == ON_BOUNDARY) ||
211        (bndry_face[faceids[1]] == ON_BOUNDARY) ||
212        (bndry_face[faceids[2]] == ON_BOUNDARY))
213     {
214       boundary[icell] = ON_BOUNDARY;
215     }
216     else
217     {
218       boundary[icell] = INTERNAL;
219     }
220 
221   }  // END for all cells
222 
223   return retval;
224 }
225 
226 /* Weld vertices of a triangle mesh that are closer than \a eps  */
weldTriMeshVertices(detail::UMesh ** surface_mesh,double eps)227 void weldTriMeshVertices(detail::UMesh** surface_mesh, double eps)
228 {
229   // Note: Use 64-bit index to accomodate small values of epsilon
230   using IdxType = axom::int64;
231   using Lattice3 = spin::RectangularLattice<3, double, IdxType>;
232   using GridCell = Lattice3::GridCell;
233 
234   // Define a lambda for hashing points
235   // implementation of hash combiner is from boost's hash_combine()
236   auto point_hash = [](const GridCell& pt) {
237     auto seed = std::hash<IdxType> {}(pt[0]);
238     for(int i = 1; i < GridCell::DIMENSION; ++i)
239     {
240       seed ^=
241         std::hash<IdxType> {}(pt[i]) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
242     }
243     return seed;
244   };
245 
246   using GridCellToIndexMap =
247     std::unordered_map<GridCell, IdxType, decltype(point_hash)>;
248 
249   /// Implementation notes:
250   ///
251   /// This function welds vertices in the triangle mesh by
252   /// quantizing them to an integer lattice with spacing \a eps.
253   ///
254   /// To avoid searching neighbor elements on the grid, we run the
255   /// algorithm twice. Once on the original lattice, and once on a
256   /// lattice that is shifted by half the grid spacing.
257   ///
258   /// Due to running this algorithm twice, it is possible in extreme cases
259   /// for vertices that are at distances up to (1.5 * eps)
260   /// (under the max norm) to be identified in this process.
261   /// This can correspond to distances of at most 1.5 * eps * \sqrt(2)
262   /// in the Euclidean norm.
263 
264   SLIC_ASSERT_MSG(eps > 0.,
265                   "Epsilon must be greater than 0. Passed in value was " << eps);
266   SLIC_ASSERT_MSG(
267     surface_mesh != nullptr && *surface_mesh != nullptr,
268     "surface_mesh must be a valid pointer to a pointer to a triangle mesh");
269 
270   int const DIM = 3;
271   detail::UMesh* oldMesh = *surface_mesh;
272 
273   detail::SpatialBoundingBox meshBB = compute_bounds(oldMesh).expand(eps);
274 
275   // Run the algorithm twice -- on the original grid and a translated grid
276   std::vector<double> offsets;
277   offsets.push_back(0);
278   offsets.push_back(eps / 2.);
279   for(std::vector<double>::const_iterator it = offsets.begin();
280       it != offsets.end();
281       ++it)
282   {
283     // We will build up a new triangle mesh with the welded indices
284     detail::UMesh* newMesh = new detail::UMesh(DIM, mint::TRIANGLE);
285 
286     // Set up the lattice for quantizing points to an integer lattice
287     detail::Point3 origin(meshBB.getMin().array() - detail::Point3(*it).array());
288     Lattice3 lattice(origin, Lattice3::SpaceVector(detail::Point3(eps)));
289 
290     // First, find unique indices for the welded vertices
291     const int numVerts = oldMesh->getNumberOfNodes();
292     int uniqueVertCount = 0;
293 
294     // A map from GridCells to the new vertex indices
295     GridCellToIndexMap vertexIndexMap(numVerts, point_hash);
296 
297     std::vector<int> vertex_remap;  // stores the new vertex indices
298     vertex_remap.resize(numVerts);  // for each old vertex
299 
300     detail::Point3 vert;
301     const double* x = oldMesh->getCoordinateArray(mint::X_COORDINATE);
302     const double* y = oldMesh->getCoordinateArray(mint::Y_COORDINATE);
303     const double* z = oldMesh->getCoordinateArray(mint::Z_COORDINATE);
304 
305     for(int i = 0; i < numVerts; ++i)
306     {
307       // get the vertex from the mesh
308       vert[0] = x[i];
309       vert[1] = y[i];
310       vert[2] = z[i];
311 
312       // find the new vertex index; if not present, insert vertex into new mesh
313       auto res = vertexIndexMap.insert(
314         std::make_pair(lattice.gridCell(vert), uniqueVertCount));
315       if(res.second == true)
316       {
317         uniqueVertCount++;
318         newMesh->appendNodes(vert.data());
319       }
320       vertex_remap[i] = static_cast<int>(res.first->second);
321     }
322 
323     // Next, add triangles into the new mesh using the unique vertex indices
324     const int NUM_TRI_VERTS = 3;
325     axom::IndexType triInds[NUM_TRI_VERTS];
326     const axom::IndexType numTris = oldMesh->getNumberOfCells();
327     for(axom::IndexType i = 0; i < numTris; ++i)
328     {
329       memcpy(triInds,
330              oldMesh->getCellNodeIDs(i),
331              NUM_TRI_VERTS * sizeof(axom::IndexType));
332 
333       for(int d = 0; d < NUM_TRI_VERTS; ++d)
334       {
335         triInds[d] = vertex_remap[triInds[d]];
336       }
337 
338       // Degeneracy check -- vertices need to be distinct
339       if(areTriangleIndicesDistinct(triInds))
340       {
341         newMesh->appendCell(triInds, mint::TRIANGLE);
342       }
343     }
344 
345     // Finally, delete old mesh and swap pointers
346     delete oldMesh;
347     oldMesh = newMesh;
348   }
349 
350   // Update the original mesh pointer to the new mesh
351   *surface_mesh = oldMesh;
352 }
353 
354 }  // end namespace quest
355 }  // end namespace axom
356