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